View事件分发

1.事件的源头在哪里

Android系统中将输入事件定义为InputEvent,而InputEvent根据输入事件的类型又分为了KeyEvent和MotionEvent,前者对应键盘事件,后者则对应屏幕触摸事件,这些事件统一由系统输入管理器InputManagerService进行分发。在 SystemServer.startOtherServices中会启动 IMS(InputManagerService)和WMS(WindowManagerService),InputManagerService会开启InputReader线程读取输入事件,然后交给InputDispatcher线程去将事件派发到目标窗口,目标窗口的确定和窗口的状态,层级等相关,而所有窗口都是通过WindowManagerService添加的。

这里需要看一下Activity的启动流程:

  1. ActivityThread.performLaunchActivity() -> activity.attach(),Activity.attach()中进行了PhoneWindow初始化, 在PhoneWindow中会创建DecorView.
  2. ActivityThread.handleResumeActivity() 中会调用WindowManagerImpl.addView() ->WindowManagerGlobal.addView(),在WindowManagerGlobal.addView()中会创建一个ViewRootImpl实例,然后调用ViewRootImpl的setView函数,在setView()中会进行Input通道的创建;此外addView的最终逻辑是ViewRootImpl.setView(decorView), 也就是将decorView赋值给ViewRootImpl的变量mView,后面的事件分发也会用到这个mView。
//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
}



//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
            requestLayout();
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                 
                mInputChannel = new InputChannel();
            }
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            }...
            
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
            
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());
            }

WMS负责APP和IMS通信信道的建立,高版本的采用的都是Socket的通信方式,而比较旧的版本采用的是Pipe管道的方式。之后让InputManager将Input通信信道与当前的窗口ID绑定,这样就能知道哪个窗口用哪个信道通信了;
信道建立后还需要注册事件监听WindowInputEventReceiver用来接收事件,至此APP就可以收到InputEvent了。详细分析请查看十分钟了解Android触摸事件原理(InputManagerService)

View事件分发_第1张图片

2.事件是如何到达View的

App端与服务端建立了双向通信之后,InputManager就能够将产生的输入事件从底层硬件分发过来,Android提供了InputEventReceiver类,以接收分发这些消息:

public abstract class InputEventReceiver {
     // Called from native code.
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }
}

InputEventReceiver是一个抽象类,其默认的实现是将接收到的输入事件直接消费掉,因此真正的实现是ViewRootImpl.WindowInputEventReceiver类:

public final class ViewRootImpl {

  final class WindowInputEventReceiver extends InputEventReceiver {
    @Override
     public void onInputEvent(InputEvent event, int displayId) {
         // 将输入事件加入队列
         enqueueInputEvent(event, this, 0, true);
     }
  }
}

 ViewRootImpl.WindowInputEventReceiver,连带调用了enqueueInputEvent()->doProcessInputEvents-> deliverInputEvent(q) -> stage.deliver(q);

接下来就是对事件的分发了,设计者在这里使用了经典的 责任链 模式:对于一个输入事件的分发而言,必然有其对应的消费者,在这个过程中为了使多个对象都有处理请求的机会,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象串成一条链,并沿着这条链一直传递该请求,直到有对象处理它为止。

设计者针对事件分发的整个责任链设计了InputStage类作为基类,作为责任链中的模版,并实现了若干个子类,为输入事件按顺序分阶段进行分发处理:

abstract class InputStage {
        private final InputStage mNext;
          ···
    }      

类InputStage提供了职责链的模板,也提供了一系列onProcess、forward、finish、apply方法,其目的也不言而喻:提供子类进行扩展的便捷。

// ----------------- InputStage的子类 ----------------------------
// 将预先输入事件提供给视图层次结构。视图预处理输入法事件阶段,将输入法的事件派发到视图的树。
final class ViewPreImeInputStage extends InputStage {}
// 执行事后输入事件的早期处理。输入法早期处理阶段。
final class EarlyPostImeInputStage extends InputStage {}
// 将后期输入事件提供给视图层次结构。视图输入处理阶段,主要处理按键、轨迹球、手指触摸及一般性的运动事件,触摸事件的分发对象是View。
final class ViewPostImeInputStage extends InputStage {}
// 从未处理的输入事件执行新输入事件的合成。综合性的事件处理阶段,该类主要轨迹球、操作杆、导航面板及未捕获的事件使用键盘进行处理。
final class SyntheticInputStage extends InputStage {}
// 用于实现支持输入事件的异步和无序处理的输入流水线级的基类。
abstract class AsyncInputStage extends InputStage {}
// ----------------- AsyncInputStage的子类----------------------------
// 将预先输入事件提供给 NativeActivity。本地方法预处理输入法事件阶段,可用于实现类似adb 输入的功能。
final class NativePreImeInputStage extends AsyncInputStage
            implements InputQueue.FinishedInputEventCallback {}
// 将预先输入事件提供给视图层次结构。输入法事件处理阶段,处理一些输入法字符等。如果对输入的内容无法识别,则继续往下转发。
final class ImeInputStage extends AsyncInputStage
            implements InputMethodManager.FinishedInputEventCallback {}
// 将事后输入事件提交到 NativeActivity 本地方法处理阶段,则构建可延迟的重用队列,此时执行操作将会异步回调结果。
final class NativePostImeInputStage extends AsyncInputStage
            implements InputQueue.FinishedInputEventCallback {}    

然后下面就是责任链模式的拼装,是在ViewRootImpl.setView()函数中:

//ViewRootImpl#setView()
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);

mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
···  

最终构造成如下的职责链:mSyntheticInputStage --> viewPostImeStage --> nativePostImeStage --> earlyPostImeStage --> imeStage --> viewPreImeStage --> nativePreImeStage。

这说明ViewRootImpl.setView()函数非常重要,该函数也正是ViewRootImpl本身职责的体现:

  • 1.链接WindowManager和DecorView的纽带,更广一点可以说是Window和View之间的纽带;

  • 2.完成View的绘制过程,包括measure、layout、draw过程;

  • 3.向DecorView分发收到的用户发起的InputEvent事件。

其中ViewPostImeInputStage就是负责UI层事件分发的,判断如果是触摸事件,调用processPointerEvent(),然后内部调用mView.dispatchPointerEvent(), 在前面提到过,mView为DecorView,辗转调用到了DecorView.dispatchTouchEvent(),此时事件也就分发到了DecorView中;DecorView实际上就是Activity中Window的根布局。

private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
            //分发到View
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            ...
            return handled ? FINISH_HANDLED : FORWARD;
        }

3.事件在View中是如何分发的

现在来看事件在DecorView中是如何分发的:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

DecorView.dispatchTouchEvent 中会调用 Window.Callback 的 dispatchTouchEvent 方法, 我们再来看看这个 Window.Callback 实例是什么,mWindow是从DecorView构造函数中传过来的;

这里需要再看一下Activity的启动流程:

  1. ActivityThread.performLaunchActivity() -> activity.attach(),Activity.attach()中进行了PhoneWindow初始化,并设置CallBack对象。
  2. ActivityThread.handleResumeActivity() 中通过PhoneWindow获取DecorView,在PhoneWindow初始化DecorView时,将window自身传给了DecorView,因此DecorView中的mWindow就是PhoneWindow。
//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          ...
            if (activity != null) {
               ...
              //可以看到此处调用了Activity的attach()方法
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
            ...
            }
}

//Activity.java
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //PhoneWindow初始化
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        //设置callback为activity自身
        mWindow.setCallback(this);
        ...
}

PhoneWindow创建DecorView的过程: 



@Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            //组装DecorView
            installDecor();
        }
        return mDecor;
    }

   private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //获取DecorView
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
    }
      //返回值就是DecorView
    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

至此我们知道window.callback就是activity, 事件传递到了Activity中;然后追踪可以看出事件的传递流程:DecorView->Activity->Window->DecorView->ViewGroup

// 伪代码
public class DecorView extends FrameLayout {
  // 1.将事件分发给Activity
  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
      return window.callback.dispatchTouchEvent(ev)
  }
}

// 2.将事件分发给Window
public class Activity {
  public boolean dispatchTouchEvent(MotionEvent ev) {
      return getWindow().superDispatchTouchEvent(ev);
  }
}

// 3.将事件再次分发给DecorView
public class PhoneWindow extends Window {
  @Override
  public boolean superDispatchTouchEvent(MotionEvent event) {
      return mDecor.superDispatchTouchEvent(event);
  }
}

//4.执行ViewGroup 的 dispatchTouchEvent,将事件分发到View体系中
public class DecorView extends FrameLayout {
  // 4.执行ViewGroup 的 dispatchTouchEvent
  public boolean superDispatchTouchEvent(MotionEvent event) {
      return super.dispatchTouchEvent(event);
  }
}

对于DecorView而言,它承担了2个职责:

  • 1.在接收到输入事件时,DecorView不同于其它View,它需要先将事件转发给最外层的Activity,使得开发者可以通过重写Activity.onTouchEvent()函数以达到对当前屏幕触摸事件拦截控制的目的,这里DecorView履行了自身(根节点)特殊的职责;

  • 2.从Window接收到事件时,作为View树的根节点,将事件分发给子View,这里DecorView履行了一个普通的View的职责。

接下来就该看事件在Activity,ViewGroup和View中是如何传递和消费的了,这一阶段终于到了面向用户的最上层了,因此也需要考虑事件是否被消费。事件先从外层传递到内层 Acitivty -> ViewGroup -> View,然后消费结果再有由内层传递到外层。

分发消费流程

事件分发过程中主要通过三个重要的方法来处理:

  • dispatchTouchEvent() 负责事件的分发
  • onTouchEvent() 负责事件的消费,返回true则表示消费当前事件,返回false则不消费
  • onInterceptTouchEvent() 负责事件的拦截,只有ViewGroup有该方法,另外两个方法Activity,ViewGroup和View都拥有

当事件到达dispatchTouchEvent()时,若其有child,则调用子View的dispatchTouchEvent(event)再将事件分发给child...以此类推,直至将事件分发到底部的View,子View会将是否消费的结果再一层层传递到最上层的dispatchTouchEvent()方法中,是一个递归的流程;若ViewGroup中有子View消费了事件,则ViewGroup的dispatchTouchEvent()返回true表示事件已经被消费,若子View没有消费事件,则将事件再交给自身的onTouchEvent()处理,最终确定事件是否被消费,将结果返回给上一层。

// 伪代码实现
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
  boolean consume = false;
  // 1.将事件分发给Child
  if (hasChild) {
    consume = child.dispatchTouchEvent();
  }
  // 2.若Child不消费该事件,或者没有child,判断自身是否消费该事件
  if (!consume) {
    consume = super.dispatchTouchEvent();
  }
  // 3.将结果向上层传递
  return consume;
}

事件序列

MotionEvent有四种类型:

  • MotionEvent.ACTION_DOWN:手指按下屏幕的瞬间(一切事件的开始)
  • MotionEvent.ACTION_MOVE:手指在屏幕上移动
  • MotionEvent.ACTION_UP:手指离开屏幕瞬间
  • MotionEvent.ACTION_CANCEL :取消手势,一般由程序产生,不会由用户产生

通常一个ACTION_DOWN, 多个ACTION_MOVE,1个ACTION_UP组成一个事件序列,

当接收到一个ACTION_DOWN时,意味着一次完整事件序列的开始,通过递归遍历找到View树中真正对事件进行消费的Child,并将其进行保存,这之后接收到ACTION_MOVE和ACTION_UP行为时,则跳过遍历递归的过程,将事件直接分发给Child这个事件的消费者;当接收到ACTION_DOWN时,则重置整个事件序列:

View事件分发_第2张图片

如图所示,其代表了一个View树,若序号为4的View是实际事件的消费者,那么当接收到ACTION_DOWN事件时,上层的ViewGroup则会通过递归找到它,接下来该事件序列中的其它事件到来时,也交给4号View去处理。

这个思路似乎没有问题,但是目前的设计中我们还缺少一把关键的钥匙,那就是如何在ViewGroup中保存实际消费事件的View?

为此设计者根据View的树形结构,设计了一个TouchTarget类,为作为一个成员属性,描述ViewGroup下一级事件分发的目标:

public abstract class ViewGroup extends View {
    // 指向下一级事件分发的`View`
    private TouchTarget mFirstTouchTarget;

    private static final class TouchTarget {
        public View child;
        public TouchTarget next;
    }
}
这里应用到了树的 深度优先搜索算法(Depth-First-Search,简称DFS算法),正如代码所描述的,每个ViewGroup都持有一个mFirstTouchTarget, 当接收到一个ACTION_DOWN时,通过递归遍历找到View树中真正对事件进行消费的Child,并保存在mFirstTouchTarget属性中,依此类推组成一个完整的分发链。

比如上文的树形图中,序号为1的ViewGroup中的mFirstTouchTarget指向序号为2的ViewGroup,后者的mFirstTouchTarget指向序号为3的ViewGroup,依此类推,最终组成了一个 1 -> 2 -> 3 -> 4 事件的分发链。

对于一个 事件序列 而言,第一次接收到ACTION_DOWN事件时,通过DFS算法为View树事件的 分发链 进行初始化,在这之后,当接收到同一事件序列的其它事件如ACTION_MOVE、ACTION_UP时,则会跳过递归流程,将事件直接分发给 分发链 下一级的Child中:

// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
  boolean consume = false;
  // ...
  if (event.isActionDown()) {
    // 1.第一次接收到Down事件,递归寻找分发链的下一级,即消费该事件的View
    // 这里可以看到,递归深度搜索的算法只执行了一次
    mFirstTouchTarget = findConsumeChild(this);
  }

  // ...
  if (mFirstTouchTarget == null) {
    // 2.分发链下一级为空,说明没有子`View`消费该事件
    consume = super.dispatchTouchEvent(event);
  } else {
    // 3.mFirstTouchTarget不为空,必然有消费该事件的`View`,直接将事件分发给下一级
    consume = mFirstTouchTarget.child.dispatchTouchEvent(event);
  }
  // ...
  return consume;
}

在进行事件分发的时候,只有对ACTION_DOWN事件返回true,才会收到后续的ACTION_MOVE和ACTION_UP的事件。这一事件序列的后续的事件ACTION_MOVE和ACTION_UP传到这个View后也不再往下传递了,都交由自身处理,当然也可以自己强行再将事件传递给子View。

事件拦截机制

ViewGroup提供了onInterceptTouchEvent()以达到让ViewGroup跳过子View的事件分发,提前结束 递流程 ,并自身决定是否消费事件,并将结果反馈给上层级的ViewGroup处理。

额外设计这样一个接口是否有必要?读者认真思考可以得知,这是有必要的,最经典的使用场景就是通过重写onInterceptTouchEvent()函数以解决开发中常见的 滑动冲突 事件;

ViewGroup的onInterceptTouchEvent()会在dispatchTouchEvent()中被调用,如果onInterceptTouchEvent返回true, 则事件不会再向子View分发,而是交给ViewGroup自身处理,ViewGroup默认是不拦截的

// 伪代码实现
// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
  // 1.若需要对事件进行拦截,直接中止事件向下分发,让自身决定是否消费事件,并将结果返回
  if (onInterceptTouchEvent(event)) {
    return super.dispatchInputEvent(event);
  }

  // ...
  // 2.若不拦截当前事件,开始事件分发流程
}

此外,为了避免额外的开销,设计者根据 事件序列 为 事件拦截机制 做出了额外的优化处理,保证了 事件拦截的判断在一个事件序列中只处理一次,伪代码简单实现如下:

// ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
  if (mFirstTouchTarget != null) {
    // 1.若需要对事件进行拦截,直接中止事件向下分发,让自身决定是否消费事件,并将结果返回
    if (onInterceptTouchEvent(event)) {
      // 2.确定对该事件序列拦截后,因此就没有了下一级要分发的Child
      mFirstTouchTarget = null;
      // 下一个事件传递过来时,最外层的if判断就会为false,不会再重复执行onInterceptTouchEvent()了
      return super.dispatchInputEvent(event);
    }
  }

  // ...
  // 3.若不拦截当前事件,开始事件分发流程
}

 滑动冲突的处理

处理滑动冲突我们常用外部拦截法和内部拦截法,外部拦截法是在ViewGroup中通过重写 onInterceptTouchEvent,在其中判断什么时候需要拦截事件由自身处理,什么时候需要放行将事件传给内层控件处理,内部控件不需要做任何处理。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if(父容器需要自己处理改事件){
                intercepted = true;
            }else {
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
            default:
            break;
    }
    return intercepted;
}

内部拦截法是在子View中使用getParent().requestDisallowInterceptTouchEvent(true) 来进行处理,在内层控件的重写方法dispatchTouchEvent中,根据逻辑来决定外层控件何时需要拦截事件,何时需要放行。伪代码如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if (需要父容器需要处理该事件) {
                //允许外层控件拦截事件
                getParent().requestDisallowInterceptTouchEvent(false);
            } else {
                //需要内部控件处理该事件,不允许上层viewGroup拦截
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
        default:
            break;
    }
    return super.dispatchTouchEvent(ev);
}

除此之外,还需要外层控件在onInterceptTouchEvent中做一点处理:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        return false;
    } else {
        return true;
    }
}

我们通过源码来查看实现原理:

//ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
        ···
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

      // Handle an initial down.
      //重置状态 ACTION_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();
      }

        // Check for interception.
        final boolean intercepted;
        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;
        }

    return handled;
}

调用getParent().requestDisallowInterceptTouchEvent(true) 方法会影响上面的 FLAG_DISALLOW_INTERCEPT 这个标志位。

FLAG_DISALLOW_INTERCEPT 一旦设置后,ViewGroup 将无法拦截除了 ACTION_DOWN 以外的其他点击事件。这是因为 ViewGroup 在分发事件时,如果是 ACTION_DOWN 就会重置 FLAG_DISALLOW_INTERCEPT 这个标志位,将导致子 View 中设置的这个标记位无效。因此,当面对 ACTION_DOWN 事件时,ViewGroup 总是会调用自己的 onInterceptTouchEvent 方法来询问自己是否要拦截事件。也就是说 onInterceptTouchEvent 可能在子 View 设置 requestDisallowInterceptTouchEvent 后不一定被调用到,所以当我们想提前处理所有的点击事件,要选择 dispatchTouchEvent,只有这个方法才能确保每次都被调用。

事件是如何被消费的

事件的消费最终是交给View来处理的,在View的dispatchTouchEvent中可以看到事件会先交给mOnTouchListener.onTouch()处理,如果未消费再由onTouchEvent()处理。

  public boolean dispatchTouchEvent(MotionEvent event) {

        if (onFilterTouchEventForSecurity(event)) {

            ListenerInfo li = mListenerInfo;

            //1.先交给mOnTouchListener.onTouch()处理
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            //2.再交给onTouchEvent()处理
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

}

 在onTouchEvent中会判断是否有TouchDelegate,有则交给TouchDelegate处理;接下来会判断clickable属性,clickable为true时才会消费事件,该属性由具体View来设置;在ACTION_UP事件中会调用performClick()方法来触发onClickListener的调用;因此我们在自定义View事件处理时,也可以通过performClick()来触发onClickListener.onClick回调。

public boolean onTouchEvent(MotionEvent event) {

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //交给代理处理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果clickable为true,才会消费
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                        ...
                    performClickInternal();
                        ...
           return true;
        }
    return false;
      
}

public boolean performClick() {

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        //交给OnClickListener处理
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        return result;
    }

 

你可能感兴趣的:(Android,View事件分发)