为什么属性动画可以响应触摸事件
关于Android 的动画及原理,可以参考:
属性动画与 VIew动画的关键区别之一,就是 属性动画 view 可以在动画过程中收到点击事件,而 view 动画过程中,点击事件仍在原地;基于此,很多人认为属性动画改变了 view 的属性,导致view 的位置发生了变化,所以属性动画可以收到点击事件,这种说法其实是错误的;
为什么如此说呢,我们这里给一个例子:
ObjectAnimator.ofFloat(v, "translationX", 0, 1000f)
.setDuration(1000)
.start();
在开发者选项中,我们打开显示布局位置:
运行如下:
发现上面显示的 view 的位置并没有发生改变;
那么上面动画到底做了什么呢,我们都知道,上面动画会调用 View 的 setTranslationX 方法,我们来看源码:
public void setTranslationX(float translationX) {
if (translationX != getTranslationX()) {
invalidateViewProperty(true, false);
// Sets the translation value for the display list on the X axis.
// 在display list 上设置了 translationX
mRenderNode.setTranslationX(translationX);
// 这里的invalidateViewProperty会调用invalidate方法,也就是会导致 view 重绘,但是,并不会导致 view 的位置发生改变(没有 onLayout 调用)
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
上面可以看出,并没有改变 view 位置,而是在display list 上设置了 translationX
关于 display list,这个就有些复杂了,暂不分析
那么还有一个问题需要 解决,就是为什么能收到点击事件,这就需要看父布局(ViewGroup)如何判断一个触控坐标是否在子 view 范围内了,在 ViewGroup 的 dispatchTouchEvent 事件中,有如下源码 :
if (!canViewReceivePointerEvents(child)// 判断是否可见,是否有动画(View 动画,即 Animation)正在播放
|| !isTransformedTouchPointInView(x, y, child, null)// 判断坐标是否在 child 内
) {
continue;
}
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
final float[] point = getTempPoint();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);// 将触摸事件的坐标点按 child 中的相反矩阵转换
final boolean isInView = child.pointInView(point[0], point[1]);// 判断转换后的点是否在 view 的位置内
if (isInView && outLocalPoint != null) {
outLocalPoint.set(point[0], point[1]);
}
return isInView;
}
public void transformPointToViewLocal(float[] point, View child) {
point[0] += mScrollX - child.mLeft;
point[1] += mScrollY - child.mTop;
if (!child.hasIdentityMatrix()) {// 如果设置了 translationX,这里肯定为 true
child.getInverseMatrix().mapPoints(point);// 将触控坐标点做相反矩阵计算,这里是核心
}
}
final boolean hasIdentityMatrix() {
return mRenderNode.hasIdentityMatrix();
}
上面可以看出,这里在判断子view 是否可以收到触控事件时,做了触控矩阵转换,如果转换后的触控位置在子view 范围内,则认为可以收到触控事件;
上面涉及到矩阵 Matrix 类,很多人可能不是很清楚这个,关于这些,可能需要读者自行学习,打开 Matrix 源码,我们可以看到这样一些方法:
public void setRotate(float degrees)
public void setScale(float sx, float sy)
public void setTranslate(float dx, float dy)
其实,在安卓中,关于 View 方面,很多平移,旋转,放大缩小,都是通过Matrix 做坐标变换来完成的,包括 Canvas,Paint等绘图基础.