Android View层级的事件分发体系——源码探究

本文深入探讨Android View的事件分发体系,包括Scroller与OverScroller,事件分发的三个关键方法:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的工作原理,以及如何处理滑动冲突。讲解了ViewGroup与View在事件分发中的角色,特别是FLAG_DISALLOW_INTERCEPT的作用,并分析了滑动冲突的常见场景和解决策略。
摘要由CSDN通过智能技术生成

“View 层级的事件分发,只是应用整体的事件分发的一小部分”

参考:
《Android 开发艺术探索》
反思 | Android 事件分发机制的设计与实现 【掘金】
ConstraintLayout详解【简书】(本文没参考,列一下,方便后续复习)

1. Scroller

scroller 本身无法实现滑动,它实际是一个保存滑动配置数据的类。

1.5 OverScroller(Scroller太旧了)

OverScroller 和 Scroller 没有继承或者组合关系,它们是独立的两个类,只是OverScroller 实现更加优化,同时它增加了一个Over的能力,就是回弹。

之前这篇文章是系统分析了一下Scroller的,但是感觉没有必要,而且讲的也不明白,所以先删掉吧。

2. View的事件分发机制

事件分发相关的三个主要方法:

public boolean dispatchTouchEvent(MotionEvent ev):
   这个方法用来进行事件的分发。如果事件传递给当前view,则调用此方法。返回结果表示是否消耗此事件,受onTouchEvent和下级View的dispatchTouchEvent方法影响。

public boolean onInterceptTouchEvent(MotionEvent ev):
   这个方法用来判断是否拦截事件。在dispatchTouchEvent方法中调用。返回结果表示是否拦截。

public boolean onTouchEvent(MotionEvent ev):
   这个方法用来处理点击事件。在dispatchTouchEvent方法中调用,返回结果表示是否消耗事件。如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

三个主要方法之间的关系

伪代码描述

// 伪代码描述,只代表三者间的关系,实际这是一个递归的调用
public boolean dispatchTouchEvent(MotionEvent event) {
   
   boolean isConsume = false;
   if (onInterceptTouchEvent(event)) {
   
       isConsume = onTouchEvent(event);
   } else {
   
       isConsume = child.dispatchTouchEvent(event);
   }
   return isConsume;
}

实质概括(大意):
  所谓的事件分发,在view这个层级(View的事件分发实际只是Android事件机制的一部分,前置的事件处理可以看另一篇文章:《Android 应用整体的事件分发体系》),就是一个递归调用的过程。
  dispatchTouchEvent :这个方法,就是递归方法体;
  onInterceptTouchEvent :控制的是否要拦截不进入下一层次的递归,直接在本层结束递归;
  onTouchEvent:则是递归的出口,终点,从最底层的View的 onTouchEvent 会一路判断然后直到最外层的 ViewGroup 的 onTouchEvent,如果没有 View 的 onTouchEvent 选择处理这个点击事件,那么事件最终会交给 Activity 处理;

3. View事件分发源码分析

ViewGroup.dispatchTouchEvent()

FLAG_DISALLOW_INTERCEPT:
  这个标记是由 View 通过 requestDisallowDispatchTouchEvent() 这个方法来控制的,子View可以通过这个方法来控制父View的拦截,一旦设置了这个变量,那么父View将不能拦截除DOWN事件之外的其他事件(DOWN事件会重置这个标志位)。

// Handle an initial down.
// down 事件触发重置标志位
if (actionMasked == MotionEvent.ACTION_DOWN) {
   
    // Throw away all previous state when starting a new touch gesture.
    // The framework may have dropped the up or cancel event for the previous gesture
    // due to an app switch, ANR, or some other state change.
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

  判断当前ViewGroup是否需要拦截此次的事件。其中,如果ViewGroup确定了由哪个View来消费事件,mFirstTouchTarget 就会指向目标 View,它的值就不会为空了。

 // Check for interception.
 final boolean intercepted;
 
 // down 事件必然触发拦截的判断,
 // 如果不是自己消费事件,而是继续传递,那么也可能触发拦截判断,
 // 具体还要看 FLAG_DISALLOW_INTERCEPT 这个标志位的值
 if (actionMasked == MotionEvent.ACTION_DOWN
         || mFirstTouchTarget != null) {
   
     final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
     if (!disallowIntercept) {
   
         intercepted = onInterceptTouchEvent(ev);
         ev.setAction(action); // restore action in case it was changed
     } else {
   
         intercepted = false;
     }
 } else {
   
     // There are no touch targets and this action is not an initial down
     // so this view group continues to intercept touches.
     intercepted = true;
 }

  判断完拦截事件相关的逻辑之后,到了具体分发事件的地方。根据坐标找到有触摸事件发生的View,然后判断动画等状态之后,注意 dispatchTransformTouchEvent() 这个方法,它里面会调用 View 的 dispatchTouchEvent() 从而实现事件从ViewGroup 到 View 的传递。

final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
   
            final float x = ev.getX(actionIndex);
            final float y = ev.getY
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值