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.0Log.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轴向下平移120fmatrix.setTranslate(100f, 120f)// 定义点数组val points = floatArrayOf(100f, 100f, 400f, 300f, 400f, 300f, 600f, 50f)val pointMap = FloatArray(points.size)// 将矩阵作用于点数组points// 其效果是所有的的点平移,沿着x轴向右平移100f,沿着y轴向下平移120fmatrix.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 = 100fcanvas?.drawCircle(300f, 300f, radius, mPaint)val matrix = Matrix()// 缩放倍数为 sx = 0.5, sy = 0.5matrix.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.5fmatrix.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.0Log.e("teaphy:" , " after - vector: ${vector.joinToString()}")// Log: after - point: 4.0, 6.0Log.e("teaphy:" , " after - point: ${point.joinToString()}")// 缩放变换matrix.setScale(0.5f, 0.5f)matrix.mapVectors(vector)matrix.mapPoints(point)//  after - vector: 1.0, 1.5Log.e("teaphy:" , " after - vector: ${vector.joinToString()}")// after - point: 2.0, 3.0Log.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.5fmatrix.setSinCos(0.5f, 0.5f)// 设置偏移变换,x轴方向偏移200f,y轴方向偏移200fmatrix.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: trueLog.e("teaphy", "rsrIdentify: $rsrIdentify")// 设置平移变换矩阵matrix.setTranslate(100f, 100f)val rsrTranslate = matrix.rectStaysRect()// Log:rsrTranslate: trueLog.e("teaphy", "rsrTranslate: $rsrTranslate")// 设置缩放变换矩阵matrix.setScale(0.2f, 0.3f)val rsrScale = matrix.rectStaysRect()// Log:rsrScale: trueLog.e("teaphy", "rsrScale: $rsrScale")// 设置旋转变换 旋转角度为180matrix.setRotate(180f)val rsrRotate = matrix.rectStaysRect()// Log: rsrRotate: trueLog.e("teaphy", "rsrRotate: $rsrRotate")// 设置旋转变换 旋转角度为60matrix.setRotate(60f)val rsrRotate60 = matrix.rectStaysRect()// Log: rsrRotate60: falseLog.e("teaphy", "rsrRotate60: $rsrRotate60")// 设置错切变换matrix.setSkew(0.5f, 0.5f)val rsrSkew = matrix.rectStaysRect()// Log: rsrSkew: falseLog.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 = 100fval 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的原理、代码验证和应用

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

自定义View之Matrix最全API解析相关推荐

  1. 安卓自定义View进阶-Matrix Camera

    原文出处:http://www.gcssloop.com/customview/matrix-3d-camera 本篇依旧属于Matrix,主要讲解Camera,Android下有很多相机应用,其中的 ...

  2. 自定义view - 收藏集 - 掘金

    Android 从 0 开始自定义控件之 View 的 draw 过程 (九) - Android - 掘金 转载请标明出处: http://blog.csdn.net/airsaid/... 本文出 ...

  3. (转载)自定义View——弹性滑动

    滑动是Android开发中非常重要的UI效果,几乎所有应用都包含了滑动效果,而本文将对滑动的使用以及原理进行介绍. 一.scrollTo与ScrollBy View提供了专门的方法用于实现滑动效果,分 ...

  4. 安卓自定义View进阶-分类与流程

    自定义View绘制流程函数调用链(简化版) 一.自定义View分类 我将自定义View分为了两类(sloop个人分类法,非官方): 1.自定义ViewGroup 自定义ViewGroup一般是利用现有 ...

  5. 超全的Android面经_安卓面经(20/30)之自定义View全解析

    系列专栏: 安卓高频面经解析大全专栏链接:150道安卓高频面试题全解析 安卓高频面经解析大全目录详情 : 安卓面经_anroid面经_150道安卓常见基础面试题全解析 安卓系统Framework面经专 ...

  6. Android自定义view之View的测量过程全解析

    Android 应用层开发中绕不开自定义 View 这个话题,虽然现在 Github 上有形形色色的开源库供大家使用, 但是作为一名开发者而言,虽然不提倡重复造轮子,但是轮子都是造出来的.碰到一些新鲜 ...

  7. Android 自定义view完全解析--带你通透了解自定义view

    参考转自郭霖博客带你一步步深入了解View系列 Android LayoutInflater原理分析 相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用 ...

  8. 【5年Android从零复盘系列之二十】Android自定义View(15):Matrix详解(图文)【转载】

    [转载]本文转载自麻花儿wt 的文章<android matrix 最全方法详解与进阶(完整篇)> [5年Android从零复盘系列之二十]Android自定义View(15):Matri ...

  9. 安卓自定义view中 绘画基本图形点线面,矩形,方形,圆,扇形,文字及沿着特定方向布局,自定义圆角ImageView图片等等相关api使用方法及举例

    安卓自定义view中 绘画基本图形点线面,矩形,方形,圆,扇形,文字及沿着特定方向布局,自定义圆角ImageView图片等等相关api使用方法及举例,图片压缩处理逻辑 本文旨在介绍自定义View的实现 ...

  10. Android自定义View全解

    目录 目录.png 1. 自定义View基础 1.1 分类 自定义View的实现方式有以下几种 类型 定义 自定义组合控件 多个控件组合成为一个新的控件,方便多处复用 继承系统View控件 继承自Te ...

最新文章

  1. select BUGS
  2. ASP.NET MVC 1.0 NVelocityViewEngine
  3. android圆形菜单
  4. guava入门学习2(新集合)
  5. Windows azure国际版下通过 windows auzre powershell为VM分配绑定virtual ip address
  6. 一个报文的路由器之旅_一个报文的路由器之旅
  7. opencv 从原始的图像中找出ROI区域
  8. 软件开发和DBA谁更吃香?有答案了
  9. 1076. Wifi密码 (15)-PAT乙级真题
  10. 使用sklearn进行数据挖掘
  11. spring-boot 加载本地静态资源文件路径配置
  12. Linux网络服务-LAMP之Php基于Apache的模块实现
  13. [转载]PLSQL安装破解
  14. 解决资源监视器不显示的问题。
  15. 你想要的单片机自学指南都在这里(大一必看)
  16. 【上电即上华为云】华为云openCPU智联模组_wifi_HF-LPX70_RISC-V_CoAP
  17. 我的VSTO之路(四):深入介绍Word开发
  18. AUBO E系列教育科研型机器人QA--持续更新中
  19. 关键时刻不纠结的秘密:极简选择
  20. 升级鸿蒙手机内数据会删除吗,鸿蒙系统:手机升级不会删除任何文件,包括APP的登录状态都不会掉...

热门文章

  1. eclipse中folder、source folder、package的区别及相互转换
  2. java定制化报表_定制自己的报表!7款实用开源报表工具
  3. 赛博僵尸道长 v1.2
  4. 一年级计算机算文具吗,一年级老师说,用这样文具的孩子,课堂上都没有认真听讲...
  5. Python爬虫练习:爬取蜂鸟网图片数据
  6. Camtasia 2019卸载-无痕强力卸载
  7. Nginx动静分离经典配置
  8. 【TFLearn和TensorFlow应用】——泰坦尼克号预测
  9. 你在项目中做过哪些安全防范措施?
  10. 国王游戏【贪心算法】