公子姓王

导航

为什么属性动画可以响应触摸事件

关于Android 的动画及原理,可以参考:

逐帧动画(AnimationDrawable)

View动画,插值器(Animation)

属性动画浅谈(Animator)

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等绘图基础.

posted on 2020-02-16 15:18  公子姓王  阅读(620)  评论(0编辑  收藏  举报