AndroidUI之Matrix

   Matrix在android开发中,特别是一些高级UI的绘制中,做平移,旋转,缩放,错切操作很是适用,但是其实Android的API中已经帮我们封装好了Matrix的很多方法和操作,现在就想记录一下学习的过程。

   打开Android的Matrix的API(我用的SDK版本是28),首先可以看到9个常量:

    public static final int MSCALE_X = 0;   //!< use with getValues/setValues
    public static final int MSKEW_X  = 1;   //!< use with getValues/setValues
    public static final int MTRANS_X = 2;   //!< use with getValues/setValues
    public static final int MSKEW_Y  = 3;   //!< use with getValues/setValues
    public static final int MSCALE_Y = 4;   //!< use with getValues/setValues
    public static final int MTRANS_Y = 5;   //!< use with getValues/setValues
    public static final int MPERSP_0 = 6;   //!< use with getValues/setValues
    public static final int MPERSP_1 = 7;   //!< use with getValues/setValues
    public static final int MPERSP_2 = 8;   //!< use with getValues/setValues

其实可以猜测,这9个常量可以代表着下面这样一个3*3 的矩阵

这里会有一个问题,我们针对的平移,缩放,旋转,错切都是针对点的,一般我们在手机中的2D坐标(x,y),按照之前学的数学原理来说,矩阵的乘法,必须是前一个举证的列数,等于后一个矩阵的行数,所以在手机中点的坐标为x,y不能构成矩阵的乘法,所以此时,引入其坐标系,增加一个标志位:(x,y,1)所以这样才可以构成矩阵的乘法。上面的一般形式下的Matrix的各个参数表示的意义:

我们在进行进行上述平移,缩放,旋转,错切的矩阵变换的时候,其实我们一般不需要关注最下面一行的值,默认值分别位 0 ,0,  1就OK。

Matrix的缩放

设原始坐标的齐次坐标为 (x0,y0,1) 缩放的scaleX = k0 ,scaleY = k2, 缩放后的坐标 为(x,y,1)

经过缩放之后得到

                              x = k0 * x0

                              y = k1* y0

在齐次坐标系下面(x,y,1)和 (2x,2y,2)...... (nx,ny,n)表示的是同一个坐标

Matrix的旋转

我们一般在使用matrix,进行旋转变换的时候,一般的都是调用setRotate(degrees) 或者 postRotate(degrees),需要传递一个自己的表示角度(角度或是弧度)的参数进去。

上面公式中假设原始坐标位(x0,y0,1) 旋转之后的坐标为(x,y,1)也即是:               

                                                 x  =  x0·cosθ - y0·sinθ

                                                 y =   y0·sinθ - x0·cosθ

Matrix的平移

  用矩阵表示  

计算得到                                                             

                                                                     x  =  x0 +  △x

                                                                     y  =  y0 +  △y

 

Matrix 的 setXXX方法 postXXX 和 preXXX 的关系

      在高等数学中,举证是满足结合律,不满足交换律的,也就是说 假设有三个举证 A ,B ,C 可以得到

                                    A* B * C  = (A*B)*C  = A* (B*C)

                                    A* B * C  ≠ B*A *C 

所以这里就造就了举证的postXXX 和 preXXX setXXX方法的不同:

假设原始矩阵位M   变换矩阵位 S  变换之后的矩阵为M'   

postXXX方法  表示举证的 左乘  ,我的记忆方法就是原始矩阵在左边

                                                        

preXXX方法,表示矩阵的右乘,我的记忆方法就是原始矩阵在 右边

setXXX方法,当调用set方法的时候,就是覆盖之前的变换,之前的变换会不起作用

注意:post 也就是 原始矩阵在左边  pre也就是原始矩阵在右边 这刚刚与我们平常理解post  和 pre 相反

Matrix.isIdentity方法

      matrix的isIdentity()方法用来判定当前矩阵是否为单位矩阵,单位矩阵,也就是对角线为1,1,1的矩阵,如下

Matrix的旋转

      关于Matrix的旋转,可以有好几个方法,对单位矩阵使用的时候没什么区别,但是如果对其他非单位矩阵的控件使用,preXXX,postXXX,还有setXXX还是由分别的。

    setRotate(float degrees)                               旋转degrees的角度

    setRotate(float degrees, float px, float py)    绕点(px,py)旋转degrees角度

    preRotate(float degrees)                               旋转degrees角度

    preRotate(float degrees, float px, float py)    绕点px,py旋转degrees角度

    postRotate(float degrees)                               旋转degrees角度

    postRotate(float degrees, float px, float py)    绕点px,py旋转degrees角度

关于矩阵 旋转的数学推导方法可以借鉴 这篇文章 ,这里只做一个结论,和效果以便更好的理解关于绕点(px,py)旋转的意义

2DArbitraryRotate

看图 可以知道,绕点(px,py)旋转可以拆分为 三步走,转变成平时我们熟悉的绕“坐标原点”旋转:

1,先将目标图形上的某一点(px,py),也就是我们要围绕旋转的点,平移到 坐标原点(零点),-----平移操作(-px,-py)。

2.将平移后的图像,绕坐标原点 旋转degrees度数。     ---旋转操作

3.将步骤2中得到的图像,再进行平移操作  ---平移操作 (px,py)

所以setRotate(float degrees, float px, float py)  这个操作可以分成下面三个矩阵相乘 再乘以原始矩阵:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("MatrixView","width/2="+mBitmap.getWidth()/2 + "  height/2="+mBitmap.getHeight()/2);
        Log.e("MatrixView","mMatrix pre rotate="+mMatrix.toShortString());
        mMatrix.setRotate(90,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
        Log.e("MatrixView","mMatrix after rotate="+mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

     

 

左图是直接调用canvas.drawBitmap直接绘制出来的,matrix是new出来的单位矩阵。

右图是经过绕bitmap的中心旋转90°得到的。

04-21 21:36:26.875 9700-9700/? E/MatrixView: width/2=187  height/2=125
04-21 21:36:26.875 9700-9700/? E/MatrixView: mMatrix pre rotate=[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
04-21 21:36:26.876 9700-9700/? E/MatrixView: mMatrix after rotate=[0.0, -1.0, 312.0][1.0, 0.0, -62.0][0.0, 0.0, 1.0]

上面的日志是打印了bitmpa的中心点的坐标 (187,125) ,后面是旋转之前的单位矩阵 和 旋转之后的矩阵

上面

             mMatrix.setRotate(90,mBitmap.getWidth()/2,mBitmap.getHeight()/2);

这句代码 等价于,下面这三句代码

  mMatrix.postTranslate(-mBitmap.getWidth()/2,-mBitmap.getHeight()/2);
  mMatrix.postRotate(90);
  mMatrix.postTranslate(mBitmap.getWidth()/2,mBitmap.getHeight()/2);

Matrix的平移

     matrix的平移方法setTranslate(float dx,float dy)和canvas的平移没啥区别,只需要记得向右或者向下平移dx或者dy位正值,向左或者向上平移 dx或者dy为负值     

Matrix的缩放

     matrix中关于缩放的方法一共有2类,但是和之前讲的rotate方法类似:

    setScale(float sx,float sy)                           x 和 y方向分别缩放 sx,sy倍

    setScale(float sx,float sy, float px, float py)    以点(px,py)为中心  x 和 y方向分别缩放 sx,sy倍

    preScale(float sx,float sy)                                  x 和 y方向分别缩放 sx,sy倍,原始矩阵在前

    preScale(float sx,float sy, float px, float py)         以点(px,py)为中心  x 和 y方向分别缩放 sx,sy倍,原始矩阵在前

    postScale(float sx,float sy, float px, float py)                            以点(px,py)为中心  x 和 y方向分别缩放 sx,sy倍,原始矩阵在后

    postScale(float sx,float sy)                         x 和 y方向分别缩放 sx,sy倍,原始矩阵在后

    以(px,py)位中心的缩放,运算原理和上文的平移的矩阵运算原理相同。

Matrix的错切

 Matrix的setSkew方法也有两类方法:

setSkew(float kx, float ky)             设置水平方向和竖直方向上的错切系数
settSkew(float kx, float ky, float px, float py)  设置水平方向和竖直方向上的错切系数,并且
preSkew(float kx, float ky)
preSkew(float kx, float ky, float px, float py)
postSkew(float kx, float ky)
postSkew(float kx, float ky, float px, float py)

例如下面的代码

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("MatrixView","mMatrix pre skew="+mMatrix.toShortString());
        mMatrix.setSkew(0.5f,0);
        Log.e("MatrixView","mMatrix after skew="+mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

04-22 22:14:32.843 3532-3532/com.matrix.demo E/MatrixView: mMatrix pre skew=[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
04-22 22:14:32.843 3532-3532/com.matrix.demo E/MatrixView: mMatrix after skew=[1.0, 0.5, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

水平错切  

  所谓的水平错切 也就是在原来点的基础上,Y坐标不变,X坐标增加或者减少某一个值,例如上图Y坐标不变,对应的X坐标被拉伸

 用矩阵表示就是这样的。

垂直错切,就是水平坐标不变,Y坐标平移一个值:

用矩阵表示为

还有一种就是符合错切,就是水平和垂直方向都错切:

用矩阵表示为:

上面关于错切的方法有一类是后面多2个参数float px,float py,这就是以点P(px,py)位错切中心,进行错切变换,默认是以原点为中心,进行错切变换,看下面代码:

mMatrix.setSkew(kx,ky,px,py);

以bitmap的中心位P(px,py)进行错切变换,上面代码的意思就是,进行错切变换之后,保持原来的p(px,py)不变,也就是在setSkew(0.1f,0)的错切变换之后,要将变换之后的p'(px',py') 平移回到原来的p(px,py)点,所以上面的代码等同于下面两个变换的操作结果:

mMatrix.postTranslate(-kx*py,-ky*px);
mMatrix.postSkew(kx,ky)

用矩阵表示如下:

Matrix的mapPoints方法

public void mapPoints(float[] pts)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] dst, float[] src)
public void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex,int pointCount)

方法大体的意思就是 映射指定的点到指定的集合:

只有一个参数的函数float [] pts,传入一个偶数数组,分别代表点的x,y坐标 经过某种变换(平移,缩放,旋转,错切)之后得到结果还是存放在pts中。

多个参数的mapPoint方法:

  • float dst ,存放经过上述某种或者几种变换之后,将src转换目标点的数组,原始的src不变
  • int dstIndex,目标数据存储的起始下标
  • float src  源坐标
  • int srcIndex ,源数据起始坐标。
  • int pointCount , 数据点的数量....
      // 初始数据为三个点 (0, 0) (80, 100) (400, 300)
        float[] src = new float[]{0, 0, 80, 100, 400, 300};
        float[] dst = new float[6];

        // 构造一个matrix,x坐标缩放0.5
        Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 1f);

        // 输出计算之前数据
        Log.i("MatrixView", "before: src="+ Arrays.toString(src));
        Log.i("MatrixView", "before: dst="+ Arrays.toString(dst));

        // 调用map方法计算(最后一个2表示两个点,即四个数值,并非两个数值)
        matrix.mapPoints(dst, 0, src, 2, 2);

        // 输出计算之后数据
        Log.i("MatrixView", "after : src="+ Arrays.toString(src));
        Log.i("MatrixView", "after : dst="+ Arrays.toString(dst));

04-24 22:11:38.555 2857-2857/com.matrix.demo I/MatrixView: before: src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
04-24 22:11:38.556 2857-2857/com.matrix.demo I/MatrixView: before: dst=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
04-24 22:11:38.557 2857-2857/com.matrix.demo I/MatrixView: after : src=[0.0, 0.0, 80.0, 100.0, 400.0, 300.0]
04-24 22:11:38.557 2857-2857/com.matrix.demo I/MatrixView: after : dst=[40.0, 100.0, 200.0, 300.0, 0.0, 0.0]

Matrix的 invert方法

public boolean invert(Matrix inverse)

反转当前矩阵,如果能反转就返回true并将反转后的值写入inverse,否则返回false。

      Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 1f);
        Log.e("MatrixView","invert before ="+matrix.toShortString());
        Matrix invert = new Matrix();
        matrix.invert(invert);

        Log.e("MatrixView","invert after ="+invert.toShortString());

会得到下面的log:

04-24 22:19:19.506 2959-2959/? E/MatrixView: invert before =[0.5, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]
04-24 22:19:19.506 2959-2959/? E/MatrixView: invert after =[2.0, 0.0, -0.0][0.0, 1.0, -0.0][0.0, 0.0, 1.0]

也就是 当前矩阵M ,逆转之后的矩阵M' 单位矩阵 I,则有

                                                                          M * M' = I

Matrix的mapRect方法

有下面2个重载方法

public boolean mapRect(RectF rect)  //测量rect并将测量结果放入rect中,返回值是判断矩形经过变换后是否仍为矩形。

public boolean mapRect(RectF dst, RectF src)  //测量src并将测量结果放入dst 中,返回值是判断矩形经过变换后是否仍为矩形。


        RectF rect = new RectF(400, 400, 1000, 800);

        Matrix matrix = new Matrix();
        matrix.setScale(0.5f, 1f);
        matrix.postSkew(1,0);

        Log.e("MatrixView","mapRect: "+rect.toString());

        boolean result = matrix.mapRect(rect);

        Log.e("MatrixView", "mapRect: "+rect.toString());
        Log.e("MatrixView", "isRect: "+ result);

04-24 22:26:43.884 3260-3260/com.matrix.demo E/MatrixView: mapRect: RectF(400.0, 400.0, 1000.0, 800.0)
04-24 22:26:43.884 3260-3260/com.matrix.demo E/MatrixView: mapRect: RectF(200.0, 400.0, 500.0, 800.0)
04-24 22:26:43.884 3260-3260/com.matrix.demo E/MatrixView: isRect: false

由于进行了错切变换,所以最后得到的图形并非矩形。

Matrix的setSinCos方法

public void setSinCos(float sinValue, float cosValue)  //sin的值为 sinValue cos的值位cosValue
public void setSinCos(float sinValue, float cosValue, float px, float py)  //中心点位(px,py)

这个方法其实和setRotate方法的作用类似,假如setRotate方法传入的角度为degrees,则setRotate(degrees)与 setSincos(sin(degrees),cos(degrees))能够达到同样的效果,后面float px,float py就是选择的中心。有了setRotate方法之后,这2个方法基本不怎么使用。

Matrix的 setPolyToPoly方法

public boolean setPolyToPoly(float[] src, int srcIndex,
        float[] dst, int dstIndex,
        int pointCount)
  • float [ ] src , 要改变的源数据的点的集合,数组长度为偶数。
  • int srcIndex , 从源数据 的第一个点开始计算
  • float [ ] dst  原始点数据需要变换为 dst的目标数据
  • int dstIndex ,目标数据的起始位置
  • int pointCount ,需要改变的点的数量

setPolyToPoly的作用是通过多点映射的方式来实现平移 ,缩放,旋转或者错切的一种或者多种变换。

pointCount = 0  相当于Matrix.reset操作,将matrix所有变换清空,重置为单位矩阵

  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.setTranslate(100,100);
        mMatrix.setPolyToPoly(new float[]{0, 0, 400, 0, 400, 400, 0, 400}, 0, new float[]{350, 350, 400,50, 450, 450, 50, 400}, 0, 0);

        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

打印的matrix日志如下

04-25 21:29:53.656 2413-2413/? E/MatrixView: [1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]

效果如下:

pointCount =1 实现的是一种平移变换,在调用这个方法的时候pointCount必须<=4

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float src [] = {0,0,50,50,100,100};
        float dst [] ={50,50,110,120,130,160};
        mMatrix.setPolyToPoly(src,0,dst,4,1);
        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

04-25 21:12:23.145 1564-1564/com.matrix.demo E/MatrixView: [1.0, 0.0, 130.0][0.0, 1.0, 160.0][0.0, 0.0, 1.0]

上面的方法其和 mMatrix.setTranslate(130,160)是同等的效果。

pointCount = 2 可实现,缩放,旋转,平移的操作。

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float src [] = {0,0,50,50,100,100};
        float dst [] ={50,50,110,120,130,160};
        mMatrix.setPolyToPoly(src,0,dst,2,2);
        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

初始效果 和 上面得到的效果如下图  

    

                 初始效果图                                                                       pointCount =2 时的上例图

pointCount = 3 可实现,缩放,旋转,平移,错切的操作。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.setPolyToPoly(new float[]{0, 0, 400, 0,400,400}, 0, new float[]{0, 0, 400, 0, 500, 400}, 0, 3);

        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

pointCount = 4的时候,可以成为任意的四边形

 protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMatrix.setPolyToPoly(new float[]{0, 0, 400, 0, 400, 400, 0, 400}, 0, new float[]{350, 350, 400,50, 450, 450, 50, 400}, 0, 4);

        Log.e("MatrixView",mMatrix.toShortString());
        canvas.drawBitmap(mBitmap,mMatrix,mPaint);
    }

效果如下:

 

Matrix的setRectToRect方法

public boolean setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf)

  • src 坐标变换前的矩形
  • dst 坐标变换后的矩形
  • stf 矩形缩放选项

由于提供坐标变换前后的参数可为任意矩形,这样的话,变换前后矩形的长宽比不一定一样,提供指定Matrix.ScaleToFit选项来确定缩放选项。Matrix.ScaleToFit定义了四种选项:

  • CENTER: 保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。
  • END:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。END提供右下对齐。
  • FILL: 可能会变换矩形的长宽比,保证变换和目标矩阵长宽一致。
  • START:保持坐标变换前矩形的长宽比,并最大限度的填充变换后的矩形。至少有一边和目标矩形重叠。START提供左上对齐。

google的API demo里面的效果图如下

 

到此为止,Martix中的大部分API都讲解记录的差不多了,这些api在自定义View中,都很有用。如有哪里写的有问题,欢迎留言。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值