对于Matrix中PreXXX和PostXXX的使用时机的理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/One_Month/article/details/89478898

在自定义View的时候我们在绘制bitmap时候
canvas.drawBitmap(bitmap,Matrix,Paint)
用到了Matrix,也就是矩阵,网上有图,说明了这个矩阵影响的方式

1.Matrix矩阵各数值影响

矩阵行列式
第一行影响X轴,分别是scale缩放,skew错切,trans平移
第二行影响Y轴,与第一行相同,作用于图形就是矩阵相乘
具体的矩阵行为控制,图片来自网上
这是影响的旋转和位移,还有一张是说明错切和缩放
缩放和错切
举一个scale,缩放的例子
缩放计算方式
等号右边就是matrix和原数据的某个点坐标,矩阵相乘,行乘以列
x = K1 * X0 + 0 * Y0 + 0 * 1 = K1 * X0
可以看出X坐标被缩放了k1倍
y = 0 * X0 + k2 * Y0 + 0 * 1 = K2 * Y0
可以看出Y坐标倍缩放了K2倍

也就是上图显示的两个位置对于缩放的影响

2.说一下Pre和Post的基本区别

基础说明到这里结束,后面说明我遇到的preXXX方法和postXXX方法的困惑
比如
平移
1.preTranslate(),源码说明

   /**
     * Preconcats the matrix with the specified translation. M' = M * T(dx, dy)
     */
    public boolean preTranslate(float dx, float dy) {
        nPreTranslate(native_instance, dx, dy);
        return true;
    }
    
M' = M * T(dx, dy)
也就是原矩阵在左边,要实现平移转换的的操作在右边,也可以说是原矩阵的左乘

T(dx, dy)就是一个函数表示,表示构造一个平移矩阵
M为原矩阵,当新建一个Matrix,就是一个单位矩阵,下图就是个单位矩阵,
斜边为1,其余为0

单位矩阵

2.如果是postTranslate,源码说明

 /**
     * Postconcats the matrix with the specified translation. M' = T(dx, dy) * M
     */
    public boolean postTranslate(float dx, float dy) {
        nPostTranslate(native_instance, dx, dy);
        return true;

M' = T(dx, dy) * M
也就是原矩阵的右乘

矩阵乘法的特性是:
矩阵乘法不满足交换律,即 AB ≠ BA
矩阵乘法满足结合律,即 (AB)C = A(BC)
矩阵与单位矩阵相乘结果不变,即 A * I = A

对于左乘和右乘有什么影响,特别是多个操作结合,比如preTranslate,preScale
等等。计算起来很复杂

网上有一种说法,说的是

preXXX就是先执行,postXXX就是后执行,但是其实我们知道,代码的执行顺序
是根据你写的代码先后,preXXX和postXXX在我们调用的时候就已经执行,得到
了新的Matrix,比如执行preTranslate,preScale
M’ = M * T * S T表示preTranslate,S表示preScale
调用了M*T就已经产生新的Matrix内部结构,然后再和S操作

所以我们在用的时候我想可以这么理解

用法结论

就是在执行逻辑上,比如,我们想要先位移,再缩放,再旋转,我们想要的是执行这个结果
就可以preRotate,preScale,preTranslate,按这个顺序调用,内部公式就是
M’ = M * R * S * T
我们可以逻辑上认为越靠右边的行为越先执行,但是代码执行顺序不是如此,只是我们为了
达到目标效果,逻辑上这么想,内部经历了复杂计算对应了我们的逻辑执行顺序,
得到我们想要的Matrix,这个Matrix实现了这个逻辑顺序下的计算,但是具体内部是怎么计算
出来的,我也无法得知,也期待有喜欢研究的道友讲解

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

		//保存画布状态
        canvas!!.save()
        //重置Matrix,避免后续影响
        mMatrix.reset()
        //画布平移,將画布中心移动到屏幕中心
        canvas.translate(width/2f,height/2f)
        //先画出原图做对比
        canvas.drawBitmap(bitmap,0f,0f,null)

		//先画一个只旋转和平移的图,为了和下面的操作对比,看scale和translate谁先执行
		(逻辑上)
        mMatrix.preRotate(90f)
        mMatrix.preTranslate(100f,100f)
        canvas.drawBitmap(bitmap,mMatrix,null)
        mMatrix.reset()
        //实现我们之前说的平移,缩放,旋转的操作,按这个顺序
        mMatrix.preRotate(progress)
        mMatrix.preScale(0.5f,0.5f)
        mMatrix.preTranslate(100f,100f)

        canvas.drawBitmap(bitmap,mMatrix,null)
        canvas.restore()
    }

效果图如下
效果图

最右边的是原图,可以看到在屏幕中心点开始绘制
最左边的是没有缩放的
最小的是3个操作都有的

我平移了(100,100),也就是向右100,向下100(屏幕坐标系是向下为Y轴正向)

假设是先做的缩放再做的平移,那么缩放时候是在原点缩放,也就是屏幕中心,那么他的左上角还应该是(0,0),然后平移了(100,100),左上角旋转后现在最终视觉上成了右上角,
那么右上角坐标现在应该是(-100,100)

但是我们看到只做了旋转和平移的那张图,也就是最左边的,他明显更加远离原点,说明他的位置才是(-100,100)
所以就说明最小的那张图先做了平移操作,坐标变成(100,100),旋转后变成(-100,100),最后坐了缩放,缩放是根据原点缩放,所以那个点缩放了一般变成了(-50,50),所以就是我们说的顺序

再做个实验,如果我们最后一步是postTranslate,能不能达到我们逻辑上的最后做平移呢

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
//        canvas!!.drawBitmap(bitmap,Rect(0,0,(bitmap.width*progress).toInt() ,bitmap.height),
//                RectF(0f,0f,bitmap.width*progress,bitmap.height.toFloat()), Paint())

//        canvas!!.drawBitmap(bitmap,0f,0f,null)
//        canvas.translate(0f,bitmap.height.toFloat()+200f)
        canvas!!.save()
        mMatrix.reset()
        canvas.translate(width/2f,height/2f)
        canvas.drawBitmap(bitmap,0f,0f,null)

        mMatrix.preRotate(progress)
        mMatrix.preTranslate(100f,100f)
        canvas.drawBitmap(bitmap,mMatrix,null)
        mMatrix.reset()
        mMatrix.preRotate(progress)
        mMatrix.preScale(0.5f,0.5f)
        //改成postTranslate,之前是preTranslate
        mMatrix.postTranslate(100f,100f)

        canvas.drawBitmap(bitmap,mMatrix,null)
        canvas.restore()
    }
    这样公式就变成
    M' = T * M * R * S

看下效果图
效果图
可以看到移动后的左上角和原点距离
然后看左边图的右上角到原点距离,两者距离一样,说明图片先经历
了缩放,原点坐标不变,然后再向右下移动了100,100,也证明了上面
的做法是可行的。

重申一遍

代码的执行顺序上并不是按照pre和post而先后执行,只是我们如果想做到
我们逻辑上让他实现一种效果,可以按照上面说的方式去思考,来合理使用
pre和post,每次可以将公式写出来,然后从右向左看公式,看下最后的效果
是不是和猜想的一致。

如果有反例,欢迎大家给我留言,让我有机会改正,谢谢

参考文章:
自定义View进阶-Matrix原理

这个参考文章也是一个系列博客,能学到不少自定义View的东西,大家可以看看

猜你喜欢

转载自blog.csdn.net/One_Month/article/details/89478898