Android design包中CoordinatorLayou
最近用了一下CoordinatorLayout,做点笔记,这篇笔记只是讲原理。
一、CoordinatorLayout 是什么以及它的使用场景
Coordinator [kəʊ'ɔ:dɪneɪtə] n. 协调者;[自] 协调器;同等的人或物
1018341-f35cdb17d901108c.jpeg
CoordinatorLayout的简单介绍
我们从CoordinatorLayout的源码上翻译上大概可以知道(这里不贴源码,自己去看)
1 CoordinatorLayout是超级FrameLayout
2 它作为一个容器,它的子View之间有特殊的相互作用(交互)。
我们通过为CoordinatorLayout的子views指定Behavior,在同一个父容器下可以提供不同的交互,并且那些子view可以和另一个子view相互作用,相互影响。通过@DefaultBehavior注释,CoordinatorLayout的子view可以使用一个默认的behavior。
CoordinatorLayout的子view也许会有个anchor(锚点,即view显示在哪块区域)。
这个子view必须是CoordinatorLayout的直接子view,不能是孙子辈儿的view!
CoordinatorLayout工作原理
CoordinatorLayout的子View能够感知兄弟View的变化行为,design包的开发团队把其称为依赖(dependency)--我能感受到你的行为变化,就表示我依赖你。兄弟View之间存在一种依赖关系,CoordinatorLayout则维护这种依赖关系。
那么我们带着两个问题去探讨CoordinatorLayout:
1 兄弟View如何建立依赖关系?
2 CoordinatorLayout如何维护子View间的依赖关系?
使用场景
1 作为顶层应用程序布局装饰
2 作为协调一个或多个子View间交互的的容器。
如:通过Behavior可以用来实现各种交互和 来自滑动抽屉、滑动删除元素和按钮关联其他元素产生的额外的布局修改。
二、这篇文章涉及到的相关名词以及一些基本知识
1 先说一下View的功能
1:去测量,布局,绘制自己。
2:接收触摸事件去做出反应。
三、 Behavior
Behavior是核心设计,Behavior翻译过来就是行为。
/**
* Interaction behavior plugin for child views of {@link CoordinatorLayout}.
*
* <p>A Behavior implements one or more interactions that a user can take on a child view.
* These interactions may include drags, swipes, flings, or any other gestures.</p>
*
* @param <V> The View type that this Behavior operates on
*/
public static abstract class Behavior<V extends View> {
- CoordinatorLayout的子view的交互行为插件。
- 一个Behavior可以实现一个或更多的交互,用户可以将这些交互用在CoordinatorLayout的子view上,这些交互包括:拖拽、滑动、飞速滑动、或者其他任何手势。
Behavior如何协调CoordinatorLayout的子View的?
Behavior必须为View添加另外一种能力--就是接受CoordinatorLayout的协调通知,并对此作出反应
Paste_Image.png回顾上面的问题:兄弟View如何建立依赖关系?
通常依赖关系的主体有两个,一个是被依赖者,另一个是依赖者。前者报告自己的行为状态的变化,后者接收这种变化,做出反应。
Behavior产生原因
- 我们知道onInterceptTouchEvent和onTouchEvent是视图层级触摸事件分发机制的核心。这种机制简单的可以概括为:触摸事件从父视图流向子视图,在流动过程中,父视图可以观察事件,并决定是否拦截,一旦父视图决定拦截,将不会将事件在传递给子视图。这种流动是单向的,并且有单一终点--最终消耗事件的View。
- 这种事件分发机制显然不满足我们的要求。最直观的一点就是,但我们把手放在视图A滚动的时候,视图B也会联动,也就是说,同样的触摸事件被两个甚至多个视图使用了。当然,我们也能通过其他实现这种功能。只是我们需要的是一种具有普适性的解决方法,而不是每次需要这种功能就去写一遍同样逻辑的代码。因此Android团队把这种能力添加进了View System的最基本类--View类--中 。
- Android团队仍保留了之前的分发机制,只是在最后一个环节上做出了修改。我们这里说的最后一个环节是指,触摸事件到达了目标视图的onTouchEvent方法。之前的实现很简单就是被目标视图直接消耗掉,就一个步骤。而现在则变成了三个步骤:
1:目标视图先把事件回送到关心此事件的父视图,让其作出处理;
2:目标视图根据父视图的处理情况,调整自己的处理策略;
3:目标视图再次将自己消耗后的触摸事件传递给父视图,父视图根据需要作出处理。
- 我们可以形象一点形容以前的分发机制是一条直线,从父视图到子视图,而现在的机制就是一条有任意闭环的曲线,事件到达了子视图后能回流到父视图。有了这套机制解决我们之前的问题就很简单了--触摸事件到达了视图A,视图A在处理之前先交给关心这个事件的父视图,父视图则完全有能力把触摸事件发送给另外一个子视图B,传递的过程中有一些附带的信息可供父视图,A视图,B视图使用,利用这些信息整个视图层级的联动过程可以协调一致。
Behavior的接口
看完上面我们基本知道这个CoordinatorLayout的实现原理了,先看看Behavior有哪些接口?
onStartNestedScroll,onNestedScrollAccepted,onStopNestedScroll,onNestedScroll,onNestedPreScroll,onNestedFling
发现Behavior具有NestedScrollingParent接口中的同名方法,但是不是通过直接实现NestedScrollingParent接口得到的。而CoordinatorLayout则实现了NestedScrollingParent接口。所以结论:
CoordinatorLayout 就是能接收子视图回传事件的父视图,它将收到的事件又原封不动的转发给另外一个子视图。我们现在终于可以回答最初的问题了--Behavior如何为View赋予被依赖者的能力的。答案很简单!因为它能获得第一手的触摸事件啊。它能在第一时间反应,改变自己的位置或者大小,那么整个视图层级为了达到协调,必然会依赖它--通过它的位置调整自己的位置。
Behavior实现依赖过程
依赖者View只需要告诉CoordinatorLayout它依赖哪个兄弟视图,每当它所依赖的兄弟视图行为改变的时候,CoordinatorLayout都会通知依赖者,此时依赖者就能做出相应改变了。在实现上Behavior添加了layoutDependsOn,onDependentViewChanged,onDependentViewRemoved,让View具备依赖者的能力。
最后总结Behavior
1:修改View测量,布局的能力(onMeasureChild等方法);
2:拦截或消耗触摸事件的能力(onTouchEvent等方法);
3:赋予View被依赖者的能力(NestedScrollingParent接口同名方法);
4:赋予View依赖者的能力(layoutDependsOn等);
1,2两点是对View的自身的扩展,3,4是为了配合CoordinatorLayout的行为作出的扩展。
最后解决一个问题,如何维护依赖关系?
CoordinatorLayout的每一个子View都与一个CoordinatorLayout.LayoutParams相关,当然,这是View System本来就有的设计,而Behavior则是CoordinatorLayout.LayoutParams类的成员变量,这样一来,CoordinatorLayout的每一个子View都会与一个Behavior相关。就这样Behavior无缝的整合到了View System中。最让人拍案叫绝的是,我们自定义了一个Behavoir,我们可以把它附着到任何控件类中。想想我们以前怎么做--用继承的方式实现。Behavior是赋予控件新行为最一劳永逸的方法。一次编写,任何一个控件都能使用。
事件流
1).布局事件
在CoordinatorLayout的onMeasure和onLayout方法中,会通过Behavior询问子视图是否需要进行相应操作,即执行Behavior中对应的方法,分别是onMeasureChild与onLayoutChild。这里onMeasureChild与onLayoutChild都会分别比Child的onMeasure与onLayout两方法优先执行
onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection)
2).触摸事件
触摸事件就是Behavior中的onInterceptTouchEvent与onTouchEvent。注意这里,如果Behavior对触摸事件进行了拦截,那么后续事件将不会再分发到Child View自身的触摸事件中了。而且事件由CoordinatorLayout分发下来,所以这里的touch事件都是未知View的,所以需要额外判断当前的点击事件是不是由我们的控件触发的。
3)变化事件
这里需要穿插一个判断依赖对象的过程。之前我们已经提及过在自定义Behavior时要分2种情况去考虑
(1)某个view监听另一个view的状态变化,例如大小、位置、显示状态等
(2)某个view监听滑动嵌套里的滑动状态
第二种情况我们就不需要特别的去进行判断了。重点来说说第一种。
从之前的源码阅读中我们知道,CoordinatorLayout会将其子View遍历一遍,在遍历的过程中去不断的通知所有的Behavior,这样就会导致Behavior收到不一定是我们关心的滑动事件,所以我们可以根据情况使用类型或者ID去判断依赖属性,过滤掉不是我们关心的滑动事件