好久没更了,这次带来一篇源码分析 因带有不少源码,我尽力编辑,发现在手机上的浏览效果依然不佳 所以,推荐在PC端阅读,或者点击原文阅读
Google推出Design库已经一年了,国内也出过一些文章关于CoordinateLayout,但是都是教你怎么使用,或者简单的自定义一些Behavior,并没有一篇文章深入去了解它的原理。
刚好这两天为了实现一个UI效果,看了CoordinateLayout
(后面简称Col)的官方文档以及源码,搞懂了它的原理,于是想着拿出来分享,特在此记录分享如何一步一步深入理解Col,希望可以填补这个空缺,也希望可以帮到你。
补充说明:
23.2.1
版本学习最好的习惯就是看官方文档,来看看Col的定义以及官网的介绍:
public class CoordinatorLayout
extends ViewGroup implements NestedScrollingParent
官网介绍:
CoordinatorLayout is a super-powered FrameLayout. CoordinatorLayout is intended for two primary use cases:
从定义可以看到Col继承自ViewGroup
,并且它被设计成一个top-level
的根布局,它本身只是一个ViewGroup,实现了NestedScrollingParent
接口,看似非常普通,
但是说CoordinatorLayout
是Design库最为重要的控件也不为过。
这里额外需要注意的是:
NestedScrollingParent
,所以当Col嵌套(作为一个子View)的时候会得不到你想要的效果,需要自己写一个Col去实现NestedScrollingChild
接口! NestedScrollingChild
接口的子View如:ListView
,ScrollView
在5.0以下版本跟Col是配合不了的需要使用RecyclerView
,NestedScrollView
才行why?它super-powered
在哪里呢?
Col最为重要的作用是:提供给子View实现各种交互的极大便利 直观的表现是我们可以使用Col非常方便地实现很多交互效果,具体效果可以看cheesesquare这个项目。
要知道,在没有Col的日子要实现简单的交互也不是件容易的事,需要通过各种回调/Event,相互回调,相互通知,甚至相互持有引用,复杂而且难以复用,但是现在有了Col,一切都变得方便了~
How?它是怎么做到的呢? 说到这里,不得不提到Col的静态内部类-→Behavior 接下去来了解一下它,老司机要开车了,快上车~
Behavior是什么,有什么作用?
Interaction behavior plugin for child views of CoordinatorLayout.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.
简单说,Behavior可以负责所有的交互甚至测量以及布局。
其实官网资料说得挺含蓄的,官方在Medium有一篇文章,叫:Intercepting everything with CoordinatorLayout Behaviors,私以为用这个标题来形容Behavior,再合适不过,intercepting-everything!(这篇文章讲得很好也很全面,极力推荐阅读)
拦截一切!!迫不及待进一步了解!!
app:app:layout_behavior="me.yifeiyuan.demo.HeaderBehavior"
DefaultBehavior
注解指定,比如@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)
第一种方式很简单,不多说,这里针对其他两种方式讲解一下,有一些注意点我们需要知道:
先撇一下Behavior
的定义以及其构造方法如下:
//定义 V 为泛型,可指定针对哪种类型的View
public static abstract class Behavior<V extends View>
//默认的构造方法
public Behavior() {}
// xml里使用
public Behavior(Context context, AttributeSet attrs) {}
当我们在Xml里指定的时候,在LayoutParams
的构造方法里会去调用parseBehavior
这个方法,parseBehavior
关键代码如下(不贴代码不行了,已尽量精简):
这里我们需要注意的是: 如果要在xml里使用Behavior 那么第二个构造方法必不可少,所以我们自定义Behavior的时候需要注意;另外你在xml定义的属性会传递到第二个构造方法里去,可以获取你在xml里配置的属性,非常方便,可以说考虑还是非常周到的
第三种通过注解的方式,又是在什么时候,怎么去实例化的呢?
在Col中的onMeasure
中会去调用prepareChildren
方法,而prepareChildren
方法又调用了一个叫getResolvedLayoutParams
的方法如下:
所以注解方式是在onMeasure
中通过getResolvedLayoutParams
去实例化的。
另外还需要知道的是,Behavior
是Col.LayoutParams的成员变量,那么也就是说只有当你的Behavior
设置给Col的 直接子View 才会有效果,这点要记住,不然徒劳无功。(Col的子View的子View就不要给它设置Behavior啦,没效果的)
以上需要牢记,不过仅仅知道这些显然是不够的!至少我不会到这里就停~
接下去继续深入阅读Behavior的源码一探究竟(一言不合就看源码)
在我阅读了Behavior
的源码后,我觉得非常有必要先搞清楚几个非常重要的概念。
弄清楚这些个概念后看源码会比较简单了,Behavior
除了构造方法外,有23个方法,限于篇幅与精力,我挑选几个最重要的方法来讲解,当然我不会死板的一个一个毫无逻辑地解释过去。
之前提到了child与dependency有着依赖关系,那么问题来了: 这个依赖关系是如何建立的?
在Behavior类中有个方法:
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)
它会被Behavior
的LayoutParamas
的dependsOn
方法调用:
boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency == mAnchorDirectChild
|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}
而LayoutParamas的dependsOn
方法会被Col调用,dependsOn
方法就是用来确定依赖关系的。
所以,最简单的确定依赖关系的方法是重写layoutDependsOn
方法,并在一定条件下返回true
即可确立依赖关系。
那为什么说一定条件呢?
比如FAB依赖于SnackBar
,是因为它在SnackBar
出现以及消失的时候需要改变自身的位置,所以FAB的layoutDependsOn
方法中对Snackbar.SnackbarLayout
返回了true,而没有依赖其他的控件:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent,FloatingActionButton child, View dependency) {
// We're dependent on all SnackbarLayouts (if enabled)
return SNACKBAR_BEHAVIOR_ENABLED && dependency instanceof Snackbar.SnackbarLayout;
}
另外需要注意的是:当确定依赖关系后,当dependency被布局(或测量)后child会紧接着被布局(或测量),Col会无视子view的顺序(原因是Col内有个ComparatormLayoutDependencyComparator
会按照依赖关系对所有的子View进行排序),这会影响它们的测量以及布局顺序
可以说layoutDependsOn
方法是自定义Behavior
最为重要的方法
建立起依赖关系之后呢?
想要做交互,似乎还缺点什么,我想在dependency发生变化的时候改变一下child,我该如何知道这个改变的时机呢?
其实不需要我们去主动获取去判断,Col跟Behavior已经帮我们做好了这一切,onDependentViewChanged
登场。
onDependentViewChanged
方法的定义:
/**
* Respond to a change in a child's dependent view
* This method is called whenever a dependent view changes in size or position outside
* of the standard layout flow. A Behavior may use this method to appropriately update
* the child view in response.
*/
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
return false;
}
简单来说就是,当我们的dependency
发生改变的时候,这个方法会调用,而我们在onDependentViewChanged
方法里做出相应的改变,就能做出我们想要的交互效果了!
可能你也注意到了onDependentViewChanged
方法是有返回值的
当我们改变了child的size
或者position
的时候我们需要返回true,差不多可以理解为 当我们的dependency
发生了改变,同样的,child也需要发生改变,这个时候我们需要返回true
提一下:onDependentViewChanged
方法是在Col的dispatchOnDependentViewChanged
里调用的
除了以上两个特别重要的方法外,Nested系列方法也非常重要,如onStartNestedScroll
和onStopNestedScroll
来监听嵌套滚动的开始和结束,不过限于篇幅,想再另外开篇去写,这里就不写了
另外还有onMeasureChild
,onLayoutChild
这个后面会讲。
我们知道,ViewGroup的测量,布局,事件分发都是需要自己处理的,那么Col究竟给了Behavior
什么特权,让它能够让它拦截一切?
让我们挨个一点一点看下去
直接备注在源码里了,不多说啦!~
onLayout
原理其实跟onMeasure
方法一样的。
在处理touch事件中,Col重写了onInterceptTouchEvent
和onTouchEvent
,另外,它们都调用了Col里定义的一个处理拦截的方法,performIntercept
(关键代码都在这方法之中),就看一下它们的实现吧:
onInterceptTouchEvent
的实现:
onTouchEvent
的实现:
可以看到,其实这两个方法做的事情并不多,其实都交给performIntercept
方法去做处理了!
performIntercept
的实现如下:
通过分析源码,可以知道,Col在关键的方法里把处理权优先交给了Behavior,所以才让Behavior拥有了拦截一切的能力,所以,原来是Col放任了Behavior!!~
Col以及Behavior的重要的几个环节分析完毕,相信大家看完后能够对它们有更深层次的了解,而不是仅仅停留在使用上面。
这篇文章断断续续写了快一个月,思路断断续续,也有几次推翻重来,原本也打算想讲得更多更细一些,只是限于篇幅与精力,最终的效果跟我最初的预期有所差距,可能也有些错误或者讲解不清晰的地方。
如果你发现任何错误,或者写得不好的地方,或者不理解的地方,非常欢迎批评指正,也非常欢迎吐槽!!!!
其实我还顺带看了AppBarLayout等的源码,如有可能,我还想把Design库下的所有控件都分析一遍。
感谢你的阅读。
Intercepting everything with CoordinatorLayout Behaviors
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0224/3991.html