Android Behavior之相关解析

来源:互联网 发布:加加林死亡 知乎 编辑:程序博客网 时间:2024/06/09 15:34

如果不了解Android嵌套滚动,最好看一下我之前的文章 Android 嵌套滑动——NestedScrolling完全解析,当然不了解对本篇文章的阅读也不会有太大的阻塞。

第一个简单的自定义Behavior

在Android 5.0 的时候推出了CoordinatorLayout控件,该控件从翻译上来说称之为 协调性布局,我的理解是,对于他下面的子控件的布局,大小,滚动等等一系列的东西,由每一个子控件商量沟通着来,及每一个控件对于测量,布局等都有发言权,不在是由类似与线性布局一样,你必须垂直着排放,某某必须在下面。那么如何实现每个子控件都有发言权呢,便是通过一个叫behavior的来进行操作。每一个子控件都可以指定一个behavior,该behavior中会包含一些CoordinatorLayoutmeaseure layout时的回调方法,这样我们可以根据每个控件的不同来进行实现,这样就达到了布局和测量交个子控件自己来做。

首先看一下我们利用behavior所实现的一个简单效果。

这里写图片描述

有两个小方块,垂直居中排列,下面的小方块会跟随上面的小方块一起滑动。

该效果可以有多种方式的实现,但为了表述behavior的原理,实现方式可能会稍显复杂。但目的是为了更好的学习behavior

实现分析

因为我们要学习使用behavior,那么肯定跟布局要使用CoordinatorLayout,他仅支持layout_gravity而不支持线性布局的垂直等排列,所以第一个问题便是怎么让下面的小方块放到合适的位置。

其次,上面方块需要能够消耗手指触摸以让自身根据手指的滑动而移动。

最后,便是使下面的方块监听上面方块的移动,以达到跟随上面方块的移动。

根据实现分析,我们来实现代码。

代码实现

首先布局文件activity_behavior.xml

<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width="match_parent"    android:layout_height="match_parent">    <View        android:id="@+id/header"        android:layout_width="50dp"        android:layout_height="50dp"        android:layout_gravity="center_horizontal"        android:layout_marginTop="50dp"        android:background="#ff00ff"        app:layout_behavior=".behavior.HeaderBehavior" />    <View        android:id="@+id/bottom"        android:layout_width="50dp"        android:layout_height="50dp"        android:layout_gravity="center_horizontal"        android:layout_marginTop="50dp"        android:background="#33f0ff"        app:layout_behavior=".behavior.BottomBehavior" /></android.support.design.widget.CoordinatorLayout>

该布局文件中主要定义了两个带有颜色的小方块,其中需要关注三个点:

  • 上面小方块定义了id为header,注意此id在后面会用到,因为我们下面的小方块是通过第一个方块来定位的。
  • 下面小方块中有一个layout_marginTop属性,如果我们不做任何操作,那么该小方块会和第一个小方块重合,我们需要的是该margin 是基于第一个小方块的,类似于垂直线性布局的margin。
  • 这两个子控件都分别添加了layout_behavior属性,该属性的值便是我们自定义的Behvior,注意代码中省略了包名(类似于activity的注册时可以省略包名一样)。

因为我们所有的操作都是在behavor中完成的,所以不在叙述Activity的代码。

从布局中我们可以看到定义了两个behavior,分别是HeaderBehaviorBottomBehavior方法,根据之前的实现分析,他们的分工如下:

  • HeaderBehavior:实现第一个小方块消耗手指触摸事件,让自己跟随手指移动。
  • BottomBehavior:根据第一个小方块的位置,定位自己的位置,在第一个小方块之下;监听第一个小方块,跟随第一个小方块移动。

基于此,我们看一下HeaderBehavior:

public class HeaderBehavior extends CoordinatorLayout.Behavior<View> {    // 记录手指触摸的位置    private int mLastY = 0;    public HeaderBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {        // 手指触摸的回调        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                mLastY = (int) ev.getRawY();                break;            case MotionEvent.ACTION_MOVE:                int y = (int) ev.getRawY();                child.setTranslationY(child.getTranslationY() + y - mLastY);                mLastY = y;                break;        }        return true;    }}

首先,该类继承CoordinatorLayout.Behavior并且声明了一个泛型,该泛型主要约束当前behavior可以声明到哪些控件上。如果定义的是RelativeLayout,则只能声明到RelativeLayout上。

在之前说过,Behavior中会包含一些测量,事件触摸的回调,而onTouchEvent便是其中的一个回调。他会将CoordinatorLayoutonTouchEvent()回调到该方法。当前方法返回true,那么CoordinatorLayoutonTouchEvent()也会返回true

具体的代码不在分析,就是根据手指移动,对当前控件进行偏移。

在这里如果对事件分发不是太熟悉的可能会有一个混淆,认为该onTouchEvent()是子控件的onTouchEvent()的回调。这是错误的。

因为behavior中的所有回调是来自与CoordinatorLayout.Behavior中的相关方法的回调,与子控件无关

对于BottomBehavior,需要完成两件事情,但这两件事情都需要依赖第一个小方块,那么怎么定义依赖呢?

Behavior中有一个重载方法layoutDependsOn(),该方法能够实现我们对第一个小方块的依赖。

看一下BottomBehavior的代码

public class BottomBehavior extends CoordinatorLayout.Behavior<View> {    public BottomBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    // =============== 根本:添加依赖 =============================    @Override    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {        return dependency.getId() == R.id.header;    }    // ================ 第一部分:定位 ========================    @Override    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {        // getDependencies  获取child 依赖的view        List<View> dependencies = parent.getDependencies(child);//        parent.getDependents(child)  // 获取依赖child 的控件        View header = findFirstDependency(dependencies);        if (header != null) {            CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();            Rect available = new Rect();            available.set(parent.getPaddingLeft() + lp.leftMargin,                    header.getBottom() + lp.topMargin,                    parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,                    parent.getHeight() - parent.getPaddingBottom() - lp.bottomMargin);            final Rect out = new Rect();            GravityCompat.apply(resolveGravity(getFinalGravity(lp.gravity)), child.getMeasuredWidth(),                    child.getMeasuredHeight(), available, out, layoutDirection);            child.layout(out.left, out.top, out.right, out.bottom);        } else {            super.onLayoutChild(parent, child, layoutDirection);        }        return true;    }    public int getFinalGravity(int gravity) {        // 获取当前控件的`layout_gravity`属性        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {            gravity = gravity | Gravity.TOP;        }        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {            gravity = gravity | Gravity.LEFT;        }        return gravity;    }    private static int resolveGravity(int gravity) {        return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity;    }    private View findFirstDependency(List<View> views) {        // 查找最根本的依赖        for (View view : views) {            if (view.getId() == R.id.header) {                return view;            }        }        return null;    }    // ================ 第二部分:监听移动 ========================    @Override    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {        child.setTranslationY(dependency.getTranslationY());        return true;    }}

根据功能,分为了三个部分,首先通过layoutDependsOn方法定义依赖,返回true表示需要依赖,从该方法可以看出对于一个控件可以添加对个依赖。

其次,对子控件进行定位,重写onLayoutChild()方法,该方法回调CoordinatorLayoutonLayout,在这里我们可以对子控进行定位。

通过parent.getDependencies(child)获取到当前子控件所依赖的所有控件,然后通过findFirstDependency()找到我们定位所用到的headerView,最后根据headerView的位置计算剩余可用的空间进行定位,调用立刻child.layout()确定最终的位置。

最后便是监听第一个小方块的移动,因为我们通过layoutDependsOn添加了依赖,那么我们可以重载onDependentViewChanged()方法,该方法在关心的控件位置和大小发生变化时会回调。

总结

整个流程下来,总的来说就是定义Behavior,在Behavior的重载方法中完成对子控件的measure layout 事件处理等等。

最后强调一下 behavior中的所有回调是来自与CoordinatorLayout.Behavior中的相关方法的回调,与子控件无关

Behavior中相关重载方法分析

在上一个例子中,我们了解到behavior的关键便是重载一些方法,那么都是有那些方法可以重载呢,我们往下看:

下面的方法都是我们自定义behavior中的方法,注意的是该方法是由CoordinatorLayout的相关方法中调用得以接到回调。

public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params)

调用时机: 当这个behavior被加入到LayoutParams中时,实际是指LayoutParams被创建时回调。

public void onDetachedFromLayoutParams()

behaviorLayoutParams中移除时回调,当ViewCoordinatorLayout中移除时不会被回调。

public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)

CoordinatiorLayoutonInterceptTouchEvent()中,会回调该方法。

其回调时机比较复杂,具体逻辑如下:

  • 遍历所有的behavior, 按照顺序执行onInterceptTouchEvent()
  • 如果其中一个behhavioronInterceptTouchEvent()返回true
    • 如果当前事件是ACTION_DOWN:则后面的behavioronIntercepTouchEvent()就不会被回调
    • 如果当前事件是其他事件,则后面会被收到一个ACTION_CANCEL 事件

CoordinatiorLayoutonInterceptTouchEvent()中,如果有某一个Behavior返回true,则会保存该对象,以便onTouchEvent()时使用。

如果该方法返回true,则CoordinatiorLayoutonInterceptTouchEvent()方法也会返回true;

public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)

CoordinatiorLayoutonTouchEvent()中,会回调该方法。

  • 如果在onInterceptTouchEvent()时,某一个behavior返回了true,则会直接调用它的onTouchEvent()方法。
  • 如果不符合上面的条件,他会走和onInterceptTouchEvent()时的类似逻辑
    • Down事件时,在某一个behavioronTouchEvent()返回true之前的behavior都会被调用,之后的不会调用。
    • 其他事件,在某一个behavioronTouchEvent()返回true之前的behavior都会被调用,之后的会受到一个CANCEL事件。

注意,如果再onInterceptTouchEvent()时没有找到一个处理的Behavior对象,会在onTouchEvent时遍历所有behavior,一旦找到一个behavior处理,则不会再一次遍历所有,而是使用保存的behavior对象。

public boolean blocksInteractionBelow(CoordinatorLayout parent, V child)

返回当前控件是否拦截触摸事件,因为触摸事件一般是按照子view自向往上进行便利,如果该方法返回了true,则会拦截触摸事件往下的传递,默认返回为false。其内部默认的实现如下:

 public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {            return getScrimOpacity(parent, child) > 0.f;        }

可见其一般是通过getScrimOpacity()来判断的

public float getScrimOpacity(CoordinatorLayout parent, V child)

该方法限定返回值[0.0 ,1.0],如果大于0,则上面那个方法返回true ,则会拦截事件。同时该方法的返回会决定一个透明度。因为如果事件无法向下传递,会在CoordinatorLayout中画一个蒙层,优先于该View的在蒙层的上方,无法处理触摸的在蒙层的下方

public int getScrimColor(CoordinatorLayout parent, V child)

拦截事件后蒙层的颜色,默认是黑色。

public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)

决定该View依赖的其他View,如果返回true,表示child关心dependency的变化,具体那些变化,会在下面的一些回调方法中展示

public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency)

View关心的dependency发生size或者position的变化时,会回调该方法,如果该方法内也修改了viewposition或者size,则该方法应该返回true

public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency)

关心的ViewCoordinatorLayout中移除时的回调通知

public boolean onMeasureChild(CoordinatorLayout parent, V child,int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

measure时的回调,该方法如果返回true,则不会在走默认的子类测量流程。

public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)

onLayout()时的回调,如果返回true,则view的具体位置由该方法来定位。

public static void setTag(View child, Object tag)
public static Object getTag(View child)

对指定的child设置和获取标签。

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

该嵌套滚动和事件分发不同,每一个behavior的该方法都会被回调。只要有一个返回true,则CoordinatorLayout也会返回true。

public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
上一个方法返回true ,则该方法就会被调用

public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)

停止嵌套滚动时的回调,每一个behavior都会被调用

public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)

嵌套滚动相关,每一个behavior都会被调用

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed)

嵌套滚动相关,每一个behavior都会被调用,但是因为其在嵌套滚动之前调用,则会计算每一个behavior中的消耗,获取最大的消耗往下传。

public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,float velocityX, float velocityY, boolean consumed)

嵌套滚动相关,每一个behavior都会被调用

public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,float velocityX, float velocityY)

嵌套滚动相关,每一个behavior都会被调用

public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state)
public Parcelable onSaveInstanceState(CoordinatorLayout parent, V child)

数据的保存和恢复相关。

public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, V child, WindowInsetsCompat insets)
public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout, V child, Rect rectangle, boolean immediate)
public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child,@NonNull Rect rect)

暂时不知道是什么意思。。

关于CoordinatorLayout中触摸分发事件onInterceptTouchEvent()onTouchEvent()的相关源码分析

从之前的介绍中,了解到我们可以在Behavior中重载onTouchEvent()onInterceptTouchEvent()方法用以获取CoordinatorLayout的事件处理,那么对于多个behavior的触摸事件监听,具体回调流程是怎么样呢,下面我们就开始走进科学~

首先看一下CoordinatorLayoutonInterceptTouchEvent()方法源码:

   @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        MotionEvent cancelEvent = null;        final int action = MotionEventCompat.getActionMasked(ev);        // Make sure we reset in case we had missed a previous important event.        if (action == MotionEvent.ACTION_DOWN) {            // 重置behavior的状态,主要是将behavior拦截事件置为true            resetTouchBehaviors();        }        // 遍历所有的behavior ,获取事件处理状态        final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);        if (cancelEvent != null) {            // 搞不懂。。。            cancelEvent.recycle();        }        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {            // 事件结束,重置behavior 的状态            resetTouchBehaviors();        }        return intercepted;    }

在开始的时候会重置一下Behavior的状态,这个状态主要指的是block,在前面behavior的方法列举中,可以看到blocksInteractionBelow()方法,该方法会返回是否拦截后面控件的事件,在这里,会先重置一下,默认为不拦截。

然后通过performIntercept(ev, TYPE_ON_INTERCEPT)方法来遍历所有的behavior获取处理状态,最后返回。

看一下performIntercept()的源码:

 private boolean performIntercept(MotionEvent ev, final int type) {        boolean intercepted = false;        boolean newBlock = false;        MotionEvent cancelEvent = null;        final int action = MotionEventCompat.getActionMasked(ev);        // 获取所有的View ,该View 的顺序在不改变绘图优先级时,默认是所有子控件的倒序,及自下往上        final List<View> topmostChildList = mTempList1;        getTopSortedChildren(topmostChildList);        final int childCount = topmostChildList.size();        // 遍历所有的View ,对包含`behavior`的进行处理回调        for (int i = 0; i < childCount; i++) {            final View child = topmostChildList.get(i);            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            final Behavior b = lp.getBehavior();            // Down事件,则不会走此逻辑,对于Move事件,如果intercepted 或 newBlock为true,则会回调一个ACTION_CANCEL            if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {                // Cancel all behaviors beneath the one that intercepted.                // If the event is "down" then we don't have anything to cancel yet.                if (b != null) {                    if (cancelEvent == null) {                        final long now = SystemClock.uptimeMillis();                        cancelEvent = MotionEvent.obtain(now, now,                                MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);                    }                    switch (type) {                        case TYPE_ON_INTERCEPT:                            b.onInterceptTouchEvent(this, child, cancelEvent);                            break;                        case TYPE_ON_TOUCH:                            b.onTouchEvent(this, child, cancelEvent);                            break;                    }                }                continue;            }            // 关键方法,找到处理的behavior ,并处理,一旦某个behavior的方法返回true ,则后面的都不会在执行此逻辑            if (!intercepted && b != null) {                switch (type) {                    case TYPE_ON_INTERCEPT:                        intercepted = b.onInterceptTouchEvent(this, child, ev);                        break;                    case TYPE_ON_TOUCH:                        intercepted = b.onTouchEvent(this, child, ev);                        break;                }                // 保存处理的behavior对象                if (intercepted) {                    mBehaviorTouchView = child;                }            }            // 在onInterceptTouchEvent 中 DOWN事件,会重置所有的behavior的状态,该返回字段为false;            // 所以对于DOWN事件,肯定不会走下面的break;提前结束循环            // 而对于其他事件,如果wasBlocking 返回true ,则肯定会走入下面的break逻辑,提前跳出。及后面的behavior不会在回调,类似于拦截。            final boolean wasBlocking = lp.didBlockInteraction();            //如果wasBlocking为false , 该方法会调用每一个的behavior的blocksInteractionBelow()判断是否拦截向后面behavior的遍历,并将值赋值给didBlockInteraction();            final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);            newBlock = isBlocking && !wasBlocking;            if (isBlocking && !newBlock) {                // isBlock == true   wasBlock = true 走入逻辑,                break;            }        }        topmostChildList.clear();        return intercepted;    }

该方法中区分onTouchEvent()onInterceptTouchEvent(),可见他们的遍历逻辑几乎一致。

代码注释上可以仔细撸一撸,到这里我们可以总结一下onInterceptTouchEvent()的执行逻辑,

对于每一个behavioronInterceptTouchEvent()方法,会先按照CoordinatorLayout布局中自下往上(可能有特殊情况,改变绘制优先级时)进行遍历:

如果是DOWN事件,会遍历到直到某一个behavioronInterceptTouchEvent()返回true为止,后面的不会在调用。在Down事件时,会找出在哪一个behavior会强制拦截所有事件。通过behaviorblocksInteractionBelow()作为依据。

如果是除了DOWN事件,如果有强制拦截事件,则首先强制拦截事件的子控件之后的控件不会被回调。然后,依然从开始遍历,遍历到强制拦截事件的子控件结束为止,如果其中一个返回了true,则后面的会收到一个CANCEL事件。

onInterceptTouchEvent()执行逻辑中,如果找到某个behavior处理事件,即返回true,则会保存该对象,以便onTouchEvent()中使用。

那么在看一下CoordinatorLayoutonTouchEvent()源码

@Override    public boolean onTouchEvent(MotionEvent ev) {        boolean handled = false;        boolean cancelSuper = false;        MotionEvent cancelEvent = null;        final int action = MotionEventCompat.getActionMasked(ev);        // 如果onInterceptTouchEvent()中找到了一个behvior中处理事件,则直接使用,否则调用performIntercept()查找处理的behavior        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();            final Behavior b = lp.getBehavior();            if (b != null) {                // 回调事件                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);            }        }        // 如果没有behavior 处理,则会调用基类的onTouchEvent()        if (mBehaviorTouchView == null) {            handled |= super.onTouchEvent(ev);        } else if (cancelSuper) {            // 在Down 的时候没有处理,在Move的时候处理,则需要取消之前的调用            if (cancelEvent == null) {                final long now = SystemClock.uptimeMillis();                cancelEvent = MotionEvent.obtain(now, now,                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);            }            // 取消当前控件的事件处理,因为有某一个behavior处理了事件            super.onTouchEvent(cancelEvent);        }        if (!handled && action == MotionEvent.ACTION_DOWN) {        }        if (cancelEvent != null) {            cancelEvent.recycle();        }        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {            resetTouchBehaviors();        }        return handled;    }

注意:在onTouchEvent()中没有重置behavior的状态。及拦截所有事件的状态。

开始的时候,如果onInterceptTouchEvent()中找到了处理的mBehaviorTouchView,则直接调用对应behavioronTouchEvent()方法,其他的不会调用。如果该behavior返回false,则CoordinatorLayoutonTouchEvent()也返回false

如果没有找到,则会调用performIntercept()找到要处理的behavior,此时会回调behavioronTouchEvent()方法。如果依然没有处理,则会调用基类的super.onTouchEvent()处理事件,如果有事件处理,则会给积累发送一个取消事件。

至于他的遍历逻辑,及performIntercept()中的逻辑和onInterceptTouchEvent()时的几乎一致。

总结

基本:所有的遍历都是从CoordinatorLayout中子控件倒序开始遍历的(改变绘制流程除外)

onInterceptTouchEvent()调用逻辑:

  • 对于Down事件:会回调从第一个开始知道某一个的behavioronInterceptTouchEvent()返回true。

  • 对于除了Down以外的事件:如果behaviorblocksInteractionBelow()返回true ,则会拦截所有事件。即只遍历从第一个到该behavior,在遍历过程中的某一个behavioronInterceptTouchEvent()返回true,则其之前的正常回调,其之后的会获得一个CANCEL事件。

onTouchEvent()调用逻辑

  • 如果onInterceptTouchEvent()中找到了behavior,及某个behavior onInterceptTouchEvent()返回了true。则逻辑如下:

    • 直接调用该behavioronTouchEvent()方法,如果该方法返回false,则调用基类的super.onTouchEvent()
  • 如果onInterceptTouchEvent()没有找到,则会进行遍历,遍历范围为从第一个到某个behaviorblocksInteractionBelow()返回true为止。如果找到了,会保存,不会在遍历。

    • DOWN事件: 会回调从第一个开始知道某一个的behavioronTouchEvent()返回true。

    • 其他事件:即只遍历从第一个到该behavior,在遍历过程中的某一个behavioronTouchEvent()返回true,则其之前的正常回调,其之后的会获得一个CANCEL事件。

    • 注意,该遍历如果找到处理的了,无论是Down或者其他事件,则不会在遍历,只会使用找到的behavior,如果没找到,则其他事件时,会再次遍历。