安卓自定义View进阶-Matrix原理

Matrix原理

作者微博: @GcsSloop

【本系列相关文章】

前言

本文内容偏向理论,和 画布操作 有重叠的部分,本文会让你更加深入的了解其中的原理。

本篇的主角Matrix,是一个一直在后台默默工作的劳动模范,虽然我们所有看到View背后都有着Matrix的功劳,但我们却很少见到它,本篇我们就看看它是何方神圣吧。

由于Google已经对这一部分已经做了很好的封装,所以跳过本部分对实际开发影响并不会太大,不想深究的粗略浏览即可,下一篇中将会详细讲解Matrix的具体用法和技巧。

Matrix简介

Matrix是一个矩阵,主要功能是坐标映射,数值转换。

它看起来大概是下面这样:

Matrix作用就是坐标映射,那么为什么需要Matrix呢? 举一个简单的例子:

我的的手机屏幕作为物理设备,其物理坐标系是从左上角开始的,但我们在开发的时候通常不会使用这一坐标系,而是使用内容区的坐标系。

以下图为例,我们的内容区和屏幕坐标系还相差一个通知栏加一个标题栏的距离,所以两者是不重合的,我们在内容区的坐标系中的内容最终绘制的时候肯定要转换为实际的物理坐标系来绘制,Matrix在此处的作用就是转换这些数值。

>

假设通知栏高度为20像素,导航栏高度为40像素,那么我们在内容区的(0,0)位置绘制一个点,最终就要转化为在实际坐标系中的(0,60)位置绘制一个点。

以上是仅作为一个简单的示例,实际上不论2D还是3D,我们要将图形显示在屏幕上,都离不开Matrix,所以说Matrix是一个在背后辛勤工作的劳模。

Matrix特点

  • 作用范围更广,Matrix在View,图片,动画效果等各个方面均有运用,相比与之前讲解等画布操作应用范围更广。
  • 更加灵活,画布操作是对Matrix的封装,Matrix作为更接近底层的东西,必然要比画布操作更加灵活。
  • 封装很好,Matrix本身对各个方法就做了很好的封装,让开发者可以很方便的操作Matrix。
  • 难以深入理解,很难理解中各个数值的意义,以及操作规律,如果不了解矩阵,也很难理解前乘,后乘。

常见误解

1.认为Matrix最下面的一行的三个参数(MPERSP_0、MPERSP_1、MPERSP_2)没有什么太大的作用,在这里只是为了凑数。

实际上最后一行参数在3D变换中有着至关重要的作用,这一点会在后面中Camera一文中详细介绍。

2.最后一个参数MPERSP_2被解释为scale

的确,更改MPERSP_2的值能够达到类似缩放的效果,但这是因为齐次坐标的缘故,并非这个参数的实际功能。

Matrix基本原理

Matrix 是一个矩阵,最根本的作用就是坐标转换,下面我们就看看几种常见变换的原理:

我们所用到的变换均属于仿射变换,仿射变换是 线性变换(缩放,旋转,错切) 和 平移变换(平移) 的复合,由于这些概念对于我们作用并不大,此处不过多介绍,有兴趣可自行了解。

基本变换有4种: 平移(translate)、缩放(scale)、旋转(rotate) 和 错切(skew)。

下面我们看一下四种变换都是由哪些参数控制的。

从上图可以看到最后三个参数是控制透视的,这三个参数主要在3D效果中运用,通常为(0, 0, 1),不在本篇讨论范围内,暂不过多叙述,会在之后对文章中详述其作用。

由于我们以下大部分的计算都是基于矩阵乘法规则,如果你已经把线性代数还给了老师,请参考一下这里:

维基百科-矩阵乘法

1.缩放(Scale)

用矩阵表示:

你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:

(x, y, 1) - 点

(x, y, 0) - 向量

另外,齐次坐标具有等比的性质,(2,3,1)、(4,6,2)…(2N,3N,N)表示的均是(2,3)这一个点。(将MPERSP_2解释为scale这一误解就源于此)。

图例:

2.错切(Skew)

错切存在两种特殊错切,水平错切(平行X轴)和垂直错切(平行Y轴)。

水平错切

用矩阵表示:

图例:

垂直错切

用矩阵表示:

图例:

复合错切

水平错切和垂直错切的复合。

用矩阵表示:

图例:

3.旋转(Rotate)

假定一个点 A(x0, y0) ,距离原点距离为 r, 与水平轴夹角为 α 度, 绕原点旋转 θ 度, 旋转后为点 B(x, y) 如下:

用矩阵表示:

图例:

4.平移(Translate)

此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。

用矩阵表示:

图例:

Matrix复合原理

其实Matrix的多种复合操作都是使用矩阵乘法实现的,从原理上理解很简单,但是,使用矩阵乘法也有其弱点,后面的操作可能会影响到前面到操作,所以在构造Matrix时顺序很重要。

我们常用的四大变换操作,每一种操作在Matrix均有三类,前乘(pre),后乘(post)和设置(set),可以参见文末对Matrix方法表,由于矩阵乘法不满足交换律,所以前乘(pre),后乘(post)和设置(set)的区别还是很大的。

前乘(pre)

前乘相当于矩阵的右乘:

这表示一个矩阵与一个特殊矩阵前乘后构造出结果矩阵。

后乘(post)

前乘相当于矩阵的左乘:

这表示一个矩阵与一个特殊矩阵后乘后构造出结果矩阵。

设置(set)

设置使用的不是矩阵乘法,而是直接覆盖掉原来的数值,所以,使用设置可能会导致之前的操作失效

组合

我们使用Matrix最终目的就是让视图显示为我们想要的状态,为此我们可能需要多种操作结合使用。

我发现很多讲解Matrix的文章喜欢用绕某一个点缩放(旋转)的示例来讲解,如下:

那么我们如果想让它基于图片中心缩放,应该该怎么办?要用到组合变换,
  1)先将图片由中心平移到原点,这是应用变换 T
  2)对图应用缩放变换 S
  3)再将图片平移回到中心,应用变换 -T

对应代码:
  matrix.postScale(0.5f, 0.5f);
  matrix.preTranslate(-pivotX, -pivotY);
  matrix.postTranslate(pivotX, pivotY);  

PS: 此段文字引用自其它文章。

首先,这个思路是没有任何问题的,也是实现绕某一点操作的核心原理,但这可能会对一部分小白造成误解,认为只能这样实现,然而查看一下Matrix的方法表就能知道四大操作都可以指定中心点,所以,上面的三行代码用一行就能完成:

matrix.postScale(0.5f, 0.5f, pivotX, pivotY);

组合操作构造Matrix时,个人建议尽量全部使用后乘或者全部使用前乘,这样操作顺序容易确定,出现问题也比较容易排查。
当然,由于矩阵乘法不满足交换律,前乘和后乘的结果是不同的,使用时应结合具体情景分析使用。

Pre与Post的区别

主要区别其实就是矩阵的乘法顺序不同,pre相当于矩阵的右乘,而post相当于矩阵的左乘。

以下观点存在歧义,故做删除标注:

在图像处理中,越靠近右边的矩阵越先执行,所以pre操作会先执行,而post操作会后执行。

在实际操作中,我们每一步操作都会得出准确的计算结果,但是为什么还会用存在先后的说法? 难道真的能够用pre和post影响计算顺序? 实则不然,下面我们用一个例子说明:

Matrix matrix = new Matrix();
matrix.postScale(0.5f, 0.8f);
matrix.preTranslate(1000, 1000);
Log.e(TAG, "MatrixTest:3" + matrix.toShortString());

在上面的操作中,如果按照正常的思路,先缩放,后平移,缩放操作执行在前,不会影响到后续的平移操作,但是执行结果却发现平移距离变成了(500, 800)。

在上面例子中,计算顺序是没有问题的,先计算的缩放,然后计算的平移,而缩放影响到平移则是因为前一步缩放后的结果矩阵右乘了平移矩阵,这是符合矩阵乘法的运算规律的,也就是说缩放操作虽然在前却影响到了平移操作,相当于先执行了平移操作,然后执行的缩放操作,因此才有pre操作会先执行,而post操作会后执行这一说法

下面我们用不同对方式来构造一个矩阵:

假设我们需要先缩放再平移。

注意:

  • 1.由于矩阵乘法不满足交换律,请保证使用初始矩阵(Initial Matrix),否则可能导致运算结果不同。
  • 2.注意构造顺序,顺序是会影响结果的。
  • 3.Initial Matrix是指new出来的新矩阵,或者reset后的矩阵,是一个单位矩阵。

1.仅用pre:

Matrix m = new Matrix();
m.reset();
m.preTranslate(tx, ty); //使用pre,越靠后越先执行。
m.preScale(sx, sy);

用矩阵表示:

2.仅用post:

Matrix m = new Matrix();
m.reset();
m.postScale(sx, sy);  //使用post,越靠前越先执行。
m.postTranslate(tx, ty);

用矩阵表示:

3.混合:

Matrix m = new Matrix();
m.reset();
m.preScale(sx, sy);
m.postTranslate(tx, ty);

或:

Matrix m = new Matrix();
m.reset();
m.postTranslate(tx, ty);
m.preScale(sx, sy);  

由于此处只有两步操作,且指定了先后,所以代码上交换并不会影响结果。

用矩阵表示:

注意: 由于矩阵乘法不满足交换律,请保证初始矩阵为空,如果初始矩阵不为空,则导致运算结果不同。

Matrix方法表

这个方法表,暂时放到这里让大家看看,方法的使用讲解放在下一篇文章中。

方法类别 相关API 摘要
基本方法 equals hashCode toString toShortString 比较、 获取哈希值、 转换为字符串
数值操作 set reset setValues getValues 设置、 重置、 设置数值、 获取数值
数值计算 mapPoints mapRadius mapRect mapVectors 计算变换后的数值
设置(set) setConcat setRotate setScale setSkew setTranslate 设置变换
前乘(pre) preConcat preRotate preScale preSkew preTranslate 前乘变换
后乘(post) postConcat postRotate postScale postSkew postTranslate 后乘变换
特殊方法 setPolyToPoly setRectToRect rectStaysRect setSinCos 一些特殊操作
矩阵相关 invert isAffine isIdentity 求逆矩阵、 是否为仿射矩阵、 是否为单位矩阵 …

总结

对于Matrix重在理解,理解了其中的原理之后用起来将会更加得心应手。

学完了本篇之后,推荐配合鸿洋大大的视频课程 打造个性的图片预览与多点触控 食用,定然能够让你对Matrix对理解更上一层楼。

About Me

作者微博: @GcsSloop

参考资料

Matrix

Android中图像变换Matrix的原理、代码验证和应用

Android中关于矩阵(Matrix)前乘后乘的一些认识

维基百科-仿射变换

维基百科-齐次坐标

维基百科-线性映射

齐次坐标系入门级思考

仿射变换与齐次坐标

时间: 2024-12-16 07:44:55

安卓自定义View进阶-Matrix原理的相关文章

安卓自定义View进阶-Matrix详解

这应该是目前最详细的一篇讲解Matrix的中文文章了,在上一篇文章Matrix原理中,我们对Matrix做了一个简单的了解,偏向理论,在本文中则会详细的讲解Matrix的具体用法,以及与Matrix相关的一些实用技巧. Matrix方法表 按照惯例,先放方法表做概览. 方法类别 相关API 摘要 基本方法 equals hashCode toString toShortString 比较. 获取哈希值. 转换为字符串 数值操作 set reset setValues getValues 设置.

安卓自定义View进阶-Path之玩出花样(PathMeasure)

Path之玩出花样 作者微博: @GcsSloop [本系列相关文章] 可以看到,在经过 Path之基本操作 Path之贝塞尔曲线 和 Path之完结篇(伪) 后, Path中各类方法基本上都讲完了,表格中还没有讲解到到方法就是矩阵变换了,难道本篇终于要讲矩阵了? 非也,矩阵这一部分仍在后面单独讲解,本篇主要讲解 PathMeasure 这个类与 Path 的一些使用技巧. PS:不要问我为什么不讲 PathEffect,因为这个方法在后面的Paint系列中. 先放一个图镇楼,省的下面无聊的内容

安卓自定义 View 进阶:贝塞尔曲线

在上一篇文章Path之基本图形中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线. 一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此刻内心也是崩溃的. 作用相关方法备注 移动起点moveTo移动下一次操作的起点位置 设置终点setLastPoint重置当前path中最后一个点位置,如果在绘制之前调用,效果和moveT

安卓自定义View进阶 - Path之完结篇(伪)

Path之完结篇(伪) 作者微博: @GcsSloop [本系列相关文章] 经历过前两篇 Path之基本操作 和 Path之贝塞尔曲线 的讲解,本篇终于进入Path的收尾篇,本篇结束后Path的大部分相关方法都已经讲解完了,但Path还有一些更有意思的玩法,应该会在后续的文章中出现吧,嗯,应该会的ˊ_>ˋ 一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此

安卓自定义View文章数据滚动显示数值

本文已经在微信公众号[Android群英传]独家发表. 本文Github代码链接 https://github.com/AndroidMsky/RandomTextView 先看看X金APP的效果: 我们自己实现的效果: 接下来介绍一下我的自定义View RandomTextView的用法和原理 用法 考入 RandomTextView.java 只有200行绝对轻量方便. xml中定义: <com.example.liangmutian.randomtextview.view.RandomTe

安卓自定义View实现图片上传进度显示(仿QQ)

首先看下我们想要实现的效果如下图(qq聊天中发送图片时的效果): 再看下图我们实现的效果: 实现原理很简单,首先我们上传图片时需要一个进度值progress,这个不管是自己写的上传的方法还是使用第三方开源库,其次,需要自定义一个View并重写onDraw方法,此例中的进度是开启了一个线程,然后模仿进度递增,然后将进度值通过自定义View调用一个自定义方法传进自定义View并根据进度进行重绘. 绘制分为三部分: 1.绘制矩形(图片面积)上半部分阴影区: 2.绘制矩形(图片面积)下半部分非阴影区:

安卓自定义View,实时绘图保存为图片

采用单缓冲与双缓冲机制分别实现. 一.单缓冲机制 1.1自定义View,重写ondraw函数 public class Brush extends View { private Paint brush = new Paint(); private Path path = new Path(); int j = 0, n = 0; // xml创建view时调用的构造函数 public Brush(Context context,AttributeSet attrs) { super(contex

安卓自定义View实现钟表

转载请注明出处:http://blog.csdn.net/baiyuliang2013/article/details/45535227 之前实现过html5版的钟表,html5也有一个画板属性Canvas,相对于安卓的Canvas来说html5的功能要强大的多,就拿钟表的实现,html5要方便简单的多,而安卓实现起来则非常复杂,像指针转动,html5可以画一条线,然后可以用这条线旋转一个弧度即可,而安卓必须是已知起点坐标和终点坐标,这就需要精确计算起始坐标了.先看下效果图,因为是图静态的,动态

Android自定义View进阶 - 贝塞尔曲线

Path之贝塞尔曲线 作者微博: @GcsSloop [本系列相关文章] 在上一篇文章Path之基本图形中我们了解了Path的基本使用方法,本次了解Path中非常非常非常重要的内容-贝塞尔曲线. 一.Path常用方法表 为了兼容性(偷懒) 本表格中去除了在API21(即安卓版本5.0)以上才添加的方法.忍不住吐槽一下,为啥看起来有些顺手就能写的重载方法要等到API21才添加上啊.宝宝此刻内心也是崩溃的. 作用 相关方法 备注 移动起点 moveTo 移动下一次操作的起点位置 设置终点 setLa