自定义View之Matrix最全API解析

来源:互联网 发布:js提取url参数 编辑:程序博客网 时间:2024/06/07 07:21
Matrix是Android SDK提供的一个 3 * 3的矩阵类,用来转换坐标。那么,它有9个值:名称|常量值– | –MSCALE_X | 0MSKEW_X | 1MTRANS_X | 2MSKEW_Y | 3MSCALE_Y | 4MTRANS_Y | 5MPERSP_0 | 6MPERSP_1 | 7MPERSP_2 | 8用矩阵格式表示就是这样:![](http://img.blog.csdn.net/20160720134631097)在Android中,我们常用的图形所涉及到的几何变换,主要包括Translate(平移)、Scale(缩放)、Rotate(旋转)。其中,Scale(缩放)和Rotate(旋转)是线性变换,而,线性变换可以用矩阵来表示。值得一提的是,我们所用的坐标系为笛卡尔坐标系,在其平面直角坐标系中,使用(x,y)来表示一个点。

笛卡尔坐标系就是直角坐标系和斜角坐标系的统称。 相交于原点的两条数轴,构成了平面放射坐标系。如两条数轴上的度量单位相等,则称
此放射坐标系为笛卡尔坐标系。两条数轴互相垂直的笛卡尔坐标系,称为笛卡尔直角坐标系,否则称为笛卡尔斜角坐标系。需要指出的是,请> 将数学中的笛卡尔坐标系与电影《异次元杀阵》中的笛卡尔坐标相区分,电影中的定义与数学中定义有出入,请勿混淆。

二维的直角坐标系是由两条相互垂直、0 点重合的数轴构成的。在平面内,任何一点的坐标是根据数轴上对应的点的坐标设定的。在平面内,> 任何一点与坐标的对应关系,类似于数轴上点与坐标的对应关系。采用直角坐标,几何形状可以用代数公式明确的表达出来。几何形状的每一> 个点的直角坐标必须遵守这代数公式。

在笛卡尔坐标系中,线性变换的矩阵的表现形式可以这样:



但是,在笛卡尔坐标系中,平移变换却不能用两个矩阵的乘法表示。平移变换T的表现形式为

尽管,平移变换的矩阵表现形式和线性变换不一样,但是, 2 X 2 的矩阵是足以表示它们,为什么 Matrix 是个 3 X 3 的矩阵?

在图形学中,所涉及的变换包括平移、旋转、缩放。当以矩阵表达式来计算时,平移是矩阵相加,旋转和缩放则是矩阵相乘。如果将平移、旋转和缩放综合起来,可以这么表示:

p’= m1*p + m2

其中:

  • m1:旋转或缩放矩阵
  • m2:平移矩阵
  • p :原向量
  • p’:变换后的向量

在图形学中,将两种简单变换的叠加:一个是线性变换,一个是平移变换,统称为“仿射变换” 。为了解决平移变换不能使用乘法的问题,引入了齐次坐标

p’= m1*p + m2

“齐次坐标表示是计算机图形学的重要手段之一,它既能够用来明确区分向量和点,同时也更易用于进行仿射(线性)几何变换。”——F.S. Hill, JR。

根据规则,定义坐标(1,2)可以使用(1,2,1),也可以使用(2,4,2),还可以使用(4,8,4),(8,16,8)…,即 (k,2k,k),k∈R(k,2k,k),k∈R 都是“合法”的齐次坐标表示,这些点都映射到欧式空间中的一点,即这些点具有 尺度不变性(Scale Invariant),是“齐性的”(同族的),所以称之为齐次坐标。

在齐次坐标系中,线性变换是如何表示的呢?比如,旋转和缩放:


这时,平移变换也可以使用矩阵乘法表示,也就是这样:

对于一个仿射变换T,可以表示成一个线性变换A后平移t:T(p)=Ap+t,其中p是待变换的点齐次坐标表示。T可以表示成如下的形式:

其中:

这里写图片描述

Matrix中9个值与仿射变换T的对应关系是这样的:

这里写图片描述

每个值的作用为:

  • MTRANS_X、MTRANS_Y:作用于平移变换(Translate)
  • MSCALE_X、MSCALE_Y:作用于缩放变换(Scale)
  • MSCALE_X、MSKEW_X、MSCALE_Y、MSKEW_Y:作用于旋转变换(Rotate)
  • MSKEW_X、MSKEW_Y:作用于错切变换(Skew)


平移变换

假定有一个点的坐标是这里写图片描述,将其移动到这里写图片描述,再假定在x轴和y轴方向移动的大小分别为 dx和dy。此时

这里写图片描述

那么:

这里写图片描述

用矩阵表示就是:

这里写图片描述

旋转变换

围绕原点旋转变换

这里写图片描述

假定有一个点的坐标是这里写图片描述,其与x轴的夹角为α,假设p点与原点的距离为r,当p绕着原点顺时针旋转θ,达到这里写图片描述

此时:

这里写图片描述

如果用矩阵表示:

这里写图片描述

围绕某个点旋转

这里写图片描述

假定有一个点这里写图片描述,绕着点,沿着顺时针方向旋转θ,达到这里写图片描述

前面已经了解到平移变换和围绕原点旋转变换,那么,我们可以这么做了,将坐标原点移到,将它作为原点。此时:

这里写图片描述

那么:

这里写图片描述

对于这里写图片描述的计算是这样的:

  1. 将坐标原点移到

    这里写图片描述

  2. 这里写图片描述绕着新坐标原点,沿着顺时针方向旋转θ:

    这里写图片描述

  3. 将坐标原点移回到原先的坐标原点:

    这里写图片描述

缩放

简单缩放

简单缩放可以直接通过将缩放系数sx,sy与对应x,y坐标相乘:

这里写图片描述

用矩阵表示就是:

这里写图片描述

基于某个点缩放

这里写图片描述

当然,我们需要在一个固定点进行缩放,那么就需要我们选择一个在缩放变换后不改变位置的点,来控制缩放后对象的位置。

得到的公式则是:

这里写图片描述

用矩形表示就是:
这里写图片描述

错切变换

错切变换(skew)在数学上又称为Shear mapping(可译为“剪切变换”)或者Transvection(缩并),它是一种比较特殊的线性变换。错切变换的效果就是让所有点的x坐标(或者y坐标)保持不变,而对应的y坐标(或者x坐标)则按比例发生平移,且平移的大小和该点到x轴(或y轴)的垂直距离成正比。错切变换,属于等面积变换,即一个形状在错切变换的前后,其面积是相等的。

在平面上,水平错切(或平行于X轴的错切)是一个将任一点映射到点的操作,m 是固定参数,称为错切因子。

水平错切的效果是将每一点水平移动,移动的长度和该点的纵坐标成比例。

  1. 则x轴上方的所有点都向右移动,而x坐标轴下的点位置不变
  2. 则x轴上方的所有点都向左移动, 轴下方点移动的方向对应相反,而x坐标轴上的点位置不变
  3. 平行于x轴的直线保持不变,其他所有线绕与x轴交点转动不同的角度
  4. 原来竖直的线则变成斜率1/m的斜线,如此参数即竖直线倾斜后的倾角,称为错切角。

假定一个点经过错切变换后得到,对于水平错切而言,应该有如下关系:

矩阵表示就是:

这里写图片描述

竖直错切的操作类似,就是将x和y互换位置。

其矩阵表示形式为:

这里写图片描述

刚才提到的水平错切还是垂直错切都是单一方向的错切(x轴或者y轴)。在Android中了,除了使用单一错切外,可能会有混合错切,即水平错切和垂直错切的混合效果。用矩形表示就是:

这里写图片描述

为什么会有前置后置之分?

对于乘法交换率,应该是十分的熟悉: AB=BA。如果A和B是矩阵,是否依然满足呢?例如:

这里写图片描述

这也就是,对于矩阵而言:

这里写图片描述

两个矩阵相乘并不能满足乘法交换律,哪个在前面,哪个在后面,还是值得重视的。Matrix给我们提供了很多方法,尤其注意到了这一点:

  • preXX:以pre开头,表示矩阵相乘时其前置,例如preTranslate
  • postXX:以post开头,表示矩阵相乘时其后置,例如postScale

相关API

构造函数

  • Matrix()
  • Matrix(Matrix src)

Matrix类提供了两个构造函数,第一个构造函数用来创建单位矩阵,第二个构造函数用来创建新的矩阵,并将指定的矩阵的内容赋值给新建的矩阵。

单位矩阵:

这里写图片描述

getValues

  • getValues(float[] values)

getValues方法就是获取矩阵中的9个值,并将它们保存到指定的数组values中。

例如,创建了一个单位矩阵,获取它的9个值:

override fun initData() {    super.initData()    val matrix = Matrix()    val values = FloatArray(9)    matrix.getValues(values)    val sb = StringBuffer()    values.joinTo(sb)    // Log: 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0    Log.i("tea", "values: $sb")}

invert

  • invert(Matrix inverse)

invert方法用来判断矩阵是否可逆。如果可以则返回true。当矩阵可逆,而且inverse不为null时,则将矩阵的逆矩阵保存在inverse中。

所谓的逆矩阵就是:矩阵A为n阶方阵,若存在n阶矩阵B,使得矩阵A、B的乘积为单位阵,则称A为可逆阵,B为A的逆矩阵。若方阵的逆阵存
在,则称为可逆矩阵或非奇异矩阵,且其逆矩阵唯一。

例如:

val matrix = Matrix()val matrixInverse = Matrix()matrix.setTranslate(3f, 3f)// 矩阵为[1.0, 0.0, 3.0, 0.0, 1.0, 3.0, 0.0, 0.0, 1.0]// 判断矩阵是否可逆val isInverse = matrix.invert(matrixInverse)Log.e("tea", "isInverse: $isInverse")// 打印逆矩阵的值val sb = StringBuffer()val values = FloatArray(9)matrixInverse.getValues(values)values.joinTo(sb)// Log: 1.0, 0.0, -3.0, 0.0, 1.0, -3.0, 0.0, 0.0, 1.0Log.i("tea", "values: $sb")
## isAffine & isIdentity - isAffine():判断是否是仿射矩阵- isIdentity():判断是否是单位矩阵isIdentity方法用来判断矩阵是否为单位矩阵,不必多说。isAffine用来判断矩阵是否为仿射变换矩阵。仿射变换,可以保持原来的线共点、点共线的关系不变,保持原来相互平行的线仍然平行,保持原来的中点仍然是中点,保持原来在一直线上几段线段之间的比例关系不变。但是仿射变换不能保持原线段的长度不变,也不能保持原来的夹角角度不变。

前面已经提到,仿射变换是线性变换和平移变换的叠加,用矩阵表示就是:

这里写图片描述就是仿射变换矩阵。

mapXXX

mapPoints

mapPoint方法用于矩阵应用于2D点数组,并将转换后的点保存到相应的2D点数组中。

它有3个变形:

  1. mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, int pointCount):此矩阵将作用于点数组src,变换从 位置为srcIndex的点开始,共变换pointCount个点并将变换后的点保存到点数组dst中

    • dst:目标点数组
    • dstIndex:用于保存变换后点的起始索引
    • src:原点数组
    • srcIndex:从第几个点开始变换
    • pointCount:变换的点的个数
  2. mapPoints(float[] dst, float[] src):它是变形1的特列,此矩阵将作用于点数组src,并将变换后的点保存到点数组dst中
  3. mapPoints(float[] pts): 此矩阵将作用于点数组pts,并将变换后的点保存到dst中

例如:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    // 创建两个矩阵    val matrix = Matrix()    // 设置平移效果,沿着x轴向右平移100f,沿着y轴向下平移120f    matrix.setTranslate(100f, 120f)    // 定义点数组    val points = floatArrayOf(100f, 100f, 400f, 300f, 400f, 300f, 600f, 50f)    val pointMap = FloatArray(points.size)    // 将矩阵作用于点数组points    // 其效果是所有的的点平移,沿着x轴向右平移100f,沿着y轴向下平移120f    matrix.mapPoints(pointMap, points)    canvas?.drawLines(points, mPaint)    canvas?.drawLines(pointMap, mPaintMap)}

这里写图片描述

mapRadius

  • mapRadius(float radius)

mapRadius方法用于矩阵作用于圆的的半径,并变换后的半径值返回。例如,以(300f, 300f)为圆心,以100f为半径绘制圆:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val radius = 100f    canvas?.drawCircle(300f, 300f, radius, mPaint)    val matrix = Matrix()    // 缩放倍数为 sx = 0.5, sy = 0.5    matrix.setScale(0.5f, 0.5f)   val radiusScale =  matrix.mapRadius(radius)    canvas?.drawCircle(300f, 300f, radiusScale, mPaintMap)}

缩放矩阵的缩放系数为(0.5f, 0.5f),此时再以以(300f, 300f)为圆心,以缩放后的半径绘制一个圆。

这里写图片描述

当缩放系数为(0.3f, 0.8f)时:

这里写图片描述

当缩放系数为(0.8f, 0.3f)时:

这里写图片描述

mapRect

mapRect方法是将矩阵作用于指定的矩形,并将变换后的矩形保存。

  • mapRect(RectF rect)
  • mapRect(RectF dst, RectF src)

该方法有2个重载方法,前者是将矩阵作用于指定的矩形rect,并将变换后的矩形保存到rect中。后者是将矩阵作用于源矩形src,并将变换后的矩形保存到目的矩形 dst,此时源矩形是不变的。

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val rectSrc = RectF(100f, 100f, 400f, 300f)    val rectDst = RectF()    // Log: before - rectSrc: RectF(100.0, 100.0, 400.0, 300.0)    Log.i("teaphy", "before - rectSrc: $rectSrc")    // Log: before - rectDst: RectF(0.0, 0.0, 0.0, 0.0)    Log.i("teaphy", "before - rectDst: $rectDst")    val matrix = Matrix()    // 设置缩放,x轴的缩放系数为0.5f,y轴的错切系数为0.5f    matrix.setScale(0.5f, 0.5f)    matrix.mapRect(rectDst, rectSrc)    // Log: after - rectSrc: RectF(100.0, 100.0, 400.0, 300.0)    Log.i("teaphy", "after - rectSrc: $rectSrc")    // Log: after - rectDst: RectF(50.0, 50.0, 200.0, 150.0)    Log.i("teaphy", "after - rectDst: $rectDst")    canvas?.drawRect(rectSrc, mPaint)    canvas?.drawRect(rectDst, mPaintMap)}

如果将缩放矩阵作用于矩形时,矩形的端点坐标将做相应的缩放。如下图:

这里写图片描述

这里尤其要注意的是:不管是线性变换,还是平移变换,甚至是仿射变换,作用于矩形之后,所得到仍然是矩形

mapVectors

  • mapVectors(float[] vecs):将矩阵作用于矢量坐标数组vecs,并将所得的矢量坐标数组取代vecs中的数据。
  • mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount):将矩阵作用于源矢量坐标数组src,其中,从索引 srcIndex开始,转换 vectorCount个点后,将所得的矢量坐标数组保存到dst中,其开始索引为 dstIndex
  • mapVectors(float[] dst, float[] src):将矩阵作用于将矩阵作用于源矢量坐标数组src,所得的矢量坐标数组保存到dst中

mapVectors方法是将矩阵作用于矢量。它与mapPoints方法类似,不同的是,它不受平移的影响。

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val vector = floatArrayOf(2f, 3f)    val point = floatArrayOf(2f, 3f)    val matrix = Matrix()    // 平移变换    matrix.setTranslate(2f, 3f)    matrix.mapVectors(vector)    matrix.mapPoints(point)    // Log: after - vector: 2.0, 3.0    Log.e("teaphy:" , " after - vector: ${vector.joinToString()}")    // Log: after - point: 4.0, 6.0    Log.e("teaphy:" , " after - point: ${point.joinToString()}")    // 缩放变换    matrix.setScale(0.5f, 0.5f)    matrix.mapVectors(vector)    matrix.mapPoints(point)    //  after - vector: 1.0, 1.5    Log.e("teaphy:" , " after - vector: ${vector.joinToString()}")    // after - point: 2.0, 3.0    Log.e("teaphy:" , " after - point: ${point.joinToString()}")}

从示例代码中,可以看出:

  1. mapPoints不受缩放变换的影响
  2. mapVectors不受平移变换的影响

postXX & preXX

前面已经提到了,两个矩阵相乘并不能满足乘法交换律,故而,前置和后置的概念被提出来,这里不多赘述。其中:

postXX方法相当于当前矩阵(A)右乘参数矩阵(B),即 BA.表述形式为

M’ = B * A

例如:

这里写图片描述

用代码表示:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    matrix.setTranslate(100f, 100f)    matrix.postRotate(90f)    // Log: Matrix{[0.0, -1.0, -100.0][1.0, 0.0, 100.0][0.0, 0.0, 1.0]}    Log.e("teaphy", "matrix: $matrix")}

preXX当于当前矩阵(A)左乘参数矩阵(B),即AB。表述形式为:

M’ = A * B

例如:

这里写图片描述

用代码表示:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    matrix.setTranslate(100f, 100f)    matrix.preRotate(90f)    // Log: Matrix{[0.0, -1.0, 100.0][1.0, 0.0, 100.0][0.0, 0.0, 1.0]}    Log.e("teaphy", "matrix: $matrix")}

它们有一个共同点,就是不会重置当前矩阵(A)

相关API

  • postConcat(Matrix other):用当前矩阵右乘指定矩阵
  • postRotate(float degrees, float px, float py):用当前矩阵右乘旋转变换矩阵,其中,旋转角度为degrees,旋转中心坐标为(px, py)
  • postRotate(float degrees):用当前矩阵右乘旋转变换矩阵,其中,旋转角度为degrees
  • postScale(float sx, float sy, float px, float py):用当前矩阵右乘缩放变换矩阵,其中,缩放比例为(Sx, Sy),缩放中心为(px,py)
  • postScale(float sx, float sy):用当前矩阵右乘缩放变换矩阵,其中,缩放比例为(Sx, Sy)
  • postSkew(float kx, float ky):用当前矩阵右乘错切变换矩阵,其中,错切比例为(kx, ky)
  • postSkew(float kx, float ky, float px, float py):用当前矩阵右乘错切变换矩阵,其中,错切比例为(kx, ky),轴心点坐标为(px, py).轴心点是应保持不变的坐标
  • postTranslate(float dx, float dy):用当前矩阵右乘平移变换矩阵,其中,x轴方向的位移量为 dx, y轴方向的位移量为 dy

  • preConcat(Matrix other):用当前矩阵左乘指定矩阵
  • preRotate(float degrees, float px, float py):用当前矩阵左乘旋转变换矩阵,其中,旋转角度为degrees,旋转中心坐标为(px, py)
  • preRotate(float degrees):用当前矩阵左乘旋转变换矩阵,其中,旋转角度为degrees
  • preScale(float sx, float sy):用当前矩阵左乘缩放变换矩阵,其中,缩放比例为(Sx, Sy)
  • preScale(float sx, float sy, float px, float py):用当前矩阵左乘错切变换矩阵,其中错切因子为(kx, ky),轴心点坐标为(px, py).轴心点是应保持不变的坐标
  • preSkew(float kx, float ky):用当前矩阵左乘错切变换矩阵,其中错切变换矩阵的错切比例为(kx, ky)
  • preSkew(float kx, float ky, float px, float py):用当前矩阵左乘错切变换矩阵,其中,错切比例为(kx, ky),轴心点坐标为(px, py).轴心点是应保持不变的坐标
  • preTranslate(float dx, float dy):用当前矩阵左乘平移变换矩阵,其中,x轴方向的位移量为 dx, y轴方向的位移量为 dy

setXX

setXX方法与postXX&preXX方法不同,其首先将当前矩阵重置为单位矩阵,即调用reset方法(),然后再根据相应的变换设置Matrix中的值。

相关API

  • setRotate(float degrees, float px, float py):设置旋转变换矩阵,其中,旋转角度为degrees,旋转中心坐标为(px, py)
  • setRotate(float degrees):设置旋转变换矩阵,其中,旋转角度为degree
  • setScale(float sx, float sy):设置缩放矩阵, 其中,缩放比例为(Sx, Sy)
  • setScale(float sx, float sy, float px, float py):设置缩放矩阵,其中,缩放比例为(Sx, Sy)
  • setSkew(float kx, float ky):设置错切变换矩阵,其中,错切因子为(kx, ky)
  • setSkew(float kx, float ky, float px, float py):设置错切变换矩阵,其中,错切因子为(kx, ky),轴心点坐标为(px, py).轴心点是应保持不变的坐标
  • setTranslate(float dx, float dy):设置平移变换矩阵,其中,x轴方向的位移量为 dx, y轴方向的位移量为 dy

特例API

set(Matrix src)

set(Matrix src)方法将src中Matrix值替换当前Matrix中的值。如果src为null,那么当前矩阵将被重置为单位矩阵。

例如:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrixsrc = Matrix()    val matrixA = Matrix()    val matrixB = Matrix()    matrixsrc.setValues(floatArrayOf(1f, 2f, 3f,                                     4f, 5f, 6f,                                     7f, 8f, 9f))    matrixA.set(matrixsrc)    matrixB.set(null)    // Log:  matrixA: Matrix{1.0, 2.0, 3.0    //                       4.0, 5.0, 6.0    //                       7.0, 8.0, 9.0}    Log.e("teaphy", "matrixA: $matrixA")    // Log: matrixB: Matrix{1.0, 0.0, 0.0    //                      0.0, 1.0, 0.0    //                      0.0, 0.0, 1.0]}    Log.e("teaphy", "matrixB: $matrixB")}

setConcat(m1, m2)

setConcat(Matrix m1, Matrix m2):将当前矩阵设置为两个指定矩阵的乘积,计算规则为: m1 * m2。

例如:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val matrixA = Matrix()    val matrixB = Matrix()    matrixA.setValues(floatArrayOf(1f, 2f, 3f,                                     4f, 5f, 6f,                                     7f, 8f, 9f))    matrixB.setValues(floatArrayOf(2f, 3f, 4f,                                5f, 6f, 7f,                                8f, 9f, 1f))    matrix.setConcat(matrixA, matrixB)    // Log:  matrixA: Matrix{18.0, 21.0, 10.5,    //                       40.5, 48.0, 28.5,    //                       63.0, 75.0, 46.5}    Log.e("teaphy", "matrix: $matrix")}

这里需要注意的一点是,两个指定矩阵的乘积所得的矩阵的公约数会被提取出来,然后才会得到最终的矩阵。

setSinCos

  • setSinCos(float sinValue, float cosValue, float px, float py):设置旋转变换矩阵,其中,旋转角度为degrees,旋转中心坐标为(px, py)
  • setSinCos(float sinValue, float cosValue):设置旋转变换矩阵,其中,旋转角度为degrees,旋转中心坐标为(px, py)

setSinCos方法用来设置旋转矩阵。与setRotate方法不同的是,它不是指定旋转的角度,而是,通过指定旋转角度的正弦和余弦值来设置旋转。此时旋转转换矩阵为:

这里写图片描述

其计算方式是这样:

这里写图片描述

例如:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val rectF = RectF(300f, 300f, 600f, 500f)    // 设置旋转变换,余弦值为0.5f,正弦值为0.5f    matrix.setSinCos(0.5f, 0.5f)    // 设置偏移变换,x轴方向偏移200f,y轴方向偏移200f    matrix.postTranslate(200f, 200f)    canvas?.drawBitmap(mBitmap, matrix, mPaint)}

示例代码中,在设置旋转变换时,将旋转角的正弦和余弦值分别设置为0.5f和0.5f,此时:

这里写图片描述

效果图:

这里写图片描述

reset

reset()方法没有啥特殊解释的,就是将矩阵重置为单位矩阵。

setValues

setValues(float[] values)方法就是数组的中的前9值一一赋值给矩阵。数组values的长度必须≥9,如果其长度小于9时,调用此方法,抛出ArrayIndexOutOfBoundsException。如果values数组的长度大于9,将取前9个值进行赋值。

例如:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val values = floatArrayOf(1f, 2f, 3f,                              4f, 5f, 6f,                              7f, 8f, 9f, 10f)    matrix.setValues(values)    // Log: matrix: Matrix{[1.0, 2.0, 3.0][4.0, 5.0, 6.0][7.0, 8.0, 9.0]}    Log.e("teaphy", "matrix: $matrix")}

在示例代码中,创建了一个长度为10的数组,然后调用setValues给matrix赋值。由于浮点数组的长度大于10,而Matrix中只有9个值,setValues只是提取了前9个值对matrix赋值。从Log打印中,也可以清晰的看到这一点。

rectStaysRect

rectStaysRect()方法用来判断矩阵作用于矩形后,是否依然可以获得一个矩形。它的判断标准是:矩阵为单位矩阵,或者只是进行平移、缩放,及旋转的角度为90的倍数,即”仿射变换”中,其旋转的角度必须为90的倍数。

例如:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    // 将矩阵重置为单位矩阵    matrix.reset()    val rsrIdentify = matrix.rectStaysRect()    // Log: rsrIdentify: true    Log.e("teaphy", "rsrIdentify: $rsrIdentify")    // 设置平移变换矩阵    matrix.setTranslate(100f, 100f)    val rsrTranslate = matrix.rectStaysRect()    // Log:rsrTranslate: true    Log.e("teaphy", "rsrTranslate: $rsrTranslate")    // 设置缩放变换矩阵    matrix.setScale(0.2f, 0.3f)    val rsrScale = matrix.rectStaysRect()    // Log:rsrScale: true    Log.e("teaphy", "rsrScale: $rsrScale")    // 设置旋转变换 旋转角度为180    matrix.setRotate(180f)    val rsrRotate = matrix.rectStaysRect()    // Log: rsrRotate: true    Log.e("teaphy", "rsrRotate: $rsrRotate")    // 设置旋转变换 旋转角度为60    matrix.setRotate(60f)    val rsrRotate60 = matrix.rectStaysRect()    // Log: rsrRotate60: false    Log.e("teaphy", "rsrRotate60: $rsrRotate60")    // 设置错切变换    matrix.setSkew(0.5f, 0.5f)    val rsrSkew = matrix.rectStaysRect()    // Log: rsrSkew: false    Log.e("teaphy", "rsrSkew: $rsrSkew")}

setPolyToPoly

  • setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)

setPolyToPoly方法通过指定的0-4点,源点的坐标及变换后的坐标,来设置变换矩阵。其中:

  1. 每个点由坐标数组的两个浮点值表示,即[x0, y0, x1, y1, …]
  2. srcIndex表示源点坐标数组中指定点的起始索引
  3. dstIndex表示变换后的坐标数组指定点的起始索引
  4. pointCount用来生成变换矩阵的点数。point不能大于4,如果大于4,将抛出IllegalArgumentException异常。

其计算方式为:

dst = m * src

如果pointCount为0,那么没有任何变换效果。

pointCount为1时,将达到平移效果。

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val width = mBitmap.width.toFloat()    val height = mBitmap.height.toFloat()    val src = floatArrayOf(width/2, height/2)    val dst = floatArrayOf(width, height)    matrix.setPolyToPoly(src, 0, dst,0, 1)    canvas?.drawBitmap(mBitmap, 0f, 0f, mPaint)    canvas?.drawBitmap(mBitmap, matrix, mPaint)}

这里写图片描述

pointCount为2时,可以达到缩放、旋转、平移 变换的效果:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val w = mBitmap.width.toFloat()    val h = mBitmap.height.toFloat()    val src = floatArrayOf(w/2, h/2, w, 0f)    val dst = floatArrayOf(w/2, h/2, w/2 + h/2, w/2 + h/2)    matrix.setPolyToPoly(src, 0, dst,0, 2)    canvas?.drawBitmap(mBitmap, 0f, 0f, mPaint)    canvas?.drawBitmap(mBitmap, matrix, mPaint)}

这里写图片描述

pointCount为3时,可以达到 缩放、旋转、平移、错切 效果:

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val w = mBitmap.width.toFloat()    val h = mBitmap.height.toFloat()    val src = floatArrayOf(0f, 0f, 0f, h, w, h)    val dst = floatArrayOf(0f, 0f, 200f, h, w + 200, h)    matrix.setPolyToPoly(src, 0, dst, 0, 3)    matrix.postTranslate(0f, 300f)    canvas?.drawBitmap(mBitmap, 0f, 0f, mPaint)    canvas?.drawBitmap(mBitmap, matrix, mPaint)}

这里写图片描述

pointCount为4时,可以达到 缩放、旋转、平移、错切以及任何形变效果。也可以达到透视效果,所谓的透视效果,就是观察角度的改变,导致投射的二维图像发生了变化。

override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val w = mBitmap.width.toFloat()    val h = mBitmap.height.toFloat()    val tra = 100f    val src = floatArrayOf(0f, 0f, 0f, h, w, h, w, 0f)    val dst = floatArrayOf(0f + tra, 0f, 0f, h, w, h, w - tra, 0f)    matrix.setPolyToPoly(src, 0, dst, 0, 4)    matrix.postTranslate(0f, 300f)    canvas?.drawBitmap(mBitmap, 0f, 0f, mPaint)    canvas?.drawBitmap(mBitmap, matrix, mPaint)}

这里写图片描述

setRectToRect

  • setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf)

setRectToRect方法是将源矩形的内容填充到目标矩形中。如果源矩形和目标矩形的长宽比例不一样,到底该如何缩放填充呢?ScaleToFit,就是用来指定填充模式

ScaleToFit 有如下四个值:

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

谷歌官方示例图形:

例如:

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {    super.onSizeChanged(w, h, oldw, oldh)    mViewWidth = w.toFloat()    mViewHeight = h.toFloat()}override fun onDraw(canvas: Canvas?) {    super.onDraw(canvas)    val matrix = Matrix()    val w = mBitmap.width.toFloat()    val h = mBitmap.height.toFloat()    val src = RectF(0f, 0f, w, h)    val dst = RectF(0f, 0f, mViewWidth, mViewHeight)    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.END)    canvas?.drawBitmap(mBitmap, 0f, 0f, mPaint)    canvas?.drawBitmap(mBitmap, matrix, mPaint)}

总结

本篇文章介绍了Matrix的原理及相关的所有API。



参考资料:

  1. 仿射变换与齐次坐标
  2. 齐次坐标系入门级思考
  3. Android Matrix
  4. Android中图像变换Matrix的原理、代码验证和应用


如果觉得我的文章对您有用,请随意点赞、评论。您的支持将鼓励我继续创作!不足之处,敬请指出改正!