一、CoordinatorLayout簡介
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.support.design.widget.CoordinatorLayout
實作了NestedScrollingParent接口,CoordinatorLayout, NestedScrollView, SwipeRefreshLayout都實作了這個接口
CoordinatorLayout is a super-powered FrameLayout. 是以屬性和FrameLayout有點像
CoordinatorLayout 控件是design下最重要的一個控件,也是最複雜、功能最強大的,這從他的作用就可以看的出來
二、CoordinatorLayout作用
CoordinatorLayout is intended for two primary use cases:
- As a top-level application decor or chrome layout
- As a container for a specific interaction with one or more child views
上面是官方給的解釋,CoordinatorLayout的作用就兩個:
- 作為一個最頂層根布局因為CoordinatorLayout本身就是繼承ViewGroup
- 作為一個容器協調子View的行為(通過CoordinatorLayout.Behavior)
是以這裡最重要就是這個Behavior了,如果你完全掌握了Behavior,CoordinatorLayout就搞定了
三、CoordinatorLayout.Behavior研究
檢視CoordinatorLayout的源碼我們可以看到這是一個抽象類,裡面定義了很多方法,如果需要使用我們要繼承CoordinatorLayout.Behavior然後重新一些方法。
我們拿AppBarLayout為例,AppBarLayout中有兩個Behavior,一個是拿來給它自己用的,另一個是拿來給它的兄弟結點用的,我們重點關注下AppBarLayout.ScrollingViewBehavior這個類。
我們用到的appbar_scrolling_view_behavior指的也是AppBarLayout.ScrollingViewBehavior這個類
通過檢視ScrollingViewBehavior也是繼承CoordinatorLayout.Behavior
java.lang.Object
↳ android.support.design.widget.CoordinatorLayout.Behavior<V extends android.view.View>
↳ android.support.design.widget.AppBarLayout.ScrollingViewBehavior
我們看看CoordinatorLayout.Behavior類中的以下方法:
1、layoutDependsOn
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//如果dependency是AppBarLayout的執行個體,說明它就是我們所需要的Dependency
return dependency instanceof AppBarLayout;
}
這個方法告訴CoordinatorLayout,這個view是依賴AppBarLayout的,後續父親可以利用這個方法,查找到這個child所有依賴的兄弟結點。在CoordinatorLayout.Behavior直接return false;
2、onDependentViewChanged
//每次dependency位置發生變化,都會執行onDependentViewChanged方法
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
return false;
}
這個方法,可以在這個回調中記錄dependency的一些位置資訊,在onLayoutChild中利用儲存下來的資訊進行計算,然後得到自身的具體位置。
3、onLayoutChild
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
return false;
}
這個方法是用來子view用來布局自身使用,如果依賴其他view,那麼系統會首先調用
4、onMeasureChild
public boolean onMeasureChild(CoordinatorLayout parent, V child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
return false;
}
這個是CoordinatorLayout在進行measure的過程中,利用Behavior對象對子view進行大小測量的一個方法。
在這個方法内,我們可以通過parent.getDependencies(child);這個方法,擷取到這個child依賴的view,然後通過擷取這個child依賴的view的大小來決定自身的大小。
5、NestedScroll
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
V child, View directTargetChild, View target, int nestedScrollAxes) {
return false;
}
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child,
View directTargetChild, View target, int nestedScrollAxes) {
// Do nothing
}
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
// Do nothing
}
public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
// Do nothing
}
這幾個方法剛好是NestedScrollingParent的方法(CoodinatorLayout是繼承了NestedScrollingParent的),也就是對CoodinatorLayout進行的一個代理(Proxy),即CoordinatorLayout自己不對這些消息進行處理,而是傳遞給子view的Behavior,進行處理。利用這樣的方法,實作了view和view之間的互動和視覺的協同(布局、滑動)。
有興趣可以看 鴻 洋大神的NestedScrolling機制完全解析:
Android NestedScrolling機制完全解析 帶你玩轉嵌套滑動
四、自定義Behavior
實作如下紅色button,高度保持跟藍色View一樣,x軸方向相反
構造方法
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
DisplayMetrics display = context.getResources().getDisplayMetrics();
width = display.widthPixels;
}
一定要重寫這個構造函數。因為CoordinatorLayout源碼中parseBehavior()函數中直接反射調用這個構造函數。
static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] {
Context.class,
AttributeSet.class
};
然後我們實作layoutDependsOn、onDependentViewChanged方法,自定義Behavior就完成了
下面是MyBehavior 的源碼
public class MyBehavior extends CoordinatorLayout.Behavior<Button> {
private int width;
public MyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
DisplayMetrics display = context.getResources().getDisplayMetrics();
width = display.widthPixels;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, Button child, View dependency) {
//如果dependency是TempView的執行個體,說明它就是我們所需要的Dependency
return dependency instanceof FollowView;
}
//每次dependency位置發生變化,都會執行onDependentViewChanged方法
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, Button child, View dependency) {
//根據dependency的位置,設定Button的位置
int top = dependency.getTop();
int left = dependency.getLeft();
int x = width - left - child.getWidth();
int y = top;
setPosition(child, x, y);
return true;
}
private void setPosition(View v, int x, int y) {
CoordinatorLayout.MarginLayoutParams layoutParams = (CoordinatorLayout.MarginLayoutParams) v.getLayoutParams();
layoutParams.leftMargin = x;
layoutParams.topMargin = y;
v.setLayoutParams(layoutParams);
}
}
項目源碼
https://github.com/Yi520153/DesignCoordinatorLayout
總結
可以看到CoodinatorLayout給我們實作了一個可以被子view代理實作方法的一個布局。這和傳統的ViewGroup不同,子view從此知道了彼此之間的存在,一個子view的變化可以通知到另一個子view。CoordinatorLayout所做的事情就是當成一個通信的橋梁,連接配接不同的view。使用Behavior對象進行通信。
參考資料
CoordinatorLayout的使用如此簡單
關于CoordinatorLayout與Behavior的一點分析
CoordinatorLayout布局的使用方式
CoordinatorLayout與滾動的處理
Android Support Design 中 CoordinatorLayout 與 Behaviors 初探