一、CoordinatorLayout介紹
就像其名字一樣噩茄,CoordinatorLayout作用在于協(xié)調(diào)子 View 之間的聯(lián)動(dòng)添怔,在其出現(xiàn)之前,如要實(shí)現(xiàn)多 View 之間的聯(lián)動(dòng)弟劲,需持有所有 View 的引用,難免各種事件處理姥芥、計(jì)算且高度耦合兔乞,寫法難度大。
CoordinatorLayout提供了一種清爽的方式凉唐,解決了 View 之間聯(lián)動(dòng)問題庸追。
CoordinatorLayout的基本用法網(wǎng)上遍地都是,不再贅述台囱。主要好奇于它的強(qiáng)大與絲滑淡溯,想弄明白原理,參考了部分博客玄坦,然后觀摩一下源碼血筑,在此記錄一下绘沉。
二煎楣、重中之重Behavior
抽象的來講,就是協(xié)調(diào)者布局中子 view 應(yīng)當(dāng)遵守的行為(個(gè)人理解)车伞。
源碼中的注釋:
A {@link Behavior} that the child view should obey.
乍一聽可能懵逼择懂,但明白其作用于原理之后,就能逐漸領(lǐng)悟這句話的意義另玖。
1困曙、首先Behavior作用
CoordinatorLayout(以下簡寫Co)中定義了兩個(gè)概念 Child 與Dependency表伦,child 就是Co 中的 子 View,Dependency就是被 child 依賴的 其他 子View慷丽,當(dāng)Dependency發(fā)生某些行為(如拖動(dòng))蹦哼,就會(huì)通知 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ā)生改變時(shí)候執(zhí)行offsetChildAsNeeded方法(此方法無外乎是一些 View 的移動(dòng)啥的)锄俄。
至此Co通過 behavior 完成了一次協(xié)調(diào)作用局劲。
(behavior的作用遠(yuǎn)不止此,且其在 Co 中權(quán)限很高奶赠,有機(jī)會(huì)在做進(jìn)一步探討)
2鱼填、Behavior原理
1.Behavior是個(gè)啥,怎么初始化的毅戈?
點(diǎn)開 Co 源碼可發(fā)現(xiàn)Behavior是Co的一個(gè)抽象內(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何時(shí)調(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)系維護(hù)到mChildDag中。
mChildDag為DirectedAcyclicGraph類型可簡單理解為可以保存對應(yīng)關(guān)系的容器塑陵。
而prepareChildren在onMeasure第一行調(diào)用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
...
}
這下就了然了感憾,概括的說 就是 Co 在 onMeasure的時(shí)候首先會(huì)通過 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)頁面 滑動(dòng)扮碧,view繪制之前趟章,與 view removed的時(shí)候,回回調(diào)onDependentViewChanged慎王。這也正好解釋了 AppBarLayout 與ScrollingViewBehavior滑動(dòng)聯(lián)動(dòng)的原理蚓土。
三、總結(jié)
粗略的探討了CoordinatorLayout 通過 behavior 實(shí)現(xiàn)協(xié)調(diào)子 View 的基本原理赖淤。
通過自定義 behavior 可爽快的實(shí)現(xiàn)一些聯(lián)動(dòng)效果蜀漆。
但 通過源碼behavior在CoordinatorLayout中作用遠(yuǎn)不止此,且behavior權(quán)限很高咱旱,有機(jī)會(huì)再分析其他作用确丢。