【view】- 触摸事件分发(2)

作者: 拔萝卜占坑 | 来源:发表于2020-03-27 16:17 被阅读0次

简介

上一篇文章【view】- 触摸事件分发(1)讲解了从底层到上层的触摸事件传递。这篇文章将具体Activity,View组件的触摸事件的传递,以及事件的分类,拦截,不同的处理会引起触摸事件流程那些变化等。

注意

如果对源码分析不感兴趣,只想知道结论,可以直接翻到文章最后看总结,如果想了解源码实现,可以参照这篇文章,自己追踪源码。

Activity

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();}
    if (getWindow().superDispatchTouchEvent(ev)) {return true;}
    return onTouchEvent(ev);
}

如果是按下事件,在进行事件分发前调用onUserInteraction()方法,在Activity该方法是没有任何实现,我们可以重新该方法。

getWindow().superDispatchTouchEvent(ev)会把触摸事件交给整个View组件之间传递,如果返回true,在Activity中触摸事件传递结束,如果反复false者调用onTouchEvent(MotionEvent event)方法。

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

Activity中逻辑很简单,下面讲解在DecorView顶层View之间的触摸事件传递。

DecorView

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

调用父类ViewGroup中的dispatchTouchEvent(MotionEvent ev)

辅助功能,残障。

if (mInputEventConsistencyVerifier != null) {
    mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
    ev.setTargetAccessibilityFocus(false);
}

如果窗口是被遮盖,不可见,onFilterTouchEventForSecurity返回false,放弃这次触摸事件的处理。

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
    if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
            && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
        // Window is obscured, drop this touch.
        return false;
    }
    return true;
}

如果是第一次按下操作,清除所有以前的状态,由于应用程序切换,ANR或某些其他状态更改,框架可能已放弃上一个手势的上移或取消事件,所以进行一些清理操作。

if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

int类型mGroupFlags变量的初始化,我把常量换成具体的值

rivate void initViewGroup() {
   ...
   mGroupFlags |= 0x1; 
   mGroupFlags |= 0x4;
   mGroupFlags |= 0x10
   mGroupFlags |= 0x40
   mGroupFlags |= 0x4000
   if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
       mGroupFlags |=  0x200000
   }
   setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
   ...
public void setDescendantFocusability(int focusability) {
   ...
    mGroupFlags &= ~0x60000;
    mGroupFlags |= (0x20000 & 0x60000);
}

检查拦截,如果是MotionEvent.ACTION_DOWN或者mFirstTouchTarget不等于null,调用onInterceptTouchEvent方法,判断当前View是否要要拦截触摸事件。

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;
}

如果被拦截,开始正常事件分发。另外,如果已经有一个正在处理手势的视图,则进行常规事件调度。

if (intercepted || mFirstTouchTarget != null) {
    ev.setTargetAccessibilityFocus(false);
}

如果事件没有撤销并且没有拦截,进入if代码块,把触摸事件分发给子控件。

获取手指索引,从注释看出,对于按下操作actionIndex ==0,单指操作。

final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

对子控件进行排序,因为控件会存在相互叠加的部分,优先顶层

 final ArrayList<View> preorderedList = buildTouchDispatchChildList();

从顶层View,也就是最先收到触摸事件的View向下遍历,获取子控件。

for (int i = childrenCount - 1; i >= 0; i--) {
    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
    ...    
}

将不可见,在动画中,手指在控件之外不能接收触摸事件的控件过滤掉

if (!canViewReceivePointerEvents(child)
        || !isTransformedTouchPointInView(x, y, child, null)) {
    ev.setTargetAccessibilityFocus(false);
    continue;
}

将触摸事件分发给符合要求的子控件。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

在dispatchTransformedTouchEvent方法中,如果触摸事件已经取消或者撤销了,那么调用父类dispatchTouchEvent方法。

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
}

分发事件给子控件,调用子控件的dispatchTouchEvent方法

if (newPointerIdBits == oldPointerIdBits) {
    ...
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            ...
            handled = child.dispatchTouchEvent(event);
            ...
        }
        return handled;
    }
   ...
}

如果子控件没有重写这个方法,那么会调用到View的dispatchTouchEvent方法。接下来分析View中的dispatchTouchEvent方法。

如果是按下操作,停止嵌套滚动

if (actionMasked == MotionEvent.ACTION_DOWN) {
    stopNestedScroll();
}

调用控件的onTouchEvent方法

if (!result && onTouchEvent(event)) {
    result = true;
}

看一下View中的onTouchEvent方法。

如果设置了委托触摸事件处理实例,者直接调用委托的onTouchEvent。

if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {return true;}
}

执行控件的点击事件监听回调中的onClick方法。

if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
    ...
        if (mPerformClick == null) {mPerformClick = new PerformClick();}
        if (!post(mPerformClick)) {performClickInternal();}
    }
}

View中的onTouchEvent方法剩余的逻辑自己分析,不在讲解。

回调ViewGroup中的dispatchTouchEvent方法中。如果dispatchTransformedTouchEvent方法返回true,代表被子空间或者父类处理了触摸事件,那么退出循环,不在分发事件给下一个子控件。

总结

下面容器以ViewGroup为中dispatchTouchEvent讲解,默认其它容器没有重写该容器里的方法。

ACTION_DOWN
  • 第一次触发down事件

    1. 如果当前触摸事件窗口是被遮盖(比如有其它可见窗口遮挡在上面),即onFilterTouchEventForSecurity返回false,那么丢弃该次触摸事件处理,handled=false,返回handled。把事件交给Activity处理。

    2. onFilterTouchEventForSecurity返回true,即当前控件可以对触摸事件进行处理。
      当前控件是否允许拦截触摸事件。

      boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      

      没有其它设置的话,此时mGroupFlags值是0x244053,&0x80000等于0,所以disallowIntercept值是false,表示ViewGroup默认允许拦截触摸事件,但是需要询问ViewGroup的onInterceptTouchEvent是否可以拦截,如果不重写该方法,返回false,依旧表示ViewGroup不拦截事件。

      正常情况下,变量canceled = false,split = true。遍历容器中所有的子控件,将触摸事件分发给子控件。在分发dispatchTransformedTouchEvent方法中,如果传入的View是null,者调用ViewGroup的父类的dispatchTouchEvent,如果不是null,调用子控件的dispatchTouchEvent,并返回dispatchTouchEvent返回的值。

      如果子控件中有消耗了触摸事件(即dispatchTouchEvent返回true),者停止遍历,后面的子控件将接收不到该触摸事件并创建TouchTarget对象target,将消耗事件的View和手指编号存在这个target中,且target.next = mFirstTouchTarget,将新创建的target赋值给mFirstTouchTarget并返回target。

      这个时候其实newTouchTarget和mFirstTouchTarget是同一个实例对象,且alreadyDispatchedToNewTouchTarget赋值为true。

      根据上面,可以得知handled被直接赋值为true,而下一个target是null,while循环终止。

      最后直接返回handled值。

      如果子控件没有消耗,后面的逻辑更简单,直接交给容器的父类处理。

      如果容器一开始要拦截该触摸事件呢?即intercepted被赋值为true,那么mFirstTouchTarget也会为null,直接将事件交给父类处理,返回父类处理的结果。

      • 父类处理触摸事件
        这里ViewGroup的父类是View。首先停止嵌套滚动,如果View符合接收触摸事件的要求,调用View的onTouchEvent方法,如果消耗了事件,result赋值true,然后返回result值。

        onTouchEvent方法中,如果View的可点击属性没有使能,直接返回clickable(是否点击使能)值。

        如果设置的委托事件处理实例,将触摸事件交给委托实例,然后直接返回true。

        后面的逻辑就会执行点击事件,回调点击事件监听方法,包括点击,长按等,最后返回true。所以有些时候,重写了onTouchEvent发现点击事件不能回调问题。

  • 第二次触发down事件
    比如在界面滑动等,可能会触发第二次down,第二次由于

    newTouchTarget = getTouchTarget(child);
    

    返回不为null,所以会直接终止循环。

    if (newTouchTarget != null) {
      ...
      break;
    }
    
ACTION_UP

按下并松开手指,这时候触发ACTION_UP事件。通过上面的分析这时候,如果ViewGroup在ACTION_DOWN不拦截,那么mFirstTouchTarget不等于null,那么不管现在ViewGroup是否拦截,不好意思,我都只会把触摸事件交给上一个消耗了ACTION_DOWN事件的View,如果都不消耗,我给Activity。

总结:如果你连ACTION_DOWN事件都不消耗,那么后面的事件序列你也别想消耗。

相关文章

  • Android 触摸事件分发

    触摸事件分发 几个重要的方法 触摸事件分发:定义在View中 @Override public boolean d...

  • 【view】- 触摸事件分发(2)

    简介 上一篇文章【view】- 触摸事件分发(1)讲解了从底层到上层的触摸事件传递。这篇文章将具体Activity...

  • View事件分发机制

    前言 Android源码分析之View系列之事件分发机制 同步至个人博客 正文 一. 概述 View的触摸事件分发...

  • Android触摸事件分发机制(2)之ViewGroup

    Android触摸事件分发机制(1)之View 上一篇文章我们分析了View的事件分发机制,今天我们分析下View...

  • Android 触摸事件传导流程

    一、简介 Android 触摸事件的分发,其实就是将一个触摸事件从顶层 View 传递到底层 View,再传递到顶...

  • Android 事件分发机制-ViewGroup篇

    触摸事件分发机制学习 -ViewGroup篇 在上一篇事件分发机制的学习-View篇基本算是逐行解析了单独view...

  • Android 事件分发

    Android 事件分发: 一、事件分发: 事件:当触摸View ViewGroup派生的控件后,将会触发一系列的...

  • Android事件分发机制

    基础知识 事件分发的对象 事件分发的对象是 点击事件(Touch事件),当用户触摸屏幕(View或者ViewGro...

  • 安卓事件分发机制

    ##事件分发原理 1 事件分发的对象是谁? 答:事件 当用户触摸屏幕时(View或ViewGroup派生的控件),...

  • Activity事件分发

    1.View的事件分发2.ViewGroup的事件分发3.Activity的事件分发 在View的事件分发最后有个...

网友评论

    本文标题:【view】- 触摸事件分发(2)

    本文链接:https://www.haomeiwen.com/subject/eythuhtx.html