当前位置: 首页 > news >正文

Android 项目:画图白板APP开发(八)——Matrix位移放大缩小(附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 * nn 是点的数量每个点两个连续的浮点数表示,分别是 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个点:可以实现平移缩放旋转任意形变(透视变换)

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_XaX 轴缩放
values[1]MSKEW_XbX 轴错切
values[2]MTRANS_XcX 轴平移
values[3]MSKEW_YdY 轴错切
values[4]MSCALE_YeY 轴缩放
values[5]MTRANS_YfY 轴平移
values[6]MPERSP_0g透视变换 (通常为0)
values[7]MPERSP_1h透视变换 (通常为0)
values[8]MPERSP_2i透视变换 (通常为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"><LinearLayoutandroid:id="@+id/ll_test16"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"></LinearLayout><LinearLayoutandroid:orientation="horizontal"android:layout_width="wrap_content"android:layout_height="wrap_content"><Buttonandroid:id="@+id/bt_test16_model"android:text="画画"android:layout_margin="1dp"android:layout_width="65dp"android:layout_height="wrap_content"/><Buttonandroid:layout_margin="1dp"android:id="@+id/bt_test16_clear"android:text="清空"android:layout_width="65dp"android:layout_height="wrap_content"/><Buttonandroid:layout_margin="1dp"android:id="@+id/bt_test16_cancel"android:text="撤销"android:layout_width="65dp"android:layout_height="wrap_content"/><Buttonandroid:layout_margin="1dp"android:id="@+id/bt_test16_revoke"android:text="反撤销"android:layout_width="80dp"android:layout_height="wrap_content"/></LinearLayout><TextViewandroid: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")@Overrideprotected 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显示百分比//一般使用一个要经常变化的数据,一般使用什么东西????先试试handlehandler = 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到3static 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;}@Overridepublic 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);}//查看出问题的idmStartXs[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")@Overrideprotected 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;}
}

http://www.dtcms.com/a/384859.html

相关文章:

  • 【大前端++】【混合开发】【node】express 文件服务器本地搭建-模拟加载图片使用
  • 如何启动Greenplum中的某个segment
  • 校验用户身份是否过期,是否存在等等JWT
  • Docker 多阶段镜像构建与缓存利用性能优化实践指南
  • Jenkinsfile配置【1】
  • 2025年渗透测试面试题总结-72(题目+回答)
  • 网络安全相关搜索引擎
  • 【Unity性能优化——Stats面板】
  • 【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
  • AI如何赋能跨境支付,亚马逊云科技与PayerMax的联合探索
  • PAT乙级_1125 子串与子列_Python_AC解法_含疑难点
  • 华清远见25072班网络编程学习day6
  • 国标GB28181视频平台EasyGBS国标GB28181软件与公安数字化安防技术衔接方案
  • 我的Web开发实践笔记:从编码设置到项目运营
  • Regression Trees|回归树
  • [数据结构——Lesson14.快速排序]
  • 城乡供水一体化智慧水务管理系统方案——推动供水高质量发展的御控工业物联网解决方案
  • 云上安全的第一道门槛:身份与访问控制
  • Blender MCP—基于AI代理的智能三维建模协同框架
  • 从零开始打造复杂动作网页:现代CSS3动画与JavaScript交互完全指南
  • 基于 OpenCV 实现实时文档扫描:从轮廓检测到透视变换全流程解析
  • Qt 系统相关 - 事件2
  • iTwinjs GeoLocation
  • 【氮化镓】C缺陷络合物导致的GaN黄光发射
  • Docker 下部署 Elasticsearch 8 并集成 Kibana 和 IK 分词器
  • 机器学习-第一章
  • 【Java EE进阶 --- SpringBoot】SpringBoot配置文件
  • 安装gemini-fullstack-langgraph-quickstart
  • IBM-Waston电信客户流失归因分析报告
  • 江协科技STM32课程笔记(二)