在自定义View的时候我们在绘制bitmap时候
canvas.drawBitmap(bitmap,Matrix,Paint)
用到了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倍
也就是上图显示的两个位置对于缩放的影响
基础说明到这里结束,后面说明我遇到的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的东西,大家可以看看