CoordinatorLayout簡介
CoordinatorLayout是一個繼承于ViewGroup的布局容器撤缴。CoordinatorLayout監(jiān)聽滑動子控件的滑動通過Behavior反饋到其他子控件并執(zhí)行一些動畫富蓄。簡單來說桨武,就是通過協(xié)調并調度里面的子控件或者布局來實現(xiàn)觸摸(一般是指滑動)產生一些相關的動畫效果鹏往。
其中,view的Behavior是通信的橋梁,我們可以通過設置view的Behavior來實現(xiàn)觸摸的動畫調度傻粘。
注意:滑動控件指的是:RecyclerView/NestedScrollView/ViewPager沼瘫,意味著ListView抬纸、ScrollView不行。
示例
下面介紹一些常見的示例晕鹊,進一步介紹CoordinatorLayout與其他控件的配合使用松却,從而做出更好的效果暴浦。
示例一、CoordinatorLayout與FloatingActionButton晓锻、Snackbar
FloatingActionButton隨列表滾動的動畫
在上一篇文章當中歌焦,我們使用CoordinatorLayout實現(xiàn)了FloatingActionButton隨著界面的滑動而進行相應的顯示與隱藏動畫的效果,我們可以自己通過監(jiān)聽滑動事件實現(xiàn)砚哆,同理独撇,我們也可以通過根布局使用CoordinatorLayout來實現(xiàn),給FloatingActionButton自定義了一個Behavior(實質也是監(jiān)聽滑動事件)躁锁。
Snackbar彈出之后被FloatingActionButton遮擋的問題
在一個界面中纷铣,既有FloatingActionButton,又有Snackbar的時候战转,那么Snackbar的彈出可能就會擋住FloatingActionButton搜立。例如:
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Snackbar snackbar = Snackbar.make(v, "是否打開GPS?", Snackbar.LENGTH_INDEFINITE);
snackbar.setAction("好的", new View.OnClickListener() {
@Override
public void onClick(View v) {
}
}).show();
}
});
那么最好的解決辦法就是使用CoordinatorLayout來作為根布局槐秧,從而解決這個問題啄踊。我們可以在Snackbar的showView方法中看到Behavior相關的操作:
final void showView() {
if (mView.getParent() == null) {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
// If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
final Behavior behavior = new Behavior();
behavior.setStartAlphaSwipeDistance(0.1f);
behavior.setEndAlphaSwipeDistance(0.6f);
behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
view.setVisibility(View.GONE);
dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
}
@Override
public void onDragStateChanged(int state) {
switch (state) {
case SwipeDismissBehavior.STATE_DRAGGING:
case SwipeDismissBehavior.STATE_SETTLING:
// If the view is being dragged or settling, cancel the timeout
SnackbarManager.getInstance().cancelTimeout(mManagerCallback);
break;
case SwipeDismissBehavior.STATE_IDLE:
// If the view has been released and is idle, restore the timeout
SnackbarManager.getInstance().restoreTimeout(mManagerCallback);
break;
}
}
});
clp.setBehavior(behavior);
// Also set the inset edge so that views can dodge the snackbar correctly
clp.insetEdge = Gravity.BOTTOM;
}
mTargetParent.addView(mView);
}
//...省略
}
在Snackbar的博客中,我們就已經知道了:Snackbar在選擇錨點的時候刁标,如果遇到了CoordinatorLayout颠通,那么就會默認選擇它作為最合適的父容器。
示例二膀懈、CoordinatorLayout與AppBarLayout聯(lián)合使用
AppBarLayout是一個繼承LinearLayout的布局容器顿锰,它與CoordinatorLayout聯(lián)合使用就可以實現(xiàn)一些動態(tài)效果(例如標題欄滑出去)。
在使用AppBarLayout的時候启搂,AppBarLayout里面的子控件需要設置一個scrollFlags屬性:
app:layout_scrollFlags="scroll"
flag包括:
scroll: 里面所有的子控件想要當滑出屏幕的時候view都必須設置這個flag硼控,沒有設置flag的view將被固定在屏幕頂部。
enterAlways:一旦往下滑胳赌,就會馬上出現(xiàn)
enterAlwaysCollapsed:當你的視圖設置了minHeight屬性的時候淀歇,那么視圖只能以最小高度進入,
只有當滾動視圖到達頂部時才擴大到完整高度匈织。
exitUntilCollapsed:滾動退出屏幕浪默,最后折疊在頂端。
snap:
實現(xiàn)標題欄滑出去效果
下面先來布局文件:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:paddingTop="?attr/actionBarSize">
<android.support.v7.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:divider="@drawable/abc_list_divider_mtrl_alpha"
app:dividerPadding="10dp"
app:showDividers="middle">
<!--這里放置大量控件使得NestedScrollView可以滑動-->
</android.support.v7.widget.LinearLayoutCompat>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:title="標題欄"
app:titleTextColor="#fff"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
根布局使用了CoordinatorLayout缀匕,然后放置了一個用于產生滾動的NestedScrollView纳决。這里需要說明的是,NestedScrollView相對于傳統(tǒng)的ScrollView來說乡小,NestedScrollView解決了一些事件沖突的問題(例如內嵌ListView)阔加。
NestedScrollView需要添加layout_behavior,告知CoordinatorLayout我是滑動的:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
然后NestedScrollView需要添加下面兩句屬性满钟,保證有上padding胜榔,同時滑動的時候可以滑到padding區(qū)域(不然的話胳喷,就會被Toolbar遮擋住了):
android:paddingTop="?attr/actionBarSize"
android:clipChildren="false"
android:clipToPadding="false"
然后我們需要一個標題欄,我們用AppBarLayout對Toolbar進行包裹:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:title="標題欄"
app:titleTextColor="#fff"/>
</android.support.design.widget.AppBarLayout>
被包裹的Toolbar需要設置scrollFlags屬性夭织,證明Toolbar可以滑出去吭露,并且回滑的時候可以馬上滑回來:
app:layout_scrollFlags="scroll|enterAlways"
AppBarLayout還可以添加其他各種各樣的控件,里面TabLayout等等尊惰,不需要滑出去的話就不添加scrollFlags讲竿,在下面一個部分會有所體現(xiàn)。
使用ViewPager+TabLayout+Fragment+AppBarLayout實現(xiàn)傳統(tǒng)的APP架構
同樣的弄屡,看布局文件:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:title="標題欄"
app:titleTextColor="#fff"
>
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="center"
app:tabIndicatorColor="#4ce91c"
app:tabIndicatorHeight="5dp"
app:tabMode="scrollable"
app:tabSelectedTextColor="#4ce91c"
app:tabTextColor="#ccc"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/vp"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
根布局還是用CoordinatorLayout题禀。然后寫一個全屏的ViewPager用來存放Fragment,由于CoordinatorLayout是支持ViewPager的膀捷,因此直接添加:
app:layout_behavior="@string/appbar_scrolling_view_behavior"
同理我們還需要一個AppBarLayout迈嘹,里面包裹標題欄Toolbar以及TabLayout,其中TabLayout沒有設置scrollFlags因此不會滑出去全庸。至于Java代碼這里就不再贅述了江锨。
示例三、CoordinatorLayout與CollapsingToolbarLayout結合使用
在示例二的基礎之上糕篇,增加一個CollapsingToolbarLayout:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:title="標題欄"
app:titleTextColor="#fff">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/test"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.5"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_collapseMode="parallax"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
屬性分析:
- 給ImageView設置的layout_collapseMode是CollapsingToolbarLayout特有的屬性。其中parallax是視差動畫效果酌心。layout_collapseParallaxMultiplier是設置視差動畫的程度拌消;none:沒有任何效果;pin:固定模式安券,在折疊的時候最后固定在頂端墩崩。
- contentScrim是折疊之后的背景顏色。
- layout_scrollFlags屬性需要給CollapsingToolbarLayout設置exitUntilCollapsed侯勉,使得頁面滑動的時候Toolbar可以留在頂端鹦筹。
自定義Behavior
自定義Behavior的情形:
- 某個View需要監(jiān)聽另外一個View的狀態(tài)(位置、大小址貌、顯示狀態(tài))铐拐。
- 某個View需要監(jiān)聽CoordinateLayout里面的滑動狀態(tài)。
情形一例子练对、兩個控件同時動作
這時候遍蟋,child需要監(jiān)聽dependency的狀態(tài),并且作出相應的動作螟凭。
- 需要重寫layoutDependsOn判斷監(jiān)聽誰虚青,這里可以巧妙利用Tag。
- 需要重寫onDependentViewChanged螺男,child的動作根據(jù)dependency的狀態(tài)進行相應棒厘。
代碼如下:
public class MyBehavior1 extends CoordinatorLayout.Behavior<View> {
private static final String TAG = MyBehavior1.class.getSimpleName();
public MyBehavior1(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 用來決定需要監(jiān)聽哪些控件或者容器的狀態(tài)(1.知道監(jiān)聽誰纵穿;2.什么狀態(tài)改變)
* CoordinatorLayout parent ,父容器
* View child, 子控件---需要監(jiān)聽dependency這個view的視圖們---觀察者
* View dependency奢人,你要監(jiān)聽的那個View
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child,
View dependency) {
//還可以根據(jù)ID或者TAG來判斷需要監(jiān)聽哪一個子控件
return (dependency instanceof TextView && dependency.getTag().toString().equals("dependent"))
|| super.layoutDependsOn(parent, child, dependency);
}
/**
* 當被監(jiān)聽的view發(fā)生改變的時候回調
* 可以在此方法里面做一些響應的聯(lián)動動畫等效果谓媒。
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
//獲取被監(jiān)聽的view的狀態(tài)---垂直方向位置
int offset = dependency.getTop() - child.getTop();
//讓被監(jiān)聽的child進行平移、旋轉等操作
ViewCompat.offsetTopAndBottom(child, offset);
child.animate().rotation(child.getTop() * 20);
return true;
}
}
布局文件如下:
<android.support.design.widget.CoordinatorLayout>
<TextView
android:id="@+id/tv_dependent"
android:tag="dependent"
android:text="被觀察--dependent"/>
<TextView
android:text="觀察者" app:layout_behavior="com.nan.advancedui.coordinatorlayout.mybehavior.MyBehavior1"
/>
</android.support.design.widget.CoordinatorLayout>
我們點擊tv_dependent达传,進行平移篙耗,那么觀察者就會平移和旋轉:
findViewById(R.id.tv_dependent).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewCompat.offsetTopAndBottom(v, 9);
}
});
情形二例子、兩個滑動控件同步
與FloatingActionButton的自定義Behavior一樣宪赶,需要重寫:
- onStartNestedScroll判斷需要監(jiān)聽什么方向的滑動宗弯。
- onNestedPreScroll、onNestedFling等進行相應動作搂妻。
例子如下:
public class MyBehavior2 extends CoordinatorLayout.Behavior<View> {
private static final String TAG = MyBehavior2.class.getSimpleName();
public MyBehavior2(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
View child, View directTargetChild, View target,
int nestedScrollAxes) {
return (nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL)
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout,
View child, View target, int dx, int dy, int[] consumed) {
int scrollY = target.getScrollY();
child.setScrollY(scrollY);
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout,
View child, View target, float velocityX, float velocityY,
boolean consumed) {
// 快速滑動的慣性移動(松開手指后還會有滑動效果)
((NestedScrollView) child).fling((int) velocityY);
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
}
看看布局文件:
<android.support.design.widget.CoordinatorLayout>
<android.support.v4.widget.NestedScrollView>
...省略
</android.support.v4.widget.NestedScrollView>
<android.support.v4.widget.NestedScrollView
app:layout_behavior="com.nan.advancedui.coordinatorlayout.mybehavior.MyBehavior2">
...省略
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
最終結果就是NestedScrollView同步的滑動蒙保。
如果覺得我的文字對你有所幫助的話,歡迎關注我的公眾號:
我的群歡迎大家進來探討各種技術與非技術的話題欲主,有興趣的朋友們加我私人微信huannan88邓厕,我拉你進群交(♂)流(♀)。