前言
最近在做关于图片查看的优化,陆陆续续的花了一周多的时间(加上其他需求修Bug的时间),恼火。
恼火.jpg不过确实有点收获,所以后续的几篇文章都会跟图片或者动画相关,本篇是个开胃菜,跟大家介绍一下如何使用Matrix。
看一下学习完 Matrix 可以写出的动画:
Matrix动画.gif一、Matrix是什么?
作为一个 Android 开发仔,我们直接使用 Matrix 的机会比较少。
但是我们却一直在和 Matrix
打交道,比如 ImageView
,通过设置不同的 ScaleType
得到不同的 Matrix
,从而展现不同的效果。再深一点,各种 View 的背后,都有 Matrix
的一份功劳!
Matrix
是一个 3*3 的矩阵,如果你已经忘了矩阵是什么?可以在 【这里】 回顾一下。
既然是一个 3*3 矩阵,所以他由9个参数组成:
上面的6个数字分别控制着缩放、斜切和平移,缩放和斜切一起控制,可以达到旋转的效果。
下面的三个参数可以控制 3D 变换,本篇中我们不做过多的介绍。
说明
本节剩下的这一部分来自 GcsSloop 的文章:《Matrix简介》
1.缩放(Scale)
image用矩阵表示:
image你可能注意到了,我们坐标多了一个1,这是使用了齐次坐标系的缘故,在数学中我们的点和向量都是这样表示的(x, y),两者看起来一样,计算机无法区分,为此让计算机也可以区分它们,增加了一个标志位,增加之后看起来是这样:
(x, y, 1) - 点
(x, y, 0) - 向量另外,齐次坐标具有等比的性质,(2,3,1)、(4,6,2)...(2N,3N,N)表示的均是(2,3)这一个点。(将MPERSP_2解释为scale这一误解就源于此)。
图例:
image2.错切(Skew)
错切存在两种特殊错切,水平错切(平行X轴)和垂直错切(平行Y轴)。
水平错切
image用矩阵表示:
image图例:
image垂直错切
image用矩阵表示:
image图例:
image复合错切
image水平错切和垂直错切的复合。
用矩阵表示:
image图例:
image3.旋转(Rotate)
假定一个点 A(x0, y0) ,距离原点距离为 r, 与水平轴夹角为 α 度, 绕原点旋转 θ 度, 旋转后为点 B(x, y) 如下:
image用矩阵表示:
image图例:
image4.平移(Translate)
image此处也是使用齐次坐标的优点体现之一,实际上前面的三个操作使用 2x2 的矩阵也能满足需求,但是使用 2x2 的矩阵,无法将平移操作加入其中,而将坐标扩展为齐次坐标后,将矩阵扩展为 3x3 就可以将算法统一,四种算法均可以使用矩阵乘法完成。
用矩阵表示:
image图例:
image二、ScaleType和Matrix
从上面我们跟着GcsSloop大佬学习了基本的 Matrix
操作,现在我们再来了解 ImageView
中的 ScaleType
和其对应 Matrix
。
我们都知道,ImageView
中对应的 ScaleTyp
有:CENTER_CROP
、CENTER
、CENTER_INSIDE
、FIT_END
、FIT_CENTER
、FIT_START
、FIT_XY
和 MATRIX
。
假设图中的 ImageView
的宽和高都为 300 像素,图片的宽高分别为 1200 和 600 像素。
1. CENTER_CROP
CENTER_CROP
是我们平时用的最多的一种类型。规则:图片宽高等比缩放,直到宽高都大于或者等于控件的宽高,图片多出来的部分会被裁切。
如何得到 CENTER_CROP
对应的 Matrix
呢?其实也很简单,分两步:
第一步:缩放图片,将图片宽高任意一边都能够大于或者等于控件的长度。
控件长 / 图片长 = o.25
控件宽 / 图片宽 = 0.5
为了能将图片充满控件,保证不留空白,需要选择控件图片比大的那一边,所以我们现在的 Matrix
为:
[0.5 0 0]
[0 0.5 0]
[0 0 1]
当前图片和控件就变成了:
活动状态.png第二步:居中平移,如上图,图片的宽仍大于控件的宽,为了让图片的中间位置显示在控件上,需要对图片做一定的平移。
translationX = (imageViewWidth - drawableWidth * scale) / 2
,需要平移矩阵左乘缩放矩阵,更新后的 Matrix 为:
[1 0 -150] [0.5 0 0] [0.5 0 -150]
[0 1 0] * [0 0.5 0] = [0 0.5 0]
[0 0 1] [0 0 1] [0 0 1]
当前图片和控件就变成了:
最终图形.png这也是应用了 CENTER_CROP
后 ImageView
的最终形态。
2. Center
Center
规则:图片不缩放,只平移,最终将图片中心区域展示在控件上。
所以经计算:
translationX = (imageViewWidth - drawableWidth) / 2;
translationY = (imageViewHeight - drawableHeight) / 2;
Matrix
为:
[1 0 -450]
[0 1 -150]
[0 0 1]
得到的图形为:
Center效果CENTER_INSIDE
CENTER_INSIDE
规则:等比缩小图片,使得控件可以展示完整的图片并居中展示,当图片小于控件的宽高时不放大。
FIT_XY
FIT_XY
规则:分别缩放图片宽高,缩放后的宽高会等于控件的宽高,图片可能会变形。
FIT_START
FIT_START
规则:等比缩放图片宽高,控件可以完整展示图片,平移至头部区域。
FIT_CENTER
FIT_CENTER
规则:等比缩放图片宽高,控件可以完整展示图片,平移至控件中心区域,当图片宽高小于控件宽高的时会对图片放大。
FIT_END
FIT_END
规则:等比缩放图片宽高,控件可以完整展示图片,平移到控件底部和尾部。
Matrix
Matrix
规则:自定义 Matrix
。
三、做一个Matrix动画
做一个 Matrix
过渡动画还是比较简单的,因为估值器 MatrixEvaluator
是现成的,只要确定前后两个或者几个 Matrix
就行, Matrix
的使用方法:
val evaluator = MatrixEvaluator()
val matrix = evaluator.evaluate(it.animatedFraction, firstMatrix, secondMatrix)
iv.imageMatrix = matrix
在本文开始的动画中,一共需要6个 Matrix
(1和5一样):
那如何获取这些 Matrix
呢?ImageView 为我们提供 ImageView#getImageMatrix()
方法,帮助我们获取当前 ImageView 的 Matrix,我们再对每幅图分析一下:
- 第一张:当前
Matrix
不变。 - 第二张:当前
Matrix
向右平移一半宽。 - 第三张:当前
Matrix
向右平移一半宽,向下平移一半高。 - 第四张:当前
Matrix
向下平移一半高。 - 第五张:当前
Matrix
同第一张。 - 第六张:当前
Matrix
向右平移1/4宽,向下平移1/4高。 - 第七张:当前
Matrix
不变。
剩下的就是动画代码:
fun doSomeThingAnimation() {
ivContent?.let { iv ->
val imgWidth = iv.width
val imgHeight = iv.height
iv.layoutParams.width = imgWidth / 2
iv.layoutParams.height = imgHeight / 2
val tX = imgWidth.toFloat() / 2
val ty = imgHeight.toFloat() / 2
val initMatrix = iv.imageMatrix
// 获取6个Matrix
val oneMatrix = Matrix(initMatrix)
val twoMatrix = Matrix(initMatrix).apply {
postTranslate(-tX, 0f)
}
val threeMatrix = Matrix(initMatrix).apply {
postTranslate(-tX, -ty)
}
val fourthMatrix = Matrix(initMatrix).apply {
postTranslate(0f, -ty)
}
val fiveMatrix = Matrix(initMatrix).apply {
postTranslate(-tX / 2, - ty / 2)
}
val sixMatrix = Matrix(initMatrix)
iv.scaleType = ScaleType.MATRIX
iv.imageMatrix = oneMatrix
// 构建6段动画
val evaluator = MatrixEvaluator()
val firstAnimator = ValueAnimator.ofFloat(0f, 1f)
firstAnimator.addUpdateListener {
val matrix = evaluator.evaluate(it.animatedFraction, oneMatrix, twoMatrix)
iv.translationX = it.animatedFraction * imgWidth / 2
iv.imageMatrix = matrix
}
val secondAnimator = ValueAnimator.ofFloat(0f, 1f)
secondAnimator.addUpdateListener {
val fraction = it.animatedFraction
iv.translationY = fraction * imgHeight / 2
val matrix = evaluator.evaluate(fraction, twoMatrix, threeMatrix)
iv.imageMatrix = matrix
}
val threeAnimator = ValueAnimator.ofFloat(0f, 1f)
threeAnimator.addUpdateListener {
val fraction = it.animatedFraction
iv.translationX = ((1 - fraction) * imgWidth / 2)
val matrix = evaluator.evaluate((fraction), threeMatrix, fourthMatrix)
iv.imageMatrix = matrix
}
val fourAnimator = ValueAnimator.ofFloat(0f, 1f)
fourAnimator.addUpdateListener {
val fraction = it.animatedFraction
iv.translationY = ((1 - fraction) * imgHeight / 2)
val matrix = evaluator.evaluate((fraction), fourthMatrix, oneMatrix)
iv.imageMatrix = matrix
}
val fiveAnimator = ValueAnimator.ofFloat(0f, 1f)
fiveAnimator.addUpdateListener {
val fraction = it.animatedFraction
iv.translationY = (fraction * imgHeight / 4)
iv.translationX = (fraction * imgWidth / 4)
val matrix = evaluator.evaluate((fraction), oneMatrix, fiveMatrix)
iv.imageMatrix = matrix
}
val sixAnimator = ValueAnimator.ofFloat(0f, 1f)
sixAnimator.addUpdateListener {
val fraction = it.animatedFraction
iv.layoutParams.width = (imgWidth / 2 + imgWidth / 2 * fraction).toInt()
iv.layoutParams.height = (imgHeight / 2 + imgHeight / 2 * fraction).toInt()
iv.translationY = ((1 - fraction) * imgHeight / 4)
iv.translationX = ((1 - fraction) * imgWidth / 4)
val matrix = evaluator.evaluate((fraction), fiveMatrix, sixMatrix)
iv.imageMatrix = matrix
iv.requestLayout()
}
val set = AnimatorSet()
set.playSequentially(firstAnimator, secondAnimator, threeAnimator, fourAnimator, fiveAnimator, sixAnimator)
set.duration = 1000
set.start()
}
}
一个简单动画技能 Get!
总结
这个动画实际使用的场景并不多,但是当你需要同一个图片不同 ScaleType 之前切换的时候,比如说小图打开成大图的时候,Matrix 动画也许可以帮的上你的忙!
精彩内容
如果觉得本文不错,「点赞」是对作者最大的鼓励~
技术不止,文章有料,关注公众号 九心说,每周一篇高质好文,和九心在大厂路上肩并肩。
引用文章:
网友评论