一、CoordinatorLayout介绍
就像其名字一样,CoordinatorLayout作用在于协调子 View 之间的联动,在其出现之前,如要实现多 View 之间的联动,需持有所有 View 的引用,难免各种事件处理、计算且高度耦合,写法难度大。
CoordinatorLayout提供了一种清爽的方式,解决了 View 之间联动问题。
CoordinatorLayout的基本用法网上遍地都是,不再赘述。主要好奇于它的强大与丝滑,想弄明白原理,参考了部分博客,然后观摩一下源码,在此记录一下。
二、重中之重Behavior
抽象的来讲,就是协调者布局中子 view 应当遵守的行为(个人理解)。
源码中的注释:
A {@link Behavior} that the child view should obey.
乍一听可能懵逼,但明白其作用于原理之后,就能逐渐领悟这句话的意义。
1、首先Behavior作用
CoordinatorLayout(以下简写Co)中定义了两个概念 Child 与Dependency,child 就是Co 中的 子 View,Dependency就是被 child 依赖的 其他 子View,当Dependency发生某些行为(如拖动),就会通知 child 以便执行相应的改变。
而Behavior就提供了:
- 依赖关系的确定
- Dependency改变后的回调
以经常使用的AppBarLayout 中的ScrollingViewBehavior为例:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
// We depend on any AppBarLayouts
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
offsetChildAsNeeded(parent, child, dependency);
return false;
}
layoutDependsOn方法为依赖关系true 表示依赖,意思是声明此 behavior 的 view依赖 Co 的自view 中所有instanceof AppBarLayout的 view。
onDependentViewChanged方法就是dependency发生改变的回调,意思就是dependency也就是AppBarLayout发生改变时候执行offsetChildAsNeeded方法(此方法无外乎是一些 View 的移动啥的)。
至此Co通过 behavior 完成了一次协调作用。
(behavior的作用远不止此,且其在 Co 中权限很高,有机会在做进一步探讨)
2、Behavior原理
1.Behavior是个啥,怎么初始化的?
点开 Co 源码可发现Behavior是Co的一个抽象内部类
public static abstract class Behavior<V extends View> {
...
}
进一步搜索,发现 Behavior为 Co 的LayoutParams的成员变量:
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* A {@link Behavior} that the child view should obey.
*/
Behavior mBehavior;
...
LayoutParams(Context context, AttributeSet attrs) {
...
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
...
}
并在LayoutParams中调用parseBehavior初始化。
(还有一种通过注解初始化,有兴趣可自行了解一下:getResolvedLayoutParams)
2.分析如何建立child 与dependency依赖关系
打开 Co 源码 command+F 搜索layoutDependsOn,顺藤摸瓜看都有何处调用:
发现在onChildViewsChanged与LayoutParams 中的方法dependsOn中调用
/**
* Check if an associated child view depends on another child view of the CoordinatorLayout.
*
* @param parent the parent CoordinatorLayout
* @param child the child to check
* @param dependency the proposed dependency to check
* @return true if child depends on dependency
*/
boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency == mAnchorDirectChild
|| shouldDodge(dependency, ViewCompat.getLayoutDirection(parent))
|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}
onChildViewsChanged先忽略,再搜dependsOn何时调用:
private void prepareChildren() {
mDependencySortedChildren.clear();
mChildDag.clear();
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);
// Now iterate again over the other children, adding any dependencies to the graph
for (int j = 0; j < count; j++) {
if (j == i) {
continue;
}
final View other = getChildAt(j);
if (lp.dependsOn(this, view, other)) {
if (!mChildDag.contains(other)) {
// Make sure that the other node is added
mChildDag.addNode(other);
}
// Now add the dependency to the graph
mChildDag.addEdge(other, view);
}
}
}
// Finally add the sorted graph list to our list
mDependencySortedChildren.addAll(mChildDag.getSortedList());
// We also need to reverse the result since we want the start of the list to contain
// Views which have no dependencies, then dependent views after that
Collections.reverse(mDependencySortedChildren);
}
prepareChildren中可以看出通过遍历Co 中所有的 View,把所有的依赖关系维护到mChildDag中。
mChildDag为DirectedAcyclicGraph类型可简单理解为可以保存对应关系的容器。
而prepareChildren在onMeasure第一行调用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
...
}
这下就了然了,概括的说 就是 Co 在 onMeasure的时候首先会通过 behavior 把所有的 child 与Dependency的依赖关系保存起来(保存到mChildDag中)。
3.如何回调onDependentViewChanged
老方法 command+F,发现最终在两处调用
onChildViewsChanged与dispatchDependentViewsChanged
后者为 public 提供给外部调用的方法,所以不用 care。
final void onChildViewsChanged(@DispatchChangeEvent final int type) {
......
for (int i = 0; i < childCount; i++) {
......
// Update any behavior-dependent views for the change
for (int j = i + 1; j < childCount; j++) {
final View checkChild = mDependencySortedChildren.get(j);
final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
final Behavior b = checkLp.getBehavior();
if (b != null && b.layoutDependsOn(this, checkChild, child)) {
......
switch (type) {
case EVENT_VIEW_REMOVED:
// EVENT_VIEW_REMOVED means that we need to dispatch
// onDependentViewRemoved() instead
b.onDependentViewRemoved(this, checkChild, child);
handled = true;
break;
default:
// Otherwise we dispatch onDependentViewChanged()
handled = b.onDependentViewChanged(this, checkChild, child);
break;
}
......
}
}
}
}
......
}
代码过长,部分省略,果然是用脚指头都能想到的循环遍历调用。
继续看onChildViewsChanged的调用:
@Override
public void onNestedScroll(...){
......
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
@Override
public void onNestedPreScroll(...){
......
if (accepted) {
onChildViewsChanged(EVENT_NESTED_SCROLL);
}
}
@Override
public boolean onNestedFling(.....){
......
}
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
onChildViewsChanged(EVENT_PRE_DRAW);
return true;
}
}
@Override
public void onChildViewRemoved(View parent, View child) {
onChildViewsChanged(EVENT_VIEW_REMOVED);
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
}
}
无非是当页面 滑动,view绘制之前,与 view removed的时候,回回调onDependentViewChanged。这也正好解释了 AppBarLayout 与ScrollingViewBehavior滑动联动的原理。
三、总结
粗略的探讨了CoordinatorLayout 通过 behavior 实现协调子 View 的基本原理。
通过自定义 behavior 可爽快的实现一些联动效果。
但 通过源码behavior在CoordinatorLayout中作用远不止此,且behavior权限很高,有机会再分析其他作用。
网友评论