Android Behavior之相关解析
来源:互联网 发布:加加林死亡 知乎 编辑:程序博客网 时间:2024/06/09 15:34
如果不了解Android嵌套滚动,最好看一下我之前的文章 Android 嵌套滑动——NestedScrolling完全解析,当然不了解对本篇文章的阅读也不会有太大的阻塞。
第一个简单的自定义Behavior
在Android 5.0 的时候推出了CoordinatorLayout
控件,该控件从翻译上来说称之为 协调性布局,我的理解是,对于他下面的子控件的布局,大小,滚动等等一系列的东西,由每一个子控件商量沟通着来,及每一个控件对于测量,布局等都有发言权,不在是由类似与线性布局一样,你必须垂直着排放,某某必须在下面。那么如何实现每个子控件都有发言权呢,便是通过一个叫behavior
的来进行操作。每一个子控件都可以指定一个behavior
,该behavior
中会包含一些CoordinatorLayout
在measeure
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
,分别是HeaderBehavior
和BottomBehavior
方法,根据之前的实现分析,他们的分工如下:
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
便是其中的一个回调。他会将CoordinatorLayout
的onTouchEvent()
回调到该方法。当前方法返回true
,那么CoordinatorLayout
的onTouchEvent()
也会返回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()
方法,该方法回调CoordinatorLayout
的onLayout
,在这里我们可以对子控进行定位。
通过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()
当behavior
从LayoutParams
中移除时回调,当View
从CoordinatorLayout
中移除时不会被回调。
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
在CoordinatiorLayout
的onInterceptTouchEvent()
中,会回调该方法。
其回调时机比较复杂,具体逻辑如下:
- 遍历所有的
behavior
, 按照顺序执行onInterceptTouchEvent()
- 如果其中一个
behhavior
的onInterceptTouchEvent()
返回true- 如果当前事件是
ACTION_DOWN
:则后面的behavior
的onIntercepTouchEvent()
就不会被回调 - 如果当前事件是其他事件,则后面会被收到一个
ACTION_CANCEL
事件
- 如果当前事件是
在CoordinatiorLayout
的onInterceptTouchEvent()
中,如果有某一个Behavior
返回true
,则会保存该对象,以便onTouchEvent()
时使用。
如果该方法返回true,则
CoordinatiorLayout
的onInterceptTouchEvent()
方法也会返回true;
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)
在CoordinatiorLayout
的onTouchEvent()
中,会回调该方法。
- 如果在
onInterceptTouchEvent()
时,某一个behavior
返回了true,则会直接调用它的onTouchEvent()
方法。 - 如果不符合上面的条件,他会走和
onInterceptTouchEvent()
时的类似逻辑Down
事件时,在某一个behavior
的onTouchEvent()
返回true
之前的behavior
都会被调用,之后的不会调用。- 其他事件,在某一个
behavior
的onTouchEvent()
返回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
的变化时,会回调该方法,如果该方法内也修改了view
的position
或者size
,则该方法应该返回true
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency)
关心的View
从CoordinatorLayout
中移除时的回调通知
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
的触摸事件监听,具体回调流程是怎么样呢,下面我们就开始走进科学~
首先看一下CoordinatorLayout
的onInterceptTouchEvent()
方法源码:
@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()
的执行逻辑,
对于每一个behavior
的onInterceptTouchEvent()
方法,会先按照CoordinatorLayout
布局中自下往上(可能有特殊情况,改变绘制优先级时)进行遍历:
如果是DOWN
事件,会遍历到直到某一个behavior
的onInterceptTouchEvent()
返回true为止,后面的不会在调用。在Down
事件时,会找出在哪一个behavior
会强制拦截所有事件。通过behavior
的blocksInteractionBelow()
作为依据。
如果是除了DOWN
事件,如果有强制拦截事件,则首先强制拦截事件的子控件之后的控件不会被回调。然后,依然从开始遍历,遍历到强制拦截事件的子控件结束为止,如果其中一个返回了true
,则后面的会收到一个CANCEL
事件。
在onInterceptTouchEvent()
执行逻辑中,如果找到某个behavior
处理事件,即返回true,则会保存该对象,以便onTouchEvent()
中使用。
那么在看一下CoordinatorLayout
的onTouchEvent()
源码
@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
,则直接调用对应behavior
的onTouchEvent()
方法,其他的不会调用。如果该behavior
返回false
,则CoordinatorLayout
的onTouchEvent()
也返回false
。
如果没有找到,则会调用performIntercept()
找到要处理的behavior
,此时会回调behavior
的onTouchEvent()
方法。如果依然没有处理,则会调用基类的super.onTouchEvent()
处理事件,如果有事件处理,则会给积累发送一个取消事件。
至于他的遍历逻辑,及performIntercept()
中的逻辑和onInterceptTouchEvent()
时的几乎一致。
总结
基本:所有的遍历都是从CoordinatorLayout
中子控件倒序开始遍历的(改变绘制流程除外)
onInterceptTouchEvent()
调用逻辑:
对于Down事件:会回调从第一个开始知道某一个的
behavior
的onInterceptTouchEvent()
返回true。对于除了Down以外的事件:如果
behavior
的blocksInteractionBelow()
返回true ,则会拦截所有事件。即只遍历从第一个到该behavior
,在遍历过程中的某一个behavior
的onInterceptTouchEvent()
返回true,则其之前的正常回调,其之后的会获得一个CANCEL
事件。
onTouchEvent()
调用逻辑
如果
onInterceptTouchEvent()
中找到了behavior
,及某个behavior
onInterceptTouchEvent()
返回了true。则逻辑如下:- 直接调用该
behavior
的onTouchEvent()
方法,如果该方法返回false
,则调用基类的super.onTouchEvent()
。
- 直接调用该
如果
onInterceptTouchEvent()
没有找到,则会进行遍历,遍历范围为从第一个到某个behavior
的blocksInteractionBelow()
返回true为止。如果找到了,会保存,不会在遍历。DOWN事件: 会回调从第一个开始知道某一个的
behavior
的onTouchEvent()
返回true。其他事件:即只遍历从第一个到该
behavior
,在遍历过程中的某一个behavior
的onTouchEvent()
返回true,则其之前的正常回调,其之后的会获得一个CANCEL
事件。- 注意,该遍历如果找到处理的了,无论是
Down
或者其他事件,则不会在遍历,只会使用找到的behavior
,如果没找到,则其他事件时,会再次遍历。
- Android Behavior之相关解析
- Android开发之自定义Behavior
- CoordinatorLayout源码解析之初识Behavior
- Android Behavior
- Behavior相关资料
- 深入解析Android Design包——Behavior
- 2behavior原理解析
- coordinatorlayout和behavior解析
- Android工程相关解析
- 一. Htmlayout之behavior
- Material之Behavior(1)
- Material之Behavior(2)
- CoordinatorLayout之Behavior使用
- android之TelephonyManager电话相关信息管理类解析
- Android之Relative布局的相关属性解析
- Android开发之scrollview嵌套listview相关布局的解析
- Activity源码之Android 6.0权限相关完全解析
- android design Behavior 初见
- IntelliJ IDEA 14.0.3取消警告
- mp4 seek
- oracle视图
- JavaScript Source Map详解
- springBoot简单入门
- Android Behavior之相关解析
- ubuntu下解决csdn网页打不开的问题
- while 和 for循环
- 柯里化-多参数合成一个参数
- docker 使用
- IP地址的分类——a,b,c 类是如何划分的
- 通过刚体组件控制物体的移动
- jjavascript开发:JSONP跨域请求数据报错 “Unexpected token :”的解决办法
- Ural1776: Anniversary Firework(概率DP)