Android 项目:画图白板APP开发(八)——Matrix位移放大缩小(附demo)
本文介绍了Android中使用Matrix类实现图形变换(位移、缩放)的方法。Matrix是处理2D图形变换的核心类,支持缩放、旋转、平移等操作。文章详细讲解了Matrix的主要方法:setScale()、setRotate()、setTranslate()等基本变换,以及postTranslate()、postScale()等追加变换方法。通过一个白板应用Demo,展示了如何结合触摸事件实现图形
在画图白板应用中,实现图形的位移、放大和缩小是基本且重要的功能。本文将介绍如何使用 Android 的 Matrix 类来实现这些变换,并提供一个完整的示例代码。
一、Matrix
Matrix 是 Android 中用于处理 2D 图形变换的核心类,它封装了一个 3×3 的矩阵,用于实现坐标变换。理解 Matrix 对于开发图形处理应用至关重要。
(1)主要功能
缩放(Scale):可以改变图片的大小,比如放大或缩小
旋转(Rotate):可以将图片绕某个点旋转一定的角度
平移(Translate):可以移动图片的位置
倾斜(Skew):可以让图片变形,比如将图片斜着拉长
(2)主要方法
1. matrix.set(Matrix matrix)
将一个矩阵的值复制到当前矩阵中
Matrix matrix1 = new Matrix();
Matrix matrix2 = new Matrix();
matrix1.setScale(2f, 2f);
matrix2.set(matrix1); // 把 matrix1 的值赋给 matrix2
2. matrix.setScale(float sx, float sy)
缩放图片
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f); // 把图片放大两倍
- 参数解析:
- sx:横向缩放比例;值大于 1 表示放大,值小于 1 表示缩小
- sy:纵向缩放比例;值大于 1 表示放大,值小于 1 表示缩小
3. matrix.setRotate(float degrees)
旋转图片
Matrix matrix = new Matrix();
matrix.setRotate(45); // 旋转45度
- 参数解析:
- degrees:旋转的角度;正数表示顺时针旋转,负数表示逆时针旋转
4. matrix.setTranslate(float dx, float dy)
移动图片
Matrix matrix = new Matrix();
matrix.setTranslate(100, 200); // 将图片向右移动100像素,向下移动200像素
- 参数解析:
- dx:横向移动距离
- dy:纵向移动距离
5. matrix.setSkew(float kx, float ky)
倾斜图片;它可以让图片产生类似于拉长、拉伸的效果
Matrix matrix = new Matrix();
matrix.setSkew(0.5f, 0.2f); // 使图片横向倾斜0.5,纵向倾斜0.2
- 参数解析:
- kx:横向倾斜系数
- ky:纵向倾斜系数
6. matrix.postTranslate(float dx, float dy)
基于现有的变换再做移动操作
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f);
matrix.postTranslate(100f, 100f); // 先缩放再平移
后面的操作都是一样的,前面加post就是追加的方法
matrix.postScale(float sx, float sy):基于当前变换追加缩放操作matrix.postRotate(float degrees):基于现有的矩阵进行旋转操作matrix.postSkew(float kx, float ky):在已有的矩阵操作上追加倾斜操作matrix.postConcat(Matrix other):请先执行我当前已有的所有变换,然后追加执行other矩阵所代表的变换。
7. matrix.invert(Matrix inverse)
用于求当前矩阵的逆矩阵,如果当前矩阵是可逆的,会将逆矩阵存储到 inverse 参数中,返回 true,否则返回 false
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f);
Matrix inverseMatrix = new Matrix();
boolean isInvertible = matrix.invert(inverseMatrix); // 计算逆矩阵
- 参数解析:
- inverse:用于得出的 当前matrix 对象 是否为逆矩阵
8. matrix.mapRect(RectF rect)
对矩形进行变换。矩形会根据当前矩阵进行缩放、平移、旋转等变换
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f);
RectF rect = new RectF(0, 0, 100, 100);
matrix.mapRect(rect); // 变换后的矩形坐标会改变
- 参数解析:
- rect:接收一个重定矩形的 RectF 对象
9. matrix.reset()
将当前的矩阵重置为单位矩阵。单位矩阵即初始状态,表示没有任何变换
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f);
matrix.reset(); // 重置为单位矩阵
10. matrix.preTranslate(float dx, float dy)
在已有矩阵操作之前进行平移
Matrix matrix = new Matrix();
matrix.setScale(2f, 2f);
matrix.preTranslate(50f, 50f); // 先平移再缩放
- 参数解析:
- dx:横向移动距离
- dy:纵向移动距离
类似的还有;
matrix.preScale(float sx, float sy):在已有矩阵操作之前进行缩放matrix.preRotate(float degrees):在已有矩阵操作之前进行旋转matrix.preSkew(float kx, float ky):在已有矩阵操作之前进行倾斜matrix.preConcat(Matrix other):先执行other矩阵,然后执行已有的矩阵所
11. matrix.setConcat(Matrix a, Matrix b)
将两个矩阵相乘,并将结果赋给当前矩阵。相当于 Matrix 的乘法运算
Matrix matrix1 = new Matrix();
matrix1.setScale(2f, 2f);
Matrix matrix2 = new Matrix();
matrix2.setRotate(45);
Matrix result = new Matrix();
result.setConcat(matrix1, matrix2); // 将 matrix1 和 matrix2 组合
- 参数解析:
- a:要组合的第一个 Matrix 对象
- b:要组合的第二个 Matrix 对象
12. matrix.setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)
将源点数组 src 变换为目标点数组 dst。可以指定多少个点参与变换,允许更复杂的几何操作。主要将源多边形的多个点通过变换映射到目标多边形的相应点。通过指定多个点,Matrix 会计算出从源点到目标点的变换矩阵。
Matrix matrix = new Matrix();
float[] src = {0f, 0f, 100f, 0f}; // 源多边形的两个点:两个点为 (0, 0) 和 (100, 0)
float[] dst = {0f, 0f, 150f, 50f}; // 目标多边形的两个点:目标点为 (0, 0) 和 (150, 50
matrix.setPolyToPoly(src, 0, dst, 0, 2); // 使用两个点进行变换。srcIndex 和 dstIndex 都为 0,表示从第一个点开始使用。pointCount 为 2,表示我们使用两个点来计算变换
- 参数解析:
- src:源点数组,包含源多边形的所有点的坐标。这个数组按顺序存储点的坐标信息,数组的长度应该是
2 * n,n 是点的数量。每个点由两个连续的浮点数表示,分别是 x 和 y 坐标。例如:src = {x1, y1, x2, y2, x3, y3, ...} - srcIndex:源点数组的起始索引,从这个位置开始读取源点数据。例如:如果
srcIndex = 0,则从 src 数组的第一个点开始使用;如果srcIndex = 2,则从数组中的第三个浮点数(即第二个点的 x 坐标)开始使用 - dst:目标点数组,包含目标多边形的所有点的坐标。这个数组与 src 类似,也按顺序存储目标多边形的坐标。Matrix 会根据这些点计算变换矩阵,将源多边形的点映射到这些目标点上
- dstIndex:目标点数组的起始索引,从这个位置开始读取目标点数据。例如:如果
dstIndex = 0,则从 dst 数组的第一个点开始使用;如果dstIndex = 2,则从数组中的第三个浮点数(即第二个点的 x 坐标)开始使用 - pointCount:参与变换的点的数量。pointCount 决定了 src 和 dst 中的点有多少个会被用于计算变换矩阵。最多支持 4 个点的变换,具体的点数决定了变换的复杂程度:
- 1个点:只能实现平移
- 2个点:可以实现平移和缩放(单轴或双轴缩放)
- 3个点:可以实现平移、缩放和旋转
- 4个点:可以实现平移、缩放、旋转和任意形变(透视变换)
- src:源点数组,包含源多边形的所有点的坐标。这个数组按顺序存储点的坐标信息,数组的长度应该是
13. matrix.getValues(float[] values)
将当前 Matrix 对象中的 9 个值(即 3x3 矩阵的所有元素)复制到指定的浮点数数组中。3X3的结构如下:
[ scaleX, skewX, transX ]
[ skewY, scaleY, transY ]
[ 0, 0, 1 ]
这是最关键的部分。传入的数组 values 的 9 个元素(索引 0 到 8)分别对应矩阵的以下位置:
| 数组索引 (index) | 矩阵中的含义 | 简写 | 对应的变换 |
|---|---|---|---|
values[0] |
MSCALE_X | a |
X 轴缩放 |
values[1] |
MSKEW_X | b |
X 轴错切 |
values[2] |
MTRANS_X | c |
X 轴平移 |
values[3] |
MSKEW_Y | d |
Y 轴错切 |
values[4] |
MSCALE_Y | e |
Y 轴缩放 |
values[5] |
MTRANS_Y | f |
Y 轴平移 |
values[6] |
MPERSP_0 | g |
透视变换 (通常为0) |
values[7] |
MPERSP_1 | h |
透视变换 (通常为0) |
values[8] |
MPERSP_2 | i |
透视变换 (通常为1) |
更直观的矩阵视图:
[ values[0] values[1] values[2] ]
[ values[3] values[4] values[5] ]
[ values[6] values[7] values[8] ]
二、demo
接下来,我们通过一个demo程序讲解下,上面方法的用法。展示图如下

平移功能:

放大缩小功能:限制了范围 30%-300%

至于撤销,反撤销与本章无关,并且这个demo里的撤销、反撤销功能也不完善。接下来直接直接上代码。
(1)activity_test16.xml
布局代码
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@drawable/beijing"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/ll_test16"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/bt_test16_model"
android:text="画画"
android:layout_margin="1dp"
android:layout_width="65dp"
android:layout_height="wrap_content"/>
<Button
android:layout_margin="1dp"
android:id="@+id/bt_test16_clear"
android:text="清空"
android:layout_width="65dp"
android:layout_height="wrap_content"/>
<Button
android:layout_margin="1dp"
android:id="@+id/bt_test16_cancel"
android:text="撤销"
android:layout_width="65dp"
android:layout_height="wrap_content"/>
<Button
android:layout_margin="1dp"
android:id="@+id/bt_test16_revoke"
android:text="反撤销"
android:layout_width="80dp"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:id="@+id/tv_16"
android:layout_marginTop="30dp"
android:layout_gravity="end"
android:text="100%"
android:textStyle="bold"
android:textColor="@color/black"
android:layout_width="40dp"
android:layout_height="wrap_content"/>
</FrameLayout>
(2)Test16Activity.java
public class Test16Activity extends Activity {
private Button bt_model;
private Button bt_cancel,bt_revoke,bt_clear;
private DrawView_Canvas drawView;
private TextView tv_16;
private Toast mToast;
private Handler handler;
@SuppressLint("ShowToast")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test16);
LinearLayout line = findViewById(R.id.ll_test16);
bt_model = findViewById(R.id.bt_test16_model);
bt_clear = findViewById(R.id.bt_test16_clear);
bt_cancel = findViewById(R.id.bt_test16_cancel);
bt_revoke = findViewById(R.id.bt_test16_revoke);
tv_16 = findViewById(R.id.tv_16);
//使用testView显示百分比
//一般使用一个要经常变化的数据,一般使用什么东西????先试试handle
handler = new Handler(msg -> {
if(msg.what == 0x101){
//将一个float的值传入
tv_16.setText(FTOString((Float) msg.obj));
if(mToast == null) {
System.out.println("AAAAAAAAAAAAAA 1:"+msg.obj);
mToast = Toast.makeText(Test16Activity.this,"当前比例:"+FTOString((Float) msg.obj) , Toast.LENGTH_SHORT);
}else {
System.out.println("AAAAAAAAAAAAAA 2:"+msg.obj);
mToast.setText("当前比例:"+FTOString((Float) msg.obj));
mToast.setDuration(Toast.LENGTH_SHORT);
}
mToast.show();
}
return false;
});
DisplayMetrics displayMetrics = new DisplayMetrics();
//获取创建的宽度和高度
getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
//创建一个DrawView 宽高一致
drawView = new DrawView_Canvas(Test16Activity.this,displayMetrics.widthPixels,displayMetrics.heightPixels,handler);
line.addView(drawView);
bt_clear.setOnClickListener(v-> drawView.clear());
bt_cancel.setOnClickListener(v-> drawView.revoked());
bt_revoke.setOnClickListener(v-> drawView.unRevoked());
bt_model.setOnClickListener(v->{
if(bt_model.getText()=="拖拽"){
bt_model.setText("画画");
drawView.mModel=1;
}else {
bt_model.setText("拖拽");
drawView.mModel=2;
}
});
//--------------handle最新使用方法
// handler = new Handler(new Handler.Callback() {
// @Override
// public boolean handleMessage(@NonNull Message msg) {
// return false;
// }
// });
}
private String FTOString(float f) {
String s = "";
int i;
f=f*100f;
i = (int) f;
if(i<30){
i=30;
}else if(i>300){
i=300;
}
s=i+"%";
return s;
}
}
通过 handler 传递放大缩小的比例消息,使用Toast显示的同时修改文字百分比
(3)DrawView_Canvas.java
public class DrawView_Canvas extends View {
private float[] mStartXs = new float[20];
private float[] mStartYs = new float[20];
private PaintDates_Canvas[] Paints = new PaintDates_Canvas[20];
private ArrayList<PaintDates_Canvas> mPaintedList = new ArrayList<>();
private ArrayList<PaintDates_Canvas> mRevokedList = new ArrayList<>();
private final int REVOKE = 1;
private final int UN_REVOKE = 2;
//private Path path = new Path();
private Path[] paths = new Path[20];
//其他的路径
Paint paint = new Paint(Paint.DITHER_FLAG);
private Bitmap cacheBitmap;
private Canvas cacheCanvas;
//为了保障大小来个总的
private Matrix matrixMain = new Matrix();
//在来一个中介
private Matrix matrix=new Matrix();
private Matrix savedMatrix=new Matrix();
float[] mainDate = new float[9]; //说白了只获取一个数据
float[] runDate = new float[9];
float nowBL = 1.0f;
//数据的范围在0.3到3
static final int NONE = 0;
static final int DRAG = 1;
static final int ZOOM = 2;
int mode = NONE;
//精度为f的点
PointF start = new PointF();
PointF mid = new PointF();
PointF midStart = new PointF();
float oldDist = 1f;
//求两指操作时间距离
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
//设置两指操作的中心点(缩放的基点)
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
public int mModel = 1;//默认为先画画
static final int HUA_HUA = 1;
static final int CAO_ZUO = 2;
private Handler handler;
public DrawView_Canvas(Context context , int width, int height,Handler handler){
super(context);
if(cacheCanvas == null){
cacheBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
cacheCanvas = new Canvas(cacheBitmap);
}
paint.setColor(Color.RED);
//设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(12);
//反锯齿
paint.setAntiAlias(true);
paint.setDither(true);
matrix.set(getMatrix());
matrixMain.set(getMatrix());
//将每个path都实例化
for (int i = 0; i < 20; i++) {
paths[i] = new Path();
}
this.handler = handler;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
//保存原始状态
if(mModel ==HUA_HUA){
// 从前一个点绘制到当前点之后,把当前点定义成下次绘制的前一个点
paths[0].moveTo(event.getX(0),event.getY(0));
mStartXs[0] = event.getX(0);
mStartYs[0] = event.getY(0);
//这里没画上去的原因:因为目前保存线的那个类没有画点的功能
//cacheCanvas.drawPoint(x,y,paint);
//保存画笔
//mPaintedList.add(new PaintDates_Canvas(new Paint(paint),new Path(path)));
Paints[0] = new PaintDates_Canvas(new Paint(paint),new Path(paths[0]));
}else {
savedMatrix.set(matrix);
start.set(event.getX(),event.getY());
mode=DRAG;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if(mModel == HUA_HUA){
//获取手指的个数
//System.out.println("AAAAAAAAAAAAAAAAA 有几个点"+event.getPointerCount());
for (int i = 1; i < event.getPointerCount(); i++) {
int pointerId = event.getPointerId(i);
if(Paints[pointerId] == null){
paths[pointerId].moveTo(event.getX(i),event.getY(i));
mStartXs[pointerId] = event.getX(i) ;//获取手指落下的x坐标
mStartYs[pointerId] = event.getY(i);//获取手指落下的y坐标
Paints[pointerId] = new PaintDates_Canvas(new Paint(paint),new Path(paths[pointerId]));
}
}
}else {
oldDist = spacing(event);
if(oldDist>10f){
savedMatrix.set(matrix);
//设置mid点
midPoint(midStart,event); //当个起点
midPoint(mid,event);
//模式设置为缩放
mode=ZOOM;
//缩放也可以拖拽
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
if(mModel == CAO_ZUO){
mode=NONE;
}else {
int pointerId = event.getPointerId(event.getActionIndex());
if(Paints[pointerId]!=null){ ////???????
mPaintedList.add(new PaintDates_Canvas(Paints[pointerId].mPaint,Paints[pointerId].mPath));
}
paths[pointerId].reset();
Paints[pointerId] = null;
}
break;
case MotionEvent.ACTION_MOVE:
if(mModel == HUA_HUA){
// 从前一个点绘制到当前点之后,把当前点定义成下次绘制的前一个点
float cx;
float cy;
System.out.println("AAAAAAAAAAA HS:" + +event.getHistorySize());
for (int i = 0; i < event.getPointerCount(); i++) {
int pointerId = event.getPointerId(i);
cx = (event.getX(i)+mStartXs[pointerId])/2f;
cy = (event.getY(i)+mStartYs[pointerId])/2f;
if(Paints[pointerId] != null){ ////????是因为相应的太多劈叉了?
Paints[pointerId].mPath.quadTo(mStartXs[pointerId],mStartYs[pointerId],cx,cy);
}
//查看出问题的id
mStartXs[pointerId] = event.getX(i);
mStartYs[pointerId] = event.getY(i);
}
}else {
if(mode==DRAG){
matrix.set(savedMatrix);
matrix.postTranslate(event.getX()-start.x, event.getY()-start.y);
}else if(mode==ZOOM){
float newDist=spacing(event);
if(newDist>10){
//防止抖动
if(Math.abs(newDist-oldDist)>1f){
matrix.set(savedMatrix);
//两者的比
midPoint(mid,event);
float scale=newDist/oldDist;
//比例+基点
//两者的顺序不一样最后导致的结果也不同(注意)先位移在缩放
matrix.postTranslate(mid.x-midStart.x, mid.y-midStart.y);
matrixMain.getValues(mainDate);
//在此需要判断一下是否在缩放范围
if(nowBL<0.3f){
if(scale>1){
matrix.postScale(scale, scale, mid.x, mid.y);
}else {
matrix.postScale((0.3f/mainDate[0]), (0.3f/mainDate[0]), mid.x, mid.y);
}
}else if(nowBL>3f){
if(scale<=1){
matrix.postScale(scale, scale, mid.x, mid.y);
}else {
matrix.postScale((3f/mainDate[0]), (3f/mainDate[0]), mid.x, mid.y);
}
}else {
matrix.postScale(scale, scale, mid.x, mid.y);
//首先排除掉两个临界的错误值(变换后的值)
matrix.getValues(runDate);
nowBL = mainDate[0]*runDate[0];
if(nowBL<0.3f){
matrix.reset();
matrix.postTranslate(mid.x-midStart.x, mid.y-midStart.y);
matrix.postScale((0.3f/mainDate[0]), (0.3f/mainDate[0]), mid.x, mid.y);
}else if(nowBL>3f){
matrix.reset();
matrix.postTranslate(mid.x-midStart.x, mid.y-midStart.y);
matrix.postScale((3f/mainDate[0]), (3f/mainDate[0]), mid.x, mid.y);
//这块部分有个bug:
//放大的过程中,到达300%时出现了短暂的偏移。(问题很小就这样吧)
}
}
matrix.getValues(runDate);
nowBL = mainDate[0]*runDate[0];
Message m = this.handler.obtainMessage();
m.what = 0x101;
m.obj = nowBL;
this.handler.sendMessage(m);
//到达临界值是可能传入不在规定范围内的值(所以要限定)
}
}
}
}
break;
case MotionEvent.ACTION_UP:
if(mModel==HUA_HUA){
//保存路径
//path.reset();
// mPaintedList.add(new PaintDates_Canvas(Paints[0].mPaint,Paints[0].mPath));
// Paints[0] = null;
// paths[0].reset();
int pointerId = event.getPointerId(event.getActionIndex());
if(Paints[pointerId]!=null){ ////????
mPaintedList.add(new PaintDates_Canvas(Paints[pointerId].mPaint,Paints[pointerId].mPath));
}
paths[pointerId].reset();
Paints[pointerId] = null;
}else {
//每个都循环一次保存一次状态
for (int i = 0; i <mPaintedList.size() ; i++) {
mPaintedList.get(i).mMatrixS.add(new MatrixDate(new Matrix(matrix),0,0,0)) ;
}
//每次使用的时候都保存一下
matrixMain.postConcat(matrix);
//保存存完之后应该清空
matrix.reset();
}
break;
}
invalidate();
return true;
}
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR); //清空画布
//清空的原因:因为在移动的时候会画很多重叠。
//临时画一下
for(PaintDates_Canvas paint1:Paints){
if(mModel==HUA_HUA){
if(paint1!=null){
paint1.draw(canvas);
}
}
}
for(PaintDates_Canvas paint1:mPaintedList){
if(mModel==HUA_HUA){
Matrix matrix_ls = null;
cacheCanvas.save();
for (int i = 0; i < paint1.mMatrixS.size(); i++) {
if(i==0){
matrix_ls =new Matrix(paint1.mMatrixS.get(i).mMatrix);
}else {
matrix_ls.postConcat(paint1.mMatrixS.get(i).mMatrix);
}
}
if(matrix_ls != null){
cacheCanvas.setMatrix(matrix_ls);
}
paint1.draw(cacheCanvas);
cacheCanvas.restore();
}else {
Matrix matrix_ls = null;
cacheCanvas.save();
for (int i = 0; i < paint1.mMatrixS.size(); i++) {
if(i==0){
matrix_ls =new Matrix(paint1.mMatrixS.get(i).mMatrix);
}else {
matrix_ls.postConcat(paint1.mMatrixS.get(i).mMatrix);
}
}
if(matrix_ls != null){
matrix_ls.postConcat(matrix); //实现动态的加载
//matrix_ls.preConcat(matrix);
cacheCanvas.setMatrix(matrix_ls);
}else {
cacheCanvas.setMatrix(matrix);
}
paint1.draw(cacheCanvas);
cacheCanvas.restore();
}
}
canvas.drawBitmap(cacheBitmap,0f,0f,null);
}
public void clear(){
mRevokedList.clear();
mPaintedList.clear();
cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR);
invalidate();
}
//撤销
public void revoked(){
reDraw(mPaintedList,REVOKE);
}
//反撤销
public void unRevoked(){
reDraw(mRevokedList,UN_REVOKE);
}
//重新绘图
private void reDraw(ArrayList<PaintDates_Canvas> List, int type) {
if(List.size() > 0){
//获取并删除
PaintDates_Canvas paint = List.get(List.size()-1);
List.remove(List.size()-1);
if(type == REVOKE){
mRevokedList.add(paint);
System.out.println("AAAAAAAAAA"+" mPaintedList:"+List.size()+" mRevokedList"+mRevokedList.size());
}else {
mPaintedList.add(paint);
System.out.println("BBBBBBBBBB"+" mPaintedList"+ mPaintedList.size()+" mRevokedList:"+List.size());
}
//清空缓存画板
cacheCanvas.drawColor(0,PorterDuff.Mode.CLEAR);
invalidate();
}
}
}
matrixMain:记录所有历史变换的累积结果(即当前画布的总变换状态)。
matrix:用于处理当前正在进行的变换(例如用户正在拖拽或缩放时的微小变换)。
savedMatrix:在每次触摸事件开始时保存当前matrix的状态,以便基于该状态进行增量变换(如postTranslate或postScale)。
处理触摸事件(onTouchEvent)
-
在
ACTION_DOWN或ACTION_POINTER_DOWN时,保存当前matrix状态到savedMatrix。 -
在
ACTION_MOVE中,根据当前模式(DRAG或ZOOM)更新matrix:-
拖拽(DRAG):使用
postTranslate实现平移。注意是先平移后缩放 -
缩放(ZOOM):使用
postScale实现缩放,并限制缩放比例在0.3f到3.0f之间。
-
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
matrix.set(savedMatrix);
matrix.postTranslate(event.getX() - start.x, event.getY() - start.y);
} else if (mode == ZOOM) {
// 计算缩放比例并应用
float scale = newDist / oldDist;
matrix.postScale(scale, scale, mid.x, mid.y);
// 限制缩放范围
// ...
}
break;
在 onDraw 中应用变换
-
在绘制每个
PaintDates_Canvas时,会遍历其保存的mMatrixS列表,依次应用所有历史变换(通过postConcat)。 -
如果是操作模式(
CAO_ZUO),还会叠加当前正在进行的变换(matrix),实现实时预览效果。
if (matrix_ls != null) {
matrix_ls.postConcat(matrix); // 叠加当前变换
cacheCanvas.setMatrix(matrix_ls);
} else {
cacheCanvas.setMatrix(matrix);
}
(4)PaintDates_Canvas.java
public class PaintDates_Canvas {
Paint mPaint;
Path mPath ;
//保存每一笔画的偏移
ArrayList<MatrixDate> mMatrixS = new ArrayList<>();
public PaintDates_Canvas(Paint mPaint, Path mPath) {
this.mPaint = mPaint;
this.mPath = mPath;
}
public void draw(Canvas canvas){
canvas.drawPath(mPath,mPaint);
}
}
(5)MatrixDate.java
public class MatrixDate {
Matrix mMatrix;
//xy的偏移量
float mOffsetX;
float mOffsetY;
//偏移倍数
float mMultiple;
public MatrixDate(Matrix mMatrix, float mOffsetX, float mOffsetY, float mMultiple) {
this.mMatrix = mMatrix;
this.mOffsetX = mOffsetX;
this.mOffsetY = mOffsetY;
this.mMultiple = mMultiple;
}
}
更多推荐
所有评论(0)