已经说过了,在AndroidDesign包中主要有两个核心概念:一是NestedScroll,另一个就是Behavior。
相比于NestedScroll这个概念来说,Behavior分析起来会难很多,因为它几乎遍布了AndroidDesign包的每一个控件,种类繁多;另外Behavior提供了二十多个空方法给使用者来重写,主要分为四类:
1.与Touch事件相关的方法
2.与NestedScroll相关的方法
3.与控件依赖相关的方法(依赖这个概念可能接触的不多,就是如果A依赖B,那么当B变化时会通知A跟着变化)
4.其他方法,如测量和布局等
由此可见,Behavior的使用是非常灵活的,所以功能也是非常的强大。但是,对于越灵活的东西,就越难将它讲清除。它有一百种用法,总不能我就举出一百个例子来进行说明,因此本文只能起到一个抛砖引玉的作用,要真正融会贯通还得靠各位自己去揣摩。

从CoordinatorLayout入手

好好的干嘛扯到CoordinatorLayout呢?
如果你这么问那你就外行了,因为如果没有CoordinatorLayout,光有Behavior是啥用都没有滴。
CoordinatorLayout就是一个容器,主要功能就是为它里面的控件传递命令,更准确的说就是使用Behavior来让子控件们相互调用。
CoordinatorLayout有自己的LayoutParams类

public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* A {@link Behavior} that the child view should obey.
*/
Behavior mBehavior;
1
2
3
4
5
1
2
3
4
5
它的布局参数类定义的第一个属性就是Behavior,而且还有layout_behavior属性供布局文件使用,可在布局文件中为CoordinatorLayout内部的控件设置behavior对象。
另外,所有的Behavior的祖宗都是CoordinatorLayout.Behavior,这是一个静态-内部-虚拟类,头衔有点长~ 我们抓住这个静态内部类就算是抓到Behavior的精髓了。
除了以上两点,最重要的一层关系是:所有Behavior的方法都是在CoordinatorLayout中调用的,比如来了个NestedScroll事件,那么CoordinatorLayout会调用自己的onNestedScroll()方法,然后在方法内部,就会调用childView的behavior对应的onNestedScroll()方法了。
具体过程,我们来详细分析。

如何处理Touch相关事件

找到CoordinatorLayout中的Behavior类,可以发现该类中定义了如下两个方法:

public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return false;
}

public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
return false;
很显然,这是拦截触摸事件和处理触摸事件的方法。
我们看看这两个方法是如何被CoordinatorLayout调用的。

先看onInterceptTouchEvent

根据View的事件体系可知,对事件是否拦截的处理在onInterceptTouchEvent()方法中,于是找到CoordinatorLayout的这个方法:

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) {
resetTouchBehaviors();
}
//这里才是处理事件拦截的代码
final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

if (cancelEvent != null) {
cancelEvent.recycle();
}

if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
resetTouchBehaviors();
}
从上面代码可知,真正的处理逻辑在performIntercept()方法中,注意它的第二个参数TYPE_ON_INTERCEPT。然后再看performIntercept方法:

private boolean performIntercept(MotionEvent ev, final int type) {

...
//遍历所有显示出来了的childView
for (int i = 0; i < childCount; i++) {
final View child = topmostChildList.get(i);
final LayoutParams lp = (LayoutParams http://www.wmyl15.com/) child.getLayoutParams();
final Behavior b = lp.getBehavior();

if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
// 如果事件已经被拦截,那么向其他childView发送cancelEvent
//...省略代码...
continue;
}

if (!intercepted && b != null) {
switch (type) {
//如果事件未拦截,且childView设置了behavior,则进行拦截判断
case TYPE_ON_INTERCEPT:
intercepted = b.onInterceptTouchEvent(this, child, ev);
break;
case TYPE_ON_TOUCH:
intercepted = b.onTouchEvent(this, child, ev);
break;
}
if (intercepted) {
mBehaviorTouchView = child;
}
}
...
}

topmostChildList.clear();

对代码进行简化之后,逻辑就很明显了。先从childView中取出LayoutParams对象,然后从LayoutParams对象中取出Behavior对象,如果performIntercept()方法第二个参数传进来的是TYPE_ON_INTERCEPT,则调用behavior.onInterceptTouchEvent()方法判断是否拦截事件。换句话说就是,是否拦截事件跟CoordinatorLayout本身没有一毛钱关系。

再看onTouchEvent

直接看CoordinatorLayout中的onTouchEvent(www.wmyl11.com)方法源码:

public boolean onTouchEvent(MotionEvent ev) {
boolean handled = false;
boolean cancelSuper = false;
MotionEvent cancelEvent = null;

final int action = MotionEventCompat.getActionMasked(ev);

//先判断mBehaviorTouchView是否为null,如果不为null,则不会执行后面的performIntercept()
//如果等于null,则调用performIntercept方法,该方法如果返回true会对mBehaviorTouchView赋值
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);
}
}

// 经过上面的两重判断之后,如果mBehavior还是null,则说明childView不消费touch事件
// 那么该touch事件交给CoordinatorLayout的parent去处理
if (mBehaviorTouchView == null) {
handled |= super.onTouchEvent(ev);
}
...
上面的代码加了注释之后,应该不需要多说什么了。
还是那句话,CoordinatorLayout本身也不会消费touch事件。

如何处理NestedScroll相关事件

先看Behavior中跟NestedScroll相关的方法

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
V child, View directTargetChild, View target, int nestedScrollAxes) {
return false;
}

public void onNestedScrollAccepted(www.yule1369.net CoordinatorLayout coordinatorLayout, V child,
View directTargetChild, View target, int nestedScrollAxes) {
// Do nothing
}

public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
// Do nothing
}

public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
// Do nothing
}

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dx, int dy, int[] consumed) {
// Do nothing
}

public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY, boolean consumed) {
return false;
}

public boolean onNestedPreFling(www.lieqibiji.com CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY) {
return false;
大家不要被这么多方法给吓到了,如果你有阅读这篇文章深入解析Android Design包——NestedScroll 就能够发现,这些方法都是NestedScrollingParent接口中定义的方法。并且CoordinatorLayout本身是实现了NestedScrollingParent接口的,那么CoordinatorLayout会如何调用Behavior的这些方法呢? 肯定是一一对应的来调用。
我想Google这么设计的目的应该是为了解耦,只要给控件提供一个Behavior就可以拥有NestedScrollingParent的功能,这样一来控件本身就与NestedScrollingParent完全无关了。
由于方法比较多,这里就不一一展示调用过程了,挑onNestedScroll方法来说一下吧。
先看CoordinatorLayout中的onNestedScroll方法:

@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed www.xyseo.net) {
final int childCount = getChildCount();
boolean accepted = false;

for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
if (view.getVisibility() == GONE) {
continue;
}

final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
//获取childView的behavior,并调用behavior的onNestedScroll方法
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed);
accepted = true;
}
}

if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);

代码逻辑非常清晰,就是直接把nestedScroll事件通过behavior传递给childView去处理。
但是,我们注意到最后一段代码,调用了一个onChildViewsChanged()方法。
这个方法具体逻辑我们在下一小结分析,它主要是处理那些依赖控件的。之所以在此处加一句,是为了那些跟滑动控件存在依赖关系的其他控件,也可以做出响应。

如何处理依赖相关事件

接下来,我们来看看Behavior中依赖相关的方法

//判断child和dependency是否存在依赖关系
public boolean layoutDependsOn(C www.wmyl88.com/ oordinatorLayout parent, V child, View dependency) {
return false;
}

//dependency发生改变时,回调此方法
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}

//dependency被移除时,回调此方法
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {

要完成上面三个方法的使命,需要满足两点:
1.需要对CoordinatorLayout所有childView进行两两判断,看它们是否存在依赖关系。
2.当一个childView发生布局改变时,CoordinatorLayout需要回调通知与其有依赖关系的其他childView。

判断依赖关系

一个View在Android系统中的显示都是:onMeasure, onLayout, onDraw
所以先看onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();

没想到第一句就看到重点了,prepareChildren就是为了整理CoordinatorLayout内部的childView,自然也会将childView之间的依赖关系确定好,来看代码:

private void prepareChildren() {
mDependencySortedChildren.clear(); //List<View>
mChildDag.clear(); //图结构 --- 无回路有向图
//遍历所有childView
for (int i = 0, count = getChildCount(); i < count; i++) {
final View view = getChildAt(i);

final LayoutParams lp = getResolvedLayoutParams(view);
lp.findAnchorView(this, view);

mChildDag.addNode(view);
//再次遍历,需要双重遍历才能将childView两两判断dependency
//将判断的结果保存在有向图mChildDag中
for (int j = 0; j < count; j++) {
if (j == i) {
continue;
}
final View other = getChildAt(j);
final LayoutParams otherLp = getResolvedLayoutParams(other);
//这个dependsOn方法就是判断依赖关系的,内部会调用Behavior.layoutDependsOn()方法
if (otherLp.dependsOn(this, other, view)) {
if (!mChildDag.contains(other)) {
mChildDag.addNode(other);
}
mChildDag.addEdge(view, other);
}
}
}
//将图中的数据排序,并保存在List中
mDependencySortedChildren.addAll(mChildDag.getSortedList());
//将List倒序设置,让被依赖的childView排在前面,依赖于它的排在后面
Collections.reverse(mDependencySortedChildren);
这里涉及到一种比较复杂的数据结构——无回路有向图,篇幅有限就不在这里多说,我们只要知道会调用layoutDependsOn方法来判断依赖关系,然后将数据最后保存在mDependencySortedChildren这个List中。
这个mDependencySortedChildren列表中保存的都是childView,不过是按照特定的顺序进行了排序:
如果childView被其他view依赖的次数最多,则排在最前面,以此类推。
至于依赖关系并没有保存,到时候要用到时,再次调用layoutDependsOn方法来判断,写到这里我好像明白了为什么要将依赖次数多的放列表前面了。

childView发生布局改变

OK,依赖关系确定了,那就看看当childView发生改变时,如何让依赖的view跟着改变。
其实在NestedScroll相关方法中,最后都会调用一句代码

if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
上面也有提到,这个方法就是处理依赖控件的变化的。在分析它之前,还有必要看看其他地方有没有调用此方法。
然后,就看到了在onAttachToWindow()方法中,为CoordinatorLayout设置了OnPreDrawListener 回调,也就是说在执行onDraw之前,回执行onPreDraw方法中的代码。
我们先来看OnPreDrawListener:

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
onChildViewsChanged(EVENT_PRE_DRAW);
return true;

onPreDraw()的代码很简单,就是执行onChildViewsChanged()方法,也就是说每次onDraw都会调用这个方法来处理依赖控件。

接下来重点看onChildViewsChanged()方法了。

final void onChildViewsChanged(@DispatchChangeEvent final int type) {

final int childCount = mDependencySortedChildren.size();

for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();

// ... 省略了对anchor和inset相关的代码 ...
//可以说,上面的代码都是为了提升效率,下面的才是真正的处理逻辑

for (int j = i + 1; j < childCount; j++) {
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
//获取对应的Behavior对象
final Behavior b = checkLp.getBehavior();

//判断依赖关系
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) {
//当NestedScroll 和 Draw都会触发这个方法,
//这里二者只能有一个继续往下执行
checkLp.resetChangedAfterNestedScroll();
continue;
}

final boolean handled;
switch (type) {
case EVENT_VIEW_REMOVED:
// 调用Behavior对应的方法,通知依赖这个child的其他view
//这里的child的被依赖,checkChild是依赖于它
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// 除了删除事件,其他的都调用下面的方法
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}

if (type == EVENT_NESTED_SCROLL) {
//这里跟上面的checkLp.getChangedAfterNestedScroll()对应
checkLp.setChangedAfterNestedScroll(handled);

同样的,为了突出处理逻辑,把一些代码省略掉了。
代码不长,希望大家根据我的注释读一下代码,自然就明白了。
其实,onChildViewsChanged这个方法也只是在调用Behavior的相关方法而已,也就是说如果childA依赖于childB,那么当childB发生布局变化时,childB的Behavior就会把这个变化同时作用到childA身上。

结语

说到这里,一句话总结CoordinatorLayout本身啥事儿也不干,全让底下的childView去干了。
而Behavior之所以强大,是因为在我们不修改View的情况下,可以对View的行为进行修改和控制。

本来是要举例说明一下Behavior的具体用法的,但是没想到一写一写已经这么长了,太长的文章不利于阅读,也不利于学习吸收,就只能把举例说明移到下一篇去写了。
这里大概说一下会分析一个什么样的示例吧。
布局层级大概就是下面这个样子

<CoordinatorLayout>

<AppBarLayout>
<CollapsingToolbarLayout />
</AppBarLayout>

<NestedScrollView />

</CoordinatorLayout>

深入解析Android Design包——Behavior的更多相关文章

  1. 安卓Design包下的TextInputLayout和FloatingActionButton的简单使用

    终于介绍到Design包的最后的东西了. 也很简单,一个是TextInputLayout. TextInputLayout作为一个父容器,包含一个新的EditText,可以给EditText添加意想不 ...

  2. 安卓Design包之超强控件CoordinatorLayout与SnackBar的简单使用

    在前面的Design中,学习使用了TabLayout,NavigationView与DrawerLayout实现的神奇效果,今天就带来本次Design包中我认为最有意义的控件CoordinatorLa ...

  3. Android Design Support Library: 学习CoordinatorLayout

    简述 CoordinatorLayout字面意思是"协调器布局",它是Design Support Library中提供的一个超级帧布局,帮助我们实现Material Design ...

  4. Android Design Support Library(三)用CoordinatorLayout实现Toolbar隐藏和折叠

    此文的代码在Android Design Support Library(一)用TabLayout实现类似网易选项卡动态滑动效果代码的基础上进行修改,如果你没有看过本系列的第一篇文章最好先看一看.Co ...

  5. 一个神奇的控件——Android CoordinatorLayout与Behavior使用指南

    CoordinatorLayout是support.design包中的控件,它可以说是Design库中最重要的控件. 本文通过模仿知乎介绍了自定义Behavior,通过模仿百度地图介绍了BottomS ...

  6. 带你实现开发者头条APP(四)---首页优化(加入design包)

    title: 带你实现开发者头条APP(四)---首页优化(加入design包) tags: design,Toolbar,TabLayout,RecyclerView grammar_cjkRuby ...

  7. 使用Design包实现QQ动画侧滑效果和滑动菜单导航

    Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的Android Design Support Library,在这个supp ...

  8. 安卓Design包之AppBar和Toolbar的联用

    前面讲了Design包的的CoordinatorLayout和SnackBar的混用,现在继续理解Design包的AppBar; AppBarLayout跟它的名字一样,把容器类的组件全部作为AppB ...

  9. 安卓Design包之TabLayout控件的简单使用

    Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的Android Design Support Library,在这个supp ...

随机推荐

  1. Hibernate学习---第十三节:hibernate过滤器和拦截器的实现

    一.hibernate 过滤器 1.在持久化映射文件中配置过滤器,代码如下: <?xml version="1.0"?> <!DOCTYPE hibernate- ...

  2. tomcat警告:Setting property 'source' to 'org.eclipse.jst.j2ee.server:ServletPro' did not find a matching property

    警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclips ...

  3. codeforces 632B B. Alice, Bob, Two Teams(暴力)

    B. Alice, Bob, Two Teams time limit per test 1.5 seconds memory limit per test 256 megabytes input s ...

  4. stl_deque.h

    stl_deque.h // Filename: stl_deque.h // Comment By: 凝霜 // E-mail: mdl2009@vip.qq.com // Blog: http:/ ...

  5. 继续学习C:数字进制表示

    1. 数字后面跟D表示十进制,如:123D. 2. 数字后面跟B表示二进制,如:10010B. 3. 数字后面跟Q表示八进制,如:652Q. 4. 数字后面跟H表示十六进制,如:2B5H. 把十进制数 ...

  6. CF221C Circling Round Treasures

    题目大意 给定一个$n\times m$的网格$(n,m\leq 20)$,每个格子都是$S\space \#\space B\space x\space .$中第一个. $S$表示起点,保证有且仅有 ...

  7. QT之在QML中使用C++类和对象

    QML其实是对ECMAScript的扩展,融合了Qt object系统,它是一种新的解释性语言,QML引擎虽然由Qt C++实现,但QML对象的运行环境说到底和C++对象的上下文环境是不通的,是平行的 ...

  8. [Codeforces 1139D] Steps to One

    [题目链接] https://codeforces.com/contest/1139/problem/D [算法] 考虑dp 设fi表示现在gcd为i , 期望多少次gcd变为1 显然 , fi = ...

  9. 【VS】VS开发中遇到的问题的总结

    1.  VS中经常会出现无法解析的外部符号,还有LINK ERROR 2019等 这类问题如果检查代码没有错误,很大概率就是lib文件错误.调试程序找出问题函数,再找出问题函数使用到的lib文件,在项 ...

  10. CF475D:CGCDSSQ

    浅谈\(RMQ\):https://www.cnblogs.com/AKMer/p/10128219.html 题目传送门:https://codeforces.com/problemset/prob ...