自定义View之Matrix最全API解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/IO_Field/article/details/78843630
Matrix是Android SDK提供的一个 3 * 3的矩阵类,用来转换坐标。那么,它有9个值: 名称|常量值 – | – MSCALE_X | 0 MSKEW_X | 1 MTRANS_X | 2 MSKEW_Y | 3 MSCALE_Y | 4 MTRANS_Y | 5 MPERSP_0 | 6 MPERSP_1 | 7 MPERSP_2 | 8 用矩阵格式表示就是这样: ![](https://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.0
Log.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的原理、代码验证和应用


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

猜你喜欢

转载自blog.csdn.net/IO_Field/article/details/78843630
今日推荐