2025-03-27

Android 动画的实现原理

Android 动画的实现原理主要涉及视图刷新机制、时间插值和属性计算,其核心是通过不断修改视图属性(如位置、透明度等)并触发重绘来实现动态效果.

  • 视图动画(View Animation)
    仅影响视图的绘制结果,不改变视图的实际位置或大小(例如点击事件仍停留在原位置)。
  • 属性动画(Property Animation)----实际修改属性值(如 view.setTranslationX()),影响视图布局和交互。
    原理:
    通过 ValueAnimator 或 ObjectAnimator 动态修改对象的任意属性(包括自定义属性),
    利用反射或 PropertyValuesHolder 实现属性值的连续变化。
    核心类:
    ValueAnimator:计算动画过程中的值,需手动更新属性。
    ObjectAnimator:自动更新目标对象的属性(如 View 的 alpha、translationX)。
    AnimatorSet:组合多个动画。
    关键机制:时间插值(Interpolator),类型估值器(TypeEvaluator)
    底层原理:

动画的每一帧由 Choreographer 调度,通过 VSYNC 信号同步屏幕刷新率(通常 60Hz)。
ValueAnimator 在每帧回调中计算新值,并触发视图重绘(invalidate())。

事件的分发机制

Android 的事件分发机制负责将用户触摸事件(MotionEvent)传递到正确的 View 或 ViewGroup,
其核心流程涉及 事件传递顺序、拦截逻辑 和 消费机制。

  1. 事件分发的三个核心方法
方法 作用 返回值意义
dispatchTouchEvent 事件分发入口,负责将事件传递给子视图或自身处理。 true:事件被消费;false:未消费,继续向上传递。
onInterceptTouchEvent 仅 ViewGroup 有,判断是否拦截事件。 true:拦截事件,不再下发子视图;false:继续下发。
onTouchEvent 处理事件(如点击、滑动)。 true:消费事件;false:未消费,向上传递。

dispatchTouchEvent:方法事件,当点击事件传给当前view时,这个方法被调用;
onInterceptTouchEvent:在dispatchTouchEvent()内部调用,判断是否拦截该事件;
onTouchEvent: 在dispatchTouchEvent()内部调用。处理点击事件。

Android中事件分发顺序:Activity(Window) -> ViewGroup -> View

事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成


image.png

dispatchTouchEvent 方法事件: View,ViewGroup都有。
View 只会把事件分发到View.onTouchEvent();
ViewGroup 有一套分发的标准--onInterceptTouchEvent()。
onInterceptTouchEvent拦截方法分发。 ViewGroup独有的。拦截了,其子类就收不到事件。
intercepted = true; // 没有分发路径,且也不是Down事件,直接拦截掉。
onTouchEvent 只有一个view,响应down事件,形成单链表下发后序move/up事件。move事件根据单链表分发事件。

  1. 事件分发流程
    DOWN 事件流程(后续 MOVE/UP 事件流程依赖 DOWN 事件的分发结果)
    当一个点击事件发生后,系统需要将这个事件传递给一个具体的view去处理。这个事件传递的过程就是分发过程。

a。Activity → Window → DecorView
事件首先由 Activity.dispatchTouchEvent() 传递到 Window(通常是 PhoneWindow),再转到顶层 DecorView(根 ViewGroup)。

b。自上而下传递(ViewGroup)
从父 ViewGroup 到子 View,依次调用:
parent.dispatchTouchEvent()
→ parent.onInterceptTouchEvent() // 判断是否拦截
→ child.dispatchTouchEvent() // 若不拦截,继续下发
→......
c。自下而上处理(View)

若子 View 的 onTouchEvent() 返回 false(未消费),事件会回溯到父 ViewGroup 的 onTouchEvent()。

d。最终处理
若所有 View 均未消费事件,最终由 Activity.onTouchEvent() 处理。

MOVE/UP 事件流程

若 DOWN 事件被某个 View 消费,后续事件(MOVE、UP)会直接dispatchTouchEvent分发给该 View,不再经过 onInterceptTouchEvent 判断(除非手动干预)。

3.关键规则

拦截机制:

  • ViewGroup 可通过 onInterceptTouchEvent() 拦截事件(如 ScrollView 在滑动时拦截子 View 的点击事件)。
  • 一旦拦截,该事件序列(DOWN 到 UP)后续事件均直接交给该 ViewGroup 的 onTouchEvent()。

消费优先级:
View.onTouchListener > View.onTouchEvent() > View.onClickListener。
若 onTouchListener 返回 true,则 onTouchEvent 和 onClick 不会被触发。

事件序列完整性:
若 View 未消费 DOWN 事件,后续事件(MOVE/UP)不会再传递给它。

4.底层机制
InputEventReceiver:

系统通过 ViewRootImpl 接收底层输入事件,并通过 Choreographer 同步到主线程。
事件传递优化:
Android 使用 TouchTarget 链表记录能接收事件的 View,避免重复遍历视图树。

  1. 总结:
    核心思想:责任链模式,事件从顶层逐步下发,再由底层决定是否消费。
    优化点:
    避免在 onTouchEvent 中阻塞主线程。
    合理使用 requestDisallowInterceptTouchEvent() 解决滑动冲突。
    理解事件分发机制有助于自定义复杂交互(如嵌套滚动、手势冲突)并优化性能。

onIntercepterEvent Down事件。拦截事件分发的遍历顺序:根,右,左。与先序遍历相反。
先遍历根节点,再从右边开始,最后遍历左边的子树。

Down事件,走树的遍历分发。
move事件:Down事件返回true,会形成单链表,单链表的遍历比树的遍历效率高。后序的move/up 事件,通过这个单链表分发路径分发。

Cancel事件:系统内部做的,清除链表(分发路径),非人为原因结束本次事件。
滑到屏幕底部,拦截掉事件后(onIntercepterEvent() return true),
在拦截的位置,生成Cancel事件,交由链表。链表下的所有元素都能收到Cancel事件。收到cancel事件后,会清空链表关系。分发路径就没了。
只有的move/up事件,只能分配到根节点vp1.

假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。

  • 调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
  • 该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()。该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。

假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。

  • DOWN事件传递到C的onTouchEvent方法,返回了true。
  • 在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法。
  • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()。
    后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。

4.1.2 汇总
当一个点击事件发生时,调用顺序如下

1.事件最先传到Activity的dispatchTouchEvent()进行事件分发
2.调用Window类实现类PhoneWindow的superDispatchTouchEvent()
3.调用DecorView的superDispatchTouchEvent()
4.最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()

View的dispatchTouchEvent()的源码分析

public boolean dispatchTouchEvent(MotionEvent event) {

if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
    return true;
}
return onTouchEvent(event);
}

onTouch()的执行高于onClick()
当控件被点击,onTouch()返回false(该事件没被onTouch()消费掉) = dispatchTouchEvent()返回false(继续向下传递) = 执行onTouchEvent() = 执行OnClick()

onTouch()和onTouchEvent()的区别

这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。

dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)
而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容