View事件分发

177 阅读4分钟

梳理后的Android点击事件分发流程

在Android中,点击一个View时,事件分发遵循一个类似“U”字型的流程:

1. 事件产生与初始分发

  • 事件产生:用户触摸屏幕,Android系统生成MotionEvent对象。
  • 初始接收Activity作为用户交互的顶层容器,首先接收到这个事件。

2. Activity中的事件传递

  • 分发方法Activity通过dispatchTouchEvent方法开始分发事件,View树的根容器DecorView是viewgroup
  • 传递对象:在dispatchTouchEvent中,事件通常被传递给Window对象(默认实现PhoneWindow,关联DecorView,一个ViewGroup)。

3. ViewGroup的事件分发机制

  • 接收与分发DecorView(或任何ViewGroup)接收到事件后,调用dispatchTouchEvent
  • 拦截判断:在分发前,通过onInterceptTouchEvent判断是否拦截事件。
    • 拦截:如果返回true,则ViewGroup拦截事件,由自己的onTouchEvent处理。
    • 不拦截:如果返回false或未重写该方法(默认false),则事件传递给子View
  • 回溯机制:若子View未消费事件(onTouchEvent返回false),事件回溯到ViewGrouponTouchEvent

4. View的事件处理

  • 处理事件View接收到事件后,通过onTouchEvent处理。
  • 消费与回溯
    • 消费:如果onTouchEvent返回true,表示事件被消费,分发流程结束。
    • 未消费:如果返回false,事件回溯到父ViewGrouponTouchEvent

5. 事件回溯过程

  • 回溯机制:未消费的事件从子View回溯到父ViewGroup
  • 持续回溯:这个过程一直进行,直到事件被某个ViewViewGroup消费,或回到DecorView的最顶层。

6. Activity的最后处理机会

  • 调用onTouchEvent:如果事件最终未被任何ViewViewGroup消费,ActivityonTouchEvent方法会被调用。
  • 处理或忽略Activity可以选择处理这个事件或忽略它。

常用问题解决

  1. 滑动冲突

    • 问题:如ListView嵌套在ScrollView里面时,两者都试图处理滑动事件。
    • 解决
      • 重写外部容器ViewGrouponInterceptTouchEvent方法,根据滑动方向或逻辑判断是否拦截事件。
      • 使用NestedScrollView代替ScrollView,它支持嵌套滚动
      • 如果确实需要在滚动视图中嵌入列表,并且列表也需要滚动,考虑使用RecyclerView代替ListView
  2. 点击事件穿透

    • 问题:一个不可见View上方的可点击View被点击时,触发了不可见View的点击事件。
    • 解决:调整布局结构或确保不可见View不参与事件分发。
  3. 自定义View的事件处理

    • 问题:需要自定义View的触摸事件处理,如拖拽、缩放等。
    • 解决:重写dispatchTouchEventonTouchEvent和设置OnTouchListener来精确控制事件处理。

两种处理方向:

从子视图处理

从子视图处理滑动冲突,一般在子视图的onTouchEvent方法中实现滑动逻辑,并在必要时通过 调用`getParent().requestDisallowInterceptTouchEvent(true);来阻止父视图拦截滑动事件。 这好处是子视图对自己的滑动行为有完全的控制权,但需要注意判断何时应该允许父视图拦截事件,何时应该自己处理。

从父视图处理

从父视图处理滑动冲突,则是在父视图的onInterceptTouchEvent方法中实现。 这个方法允许父视图在事件传递给子视图之前决定是否要拦截该事件。 如果父视图决定拦截事件,那么它会返回true,并在onTouchEvent中处理滑动。 这好处是父视图可以全局地控制滑动行为,但需要了解子视图的滑动状态,在适当的时候拦截或取消拦截。

实际应用

在实际应用中,你可能会结合使用这两种方法。例如,你可能在父视图的onInterceptTouchEvent中根据一些条件(如滑动方向、子视图的滚动位置等)来决定是否拦截事件,同时在子视图的onTouchEvent中处理具体的滑动逻辑。

此外,随着Android版本的更新,一些新的API和类(如NestedScrollingChildNestedScrollingParent等)被引入来更好地支持嵌套滚动的场景。这些API提供了一套标准的机制来处理滑动冲突,使得开发者可以更容易地实现复杂的滑动效果。因此,在开发过程中,也应该关注这些新的API和最佳实践。