一、CoordinatorLayout介紹
就像其名字一樣,CoordinatorLayout作用在于協(xié)調(diào)子 View 之間的聯(lián)動,在其出現(xiàn)之前,如要實現(xiàn)多 View 之間的聯(lián)動,需持有所有 View 的引用,難免各種事件處理、計算且高度耦合,寫法難度大。
CoordinatorLayout提供了一種清爽的方式,解決了 View 之間聯(lián)動問題。
CoordinatorLayout的基本用法網(wǎng)上遍地都是,不再贅述。主要好奇于它的強大與絲滑,想弄明白原理,參考了部分博客,然后觀摩一下源碼,在此記錄一下。
二、重中之重Behavior
抽象的來講,就是協(xié)調(diào)者布局中子 view 應(yīng)當(dāng)遵守的行為(個人理解)。
源碼中的注釋:
A {@link Behavior} that the child view should obey.
乍一聽可能懵逼,但明白其作用于原理之后,就能逐漸領(lǐng)悟這句話的意義。
1、首先Behavior作用
CoordinatorLayout(以下簡寫Co)中定義了兩個概念 Child 與Dependency,child 就是Co 中的 子 View,Dependency就是被 child 依賴的 其他 子View,當(dāng)Dependency發(fā)生某些行為(如拖動),就會通知 child 以便執(zhí)行相應(yīng)的改變。
而Behavior就提供了:
- 依賴關(guān)系的確定
- Dependency改變后的回調(diào)
以經(jīng)常使用的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方法為依賴關(guān)系true 表示依賴,意思是聲明此 behavior 的 view依賴 Co 的自view 中所有instanceof AppBarLayout的 view。
onDependentViewChanged方法就是dependency發(fā)生改變的回調(diào),意思就是dependency也就是AppBarLayout發(fā)生改變時候執(zhí)行offsetChildAsNeeded方法(此方法無外乎是一些 View 的移動啥的)。
至此Co通過 behavior 完成了一次協(xié)調(diào)作用。
(behavior的作用遠(yuǎn)不止此,且其在 Co 中權(quán)限很高,有機會在做進(jìn)一步探討)
2、Behavior原理
1.Behavior是個啥,怎么初始化的?
點開 Co 源碼可發(fā)現(xiàn)Behavior是Co的一個抽象內(nèi)部類
public static abstract class Behavior<V extends View> {
...
}
進(jìn)一步搜索,發(fā)現(xiàn) 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中調(diào)用parseBehavior初始化。
(還有一種通過注解初始化,有興趣可自行了解一下:getResolvedLayoutParams)
2.分析如何建立child 與dependency依賴關(guān)系
打開 Co 源碼 command+F 搜索layoutDependsOn,順藤摸瓜看都有何處調(diào)用:
發(fā)現(xiàn)在onChildViewsChanged與LayoutParams 中的方法dependsOn中調(diào)用
/**
* 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何時調(diào)用:
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,把所有的依賴關(guān)系維護到mChildDag中。
mChildDag為DirectedAcyclicGraph類型可簡單理解為可以保存對應(yīng)關(guān)系的容器。
而prepareChildren在onMeasure第一行調(diào)用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
...
}
這下就了然了,概括的說 就是 Co 在 onMeasure的時候首先會通過 behavior 把所有的 child 與Dependency的依賴關(guān)系保存起來(保存到mChildDag中)。
3.如何回調(diào)onDependentViewChanged
老方法 command+F,發(fā)現(xiàn)最終在兩處調(diào)用
onChildViewsChanged與dispatchDependentViewsChanged
后者為 public 提供給外部調(diào)用的方法,所以不用 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;
}
......
}
}
}
}
......
}
代碼過長,部分省略,果然是用腳指頭都能想到的循環(huán)遍歷調(diào)用。
繼續(xù)看onChildViewsChanged的調(diào)用:
@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);
}
}
無非是當(dāng)頁面 滑動,view繪制之前,與 view removed的時候,回回調(diào)onDependentViewChanged。這也正好解釋了 AppBarLayout 與ScrollingViewBehavior滑動聯(lián)動的原理。
三、總結(jié)
粗略的探討了CoordinatorLayout 通過 behavior 實現(xiàn)協(xié)調(diào)子 View 的基本原理。
通過自定義 behavior 可爽快的實現(xiàn)一些聯(lián)動效果。
但 通過源碼behavior在CoordinatorLayout中作用遠(yuǎn)不止此,且behavior權(quán)限很高,有機會再分析其他作用。