属性动画是如何在移动时响应用户点击事件的?

属性动画是如何在移动时响应用户点击事件的?

也就是说,我们在使用了属性动画之后,该 View 依然可以正确地接收到事件的分派。

ViewGroup 是如何找到这个 View 的

我们知道,调用 View 的 translationXX 方法之后,虽然在屏幕上的位置是变了,但是它的 [left,top,right,bottom] 是不会变的。

来捋一遍 ViewGroup 分派事件的大致流程:

当手指按下时,触摸事件会经过 ViewGroup 中的 dispatchTouchEvent方法筛选符合条件(手指在边界范围内)的子 View 进行分派事件(如果未被 onInterceptTouchEvent拦截的话)。

那么,如果某个子 View 刚好应用了 translation 属性动画,在 ViewGroup 筛选子 View 时,直接判断触摸点是否在 [left,top,right,bottom] 范围内,是肯定不行的。

那它是怎么判断的呢?

  1. 它会先调用子 View 的 hasIdentityMatrix方法来判断这个 View 是否应用过位移、缩放、旋转之类的属性动画。

  2. 如果应用过的话,那接下来还会把触摸点映射到该子 View 的逆矩阵上( getInverseMatrix)。

  3. 判断处理后的触摸点,是否在该子 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 变换,并且不断刷新的过程。

讨论数量: 0

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!