属性动画是如何在移动时响应用户点击事件的?
也就是说,我们在使用了属性动画之后,该 View 依然可以正确地接收到事件的分派。
ViewGroup 是如何找到这个 View 的
我们知道,调用 View 的 translationXX 方法之后,虽然在屏幕上的位置是变了,但是它的 [left,top,right,bottom]
是不会变的。
来捋一遍 ViewGroup 分派事件的大致流程:
当手指按下时,触摸事件会经过 ViewGroup 中的 dispatchTouchEvent
方法筛选符合条件(手指在边界范围内)的子 View 进行分派事件(如果未被 onInterceptTouchEvent
拦截的话)。
那么,如果某个子 View 刚好应用了 translation 属性动画,在 ViewGroup 筛选子 View 时,直接判断触摸点是否在 [left,top,right,bottom]
范围内,是肯定不行的。
那它是怎么判断的呢?
-
它会先调用子 View 的
hasIdentityMatrix
方法来判断这个 View 是否应用过位移、缩放、旋转之类的属性动画。 -
如果应用过的话,那接下来还会把触摸点映射到该子 View 的逆矩阵上(
getInverseMatrix
)。 -
判断处理后的触摸点,是否在该子 View 的边界范围内。
上面说到了" 把触摸点映射到该子 View 的逆矩阵上 ",那它是怎么个映射法:
比如一个 View 它水平平移了200,那它所对应的逆矩阵就是水平平移了-200,
如果触摸点坐标是[500,500]的话,那么映射之后,就是[300,500],也就是反方向移动同样的距离了。
可以这样来理解:
如果一个 View 向右移动了一个拇指的距离,当手指在它的新位置上按下的时候,
(它最终还是要判断是否在原来的边界范围内的,那只能把触摸的坐标,给转回去,转回它应用变换之前的位置上),
那 ViewGroup 在检测到它应用了变换后,会把现在的触摸点,向左(刚刚是向右)移动一个拇指的距离(抵消),再来判断是否在该 View 的边界范围内。
并在每一帧之后把控件的位置重新摆放。
@Override
public boolean onTouch(View v, MotionEvent event) {
...
if (event.getAction() == MotionEvent.ACTION_MOVE) {
...
mView.layout(x - widthOffset, y - heightOffset - toolbarHeight,
x + widthOffset, y + heightOffset - toolbarHeight);
...
}
return true;
}
那么为什么只有属性动画可以这样,补间动画就不行呢?
View 在 draw 的时候,会检测是否设置了 Animation (补间动画),
如果有的话,会获取这个动画当前的值(旋转或位移或缩放,透明度等),应用到 canvas 上,然后把东西 draw 出来。
比如设置了位移动画,当前值是向右移动了100,那么效果就等于这样:
Matrix matrix = new Matrix();
matrix.setTranslate(100, 0);
canvas.setMatrix(matrix);
它的作用只会在 draw 的时候有效。
虽然大家都是操作 Matrix,但是 Matrix 的对象不一样,所以在 ViewGroup 筛选的时候,应用属性动画的 View 会被正确找到,而补间动画的不行。
属性动画和补间的 Matrix 又什么区别?
属性动画
属性动画所影响的Matrix,是在 View 的 mRenderNode
中 的 stagingProperties
里面的,这里的 Matrix,每个 View 之间都是独立的,所以可以各自保存不同的变换状态。
补间动画
而补间动画,它所操作的 Matrix,其实是借用了它父容器的一个叫 mChildTransformation
的属性(里面有 Matrix ),通过 getChildTransformation
获得。
也就是说,一个 ViewGroup 中,无论它有几个子 View 都好,在这些子 View 播放补间动画的时候,都是共用同一个 Transformation 对象的(也就是共用一个 Matrix ),这个对象放在 ViewGroup 里面。
你也许会想: 共用?不可能吧,那为什么可以同时播放好几个动画,而互相不受影响呢?
是的,在补间动画更新每一帧的时候,父容器的 mChildTransformation
里面的 Matrix ,都会被 reset。
你也许会想又会想:每次重置 Matrix 难道不会受到影响吗?
每次重置 Matrix 而不受影响的原因:
是因为这些补间动画,都是基于当前播放进度,来计算出绝对的动画值并应用的,保存旧动画值是没有意义的。
就拿位移动画 TranslateAnimation 来说,比如它要向右移动 500,当前的播放进度是 50%,那就是已经向右移动了 250,在 View 更新帧的时候,就会把这个向右移动了 250 的 Matrix 应用到 Canvas 上,当下次更新帧时,比如进度是 60%,那计算出来的偏移量就是 300 ,这时候,已经不需要上一次的旧值 250 了,就算 Matrix 在应用前被重置了,也不影响最后的效果。
总结
属性动画有两个非常重要的类:ValueAnimator
类 & ObjectAnimator
类,二者的区别在于:
ValueAnimator
类是先改变值,然后 手动赋值 给对象的属性从而实现动画;是 间接 对对象属性进行操作;而 ValueAnimator
类本质上是一种 改变值 的操作机制。
ObjectAnimator
类是先改变值,然后 自动赋值 给对象的属性从而实现动画;是 直接 对对象属性进行操作;可以理解为:ObjectAnimator
更加智能、自动化程度更高。
而补间动画的核心本质就是在一定的持续时间内,不断改变 Matrix 变换,并且不断刷新的过程。