从原理了解 Android Matrix

1,685

0、前言

一开始接触Matrix是因为需要对图片做一些处理,然而笔者是一个刚毕业半年的小青年,最初连Matrix这个类都不知道,后来通过各种查找资料才知道Matrix是用来做一些平移、旋转、缩放操作的,而且不仅仅只能作用于图片上,同样也可以作用于Rect、Point等。然后网上的资料有对有错,也不是很详细,导致笔者在这过程中踩了不少坑。本文旨在帮助和笔者一样还不是很了解Matrix,甚至还没用过的读者,从原理上了解Matrix,也为自己做一个总结。

1、Matrix是什么?

Matrix翻译成中文叫做矩阵,样子就长成下面这样

[ 1 4 2 5 3 6 ]  

矩阵在数学上代表的是一组线性方程,学过线性代数的同学肯定就很了解了。啥?没学过怎么办?别担心,本文只用到了矩阵的乘法,并不会涉及很深的线代知识。

2、矩阵的乘法

矩阵乘法公式:
矩阵乘法公式

单看乘法公式可能不是很直观,再来看一下两个2*2的矩阵相乘是怎么运算的

[ x 11 x 21 x 12 x 22 ] [ y 11 y 21 y 12 y 22 ] =[ x 11 y 11 +x 12 y 21 x 21 y 11 +x 22 y 21 x 11 y 12 +x 12 y 22 x 21 y 12 +x 22 y 22 ]  

来个简单的例题练习一下

[ 0 1 1 2 ] [ 1 2 2 4 ] =[ 2 5 4 10 ]  

怎么样,是不是很简单呢。好了,准备工作就做到这里,接下来就进去正题了。

3、Android中的Matrix

首先来看一下Android Developer上对于Matrix类的介绍

The Matrix class holds a 3x3 matrix for transforming coordinates.

可以看出Matrix就是用于坐标变换的。而且是一个3x3的矩阵。就长酱紫

M SC ALE _X M SK EW _Y M PE RS P_ 0 M SK EW _X M SC ALE _Y M PE RS P_ 1 M TRA NS _X M TRA NS _Y M PE RS P _2 (Android Matrix)

Matrix可以操作的基础变换可分为四种:
Scale:缩放
Skew:错切
Rotate:旋转
Translate:平移

顺带一提,最后一行的三个参数分别为0,0,1。是固定值,那为什么要这样做,明明六个参数的可以做的事情为什么要变成9个?其实这个在线代里面叫做齐次方程,简而言之就是为了方便计算。

4、Matrix中的setXXX、preXXX、postXXX

这三者有何区别?setXXX是最简单的,调用setXXX会直接将Matrix重置并赋值。而pre和post就没那么简单了,如果单纯只使用一种变换,那么pre和post是没有区别的(为什么没有区别呢?此处先留一个坑)。但是如果组合使用,那么区别就大了。笔者当时就是同时使用了scale和translate,踩坑之旅就此开始…

一开始笔者在网上查找资料,发现不少博文说pre是先执行,post是后执行。比如说:我先调用postTransla,然后再调用preScale,则系统会先执行scale再执行post。然而坑就是这么踩进去的,首先博文并没有说清楚,先执行和后执行有什么差异,再者,这里的逻辑存在问题,我们都知道代码都是一行一行去执行的,不可能先执行下一行再返回去执行上一行。

这里首先要说明的是,pre根本不是什么先执行,post也根本不是后执行,pre和post分别代表着左乘(前乘?)和右乘(后乘?),不过有点搞不懂谷歌这边为什么要用pre和post。本文为了方便理解,就直译为前乘和后乘,左为前,右为后。

以translate为例,谷歌对于postTranslate的说明是这样的

postTranslate
boolean postTranslate (float dx, float dy)
Postconcats the matrix with the specified translation.

M’ = T(dx, dy) * M

后乘将自身置于后面,前面矩阵乘以自身

preTranslate同理

M’ = M * T(dx, dy)

前乘将自身置于前,再乘以后面矩阵

为什么会有前乘后乘,因为矩阵不符合乘法交换律(特殊情况除外,如:两个矩阵一模一样),矩阵交换相乘会得出不一样的结果,建议同学们自己验证一下。

这里再提一下,笔者原本以为前乘是前边乘以一个矩阵,然而这与实际完全相反,后来看了api才知道错了,所以提倡大家遇到问题一定要去官网找资料,那肯定是最权威的,况且谷歌中国不是已经回归了吗,不必再辛苦翻墙,此时不看更待何时。

5、组合使用变换时,preXXX和postXXX产生的不同结果

本文仅以Scale和Translate进行讲解,其他情况童鞋们可以自己证明

  • preScale(ΔX , ΔY)

S cale_X 0 0 0 S cale_Y 0 T ransla te_X T ranslate _Y 1 Δ X 0 0 0 Δ Y 0 0 0 1

= S cale_X Δ X 0 0 0 S cale_Y Δ Y 0 T ranslate _X T ran sla te_ Y 1  

结论:preScale(float x, float y)并不会影响原先的偏移量。

  • postScale(ΔX , ΔY)

Δ X 0 0 0 Δ Y 0 0 0 1 S cale_X 0 0 0 S cale_Y 0 T ranslate _X T ran sla te_ Y 1

= Δ XS cale_X 0 0 0 Δ YS cale_Y 0 Δ XT ranslate _X Δ YT ranslate _Y 1  

结论:postScale(float x, float y)会将原先的偏移量一起缩放。

本文前面挖了一个坑,当只有一种类型变换时,pre和post的效果是一致的。当自由scale时,translate等于0,所以两者得出的结果一致。

  • preTranslate(ΔX , ΔY)

S cale_X 0 0 0 S cale_Y 0 T ransla te_X T ranslate _Y 1 1 0 0 0 1 0 Δ X Δ Y 1

= S cale_X 0 0 0 S cale_Y 0 S cale_X Δ X+T ranslate _X S cal e_Y Δ Y +T ran sla te_ Y 1  

结论:preTranslate(float x, float y)按照之前的缩放比例缩放偏移量。

  • postTranslate(ΔX , ΔY)

1 0 0 0 1 0 Δ X Δ Y 1 S cale_X 0 0 0 S cale_Y 0 T ranslate _X T ran sla te_ Y 1

= S cale_X 0 0 0 S cale_Y 0 T ranslate _X+Δ X T ranslate _Y +Δ Y 1  

结论:postTranslate(float x, float y)会偏移指定值,与缩放参数无关。

同理,当使用Translate变换时,scale等于1,preTranslate和postTranslate的结果是一致的。

关于其他的变换就不再证明了,既然知道了原理,同学们就自己动手试着做看看。

6、后话

这段话写给看到最后的童鞋,同时也是写给自己。虽然Google已经封装好了Api供开发者使用,但是必要时还是得去了解原理,否则模凌两可所走的弯路,可能会比你脚踏实地来得更加曲折。这不仅仅是作为开发者应该具备的素质,更是人生中的一笔巨大财富。