登录

去注册

登录

注册

去登录

注册

每日一问 | 为什么属性动画移动一个控件后,目标位置仍然能响应用户事件?

xiaoyang   2019-06-27   收藏

还可以聊一下属性动画原理,和别的动画的区别~


本周2/3。

12

为什么属性动画移动一个控件后,目标位置仍然能响应用户事件?

也就是说,应用了属性动画之后,该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的边界范围内。


那么为什么只有属性动画可以这样,补间动画就不行呢?

View在draw的时候,会检测是否设置了Animation(补间动画),
如果有的话,会获取这个动画当前的值(旋转或位移或缩放,透明度等),应用到canvas上,然后把东西draw出来。
比如设置了位移动画,当前值是向右移动了100,那么效果就等于这样:

Matrix matrix = new Matrix();
matrix.setTranslate(100, 0);
canvas.setMatrix(matrix);

它的作用只会在draw的时候有效。

虽然大家都是操作Matrix,但是Matrix的对象不一样(属性动画操作的Matrix,是View的mRenderNode所对应的Matrix),
所以在ViewGroup筛选的时候,应用属性动画的View会被正确找到,而补间动画的不行。

回复
nanchen2251 : @陈小缘 

为此,我专门测试了一下覆盖的情况,确实如此。

2019-06-26 回复
2

补充 & 回复@15023822235

差不多,属性动画所影响的Matrix,是在View的mRenderNode中的stagingProperties里面的,这里的Matrix,每个View之间都是独立的,所以可以各自保存不同的变换状态。
而补间动画,它所操作的Matrix,其实是借用了它父容器的一个叫mChildTransformation的属性(里面有Matrix),通过getChildTransformation获得。
也就是说,一个ViewGroup中,无论它有几个子View都好,在这些子View播放补间动画的时候,都是共用同一个Transformation对象的(也就是共用一个Matrix),这个对象放在ViewGroup里面。

有同学可能会问:共用?不可能吧,那为什么可以同时播放好几个动画,而互相不受影响呢?

是的,在补间动画更新每一帧的时候,父容器的mChildTransformation里面的Matrix,都会被reset。

每次重置Matrix而不受影响的原因:

是因为这些补间动画,都是基于当前播放进度,来计算出绝对的动画值并应用的,保存旧动画值是没有意义的。
就拿位移动画TranslateAnimation来说,比如它要向右移动500,当前的播放进度是50%,那就是已经向右移动了250,在View更新帧的时候,就会把这个向右移动了250的Matrix应用到Canvas上,当下次更新帧时,比如进度是60%,那计算出来的偏移量就是300,这时候,已经不需要上一次的旧值250了,就算Matrix在应用前被重置了,也不影响最后的效果。

回复
2

属性动画的工作原理很简单,其实就是在一定的时间间隔内,通过不断地对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在属性上的动画效果。这个属性可以是任意对象的属性。

属性动画有两个非常重要的类:`ValueAnimator` 类 & `ObjectAnimator` 类,二者的区别在于:
`ValueAnimator` 类是先改变值,然后 **手动赋值** 给对象的属性从而实现动画;是 **间接** 对对象属性进行操作;而 `ValueAnimator` 类本质上是一种 **改变值** 的操作机制。

`ObjectAnimator` 类是先改变值,然后 **自动赋值** 给对象的属性从而实现动画;是 **直接** 对对象属性进行操作;可以理解为:`ObjectAnimator` 更加智能、自动化程度更高。


而补间动画的核心本质就是在一定的持续时间内,不断改变 Matrix 变换,并且不断刷新的过程。

对于属性动画移动后,为啥在目标位置还可以响应事件的问题,我也是看了小缘的回答,才去爬了一波代码,哈哈哈,又涨姿势了。

回复
1

属性动画最后会调用如下代码:

@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;
}

会把控件的位置重新摆放

回复
0

小缘牛逼

大概意思就是

属性动画做出的变换是存在view上面的,可以在触摸事件的时候逆向检查

补间动画做出的变化是直接在画布上的,变换了就变化了,没有存值,导致group并不知道儿子的新位置?所以无法反向查找view判断事件分发区域?

回复

删除留言

确认删除留言,会导致相关评论丢失?

取消 确定