一篇文章學會Coordinatorlayout+AppbarLayout
現如今,摺疊式佈局在App中相當常見,給人一種科技感,充滿良好的使用者體驗。Coordinatorlayout+AppbarLayout+CollapsingToolbarLayout這三個臭皮匠聯合起來用千變萬化,啊,我重來沒有見過如此超凡脫俗之效果。
網上大多來不來就將這仨揉在一起,佈局也是直接全部巢狀完成搬上來,但是你真的理解它們之間的協作關係嗎?相互聯動的原理是什麼呢?一個個控制元件都沒整明白寫出這個功能也沒有意義呀。那我就一個一個拆開來講,分別來個功能,再一個接一個拼接。接下來,讓我們一起走進它們的內心世界。
github程式碼直通車: ofollow,noindex">https://github.com/18380438200/CoordinatorlayoutFull
先上效果圖:

giphy的副本.gif
部落格講解demo地址: https://github.com/18380438200/MDView
ToolBar(因為涉及到,也一併講解)
從Android3.0後出現ActionBar,但是這效果,誰用誰知道啊。顏色不好看不說,佈局也是無法訂製,都不如自定義ActionBar的好。可見我的另一篇自定義[Actionbar] http://www.jianshu.com/p/43b51e1062f1 。
使用方式:
1.首先在Activity主題裡面將預設Actionbar改為NoActionbar
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
2.繫結toolbar ,setSupportActionBar(toolbar) 設定toolbar為標題欄
3.設定常用屬性:
toolbar.setNavigationIcon(int resId); toolbar.setLogo(int resId); toolbar.setTitle(""); toolbar.setSubtitle(""); toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener listener);
4.引用選單
@Override public boolean onCreateOptionsMenu(Menu menu) { //引入options選單 getMenuInflater().inflate(R.menu.menu,menu); return true; }
5.在menu資料夾中設定選單
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/menu_1" android:title="選單1" android:icon="@mipmap/make_music_voice_changer_female" app:showAsAction="collapseActionView"/> <item android:id="@+id/menu_2" android:title="選單2" android:icon="@mipmap/make_music_voice_changer_female" app:showAsAction="collapseActionView"/> <item android:id="@+id/menu_3" android:title="選單3" android:icon="@mipmap/make_music_voice_changer_female" app:showAsAction="collapseActionView"/> <item android:id="@+id/menu_4" android:title="選單4" android:icon="@mipmap/make_music_voice_changer_female" app:showAsAction="collapseActionView"/> </menu>
或者直接在佈局中新增子view使用
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" > <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="返回" android:textSize="13sp" android:textColor="@android:color/white" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="right" android:layout_centerHorizontal="true" android:layout_marginRight="6dp" android:gravity="center" android:padding="4dp" android:textColor="#fff" android:textSize="14sp" android:text="選單"/> </android.support.v7.widget.Toolbar>

showAsAction屬性
- ifRoom 會顯示在Item中,空間不足會將後面item收起來,如果已經有4個或者4個以上的Item時會隱藏在溢位列表中。
- never 永遠不會顯示。只會在藏出列表中顯示,而且只顯示標題,所以在定義item的時候,最好把標題都帶上。
- always 無論是否超出空間,總會顯示。
- withText withText值示意Action bar要顯示文字標題。Action bar會盡可能的顯示這個標題,但是,如果圖示有效並且受到Action bar空間的限制,文字標題有可能顯示不全。
-
collapseActionView 聲明瞭這個操作視窗應該被摺疊到一個按鈕中,當用戶選擇這個按鈕時,這個操作視窗展開。否則,這個操作視窗在預設的情況下是可見的,並且即便在用於不適用的時候,也要佔據操作欄的有效空間。
例如效果:
ifroom的效果
collapseActionView的效果
Coordinatorlayout :
定義:is a super-powered Framelayout
是一個超級有力量的爸爸,官方給的定義就足以證明它的強大。
作用:協調子view的相互關係,比如位置、大小,就像有幾個調皮孩子的爸爸,要管管孩子的行為。
Behavior:

Behavior的來源
開啟Coordinatorlayout看,Behavior是CoordinatorLayout的一個泛型抽象內部類(這麼長累不累呀),所以給子view新增layout_behavior屬性是來自於它。
我寫了一個例子來理解CoordinatorLayout的工作原理:

這是一個大叔跟隨女孩的故事
<android.support.design.widget.CoordinatorLayout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/coordinatorLayout" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context="com.example.md.mdview.CoordinatorLayoutActivity"> <View android:id="@+id/view_girl" android:layout_width="70dp" android:layout_height="70dp" android:layout_marginLeft="200dp" android:background="@mipmap/make_music_voice_changer_female" /> <View android:id="@+id/view_uncle" android:layout_width="100dp" android:layout_height="100dp" android:background="@mipmap/make_music_voice_changer_uncle" app:layout_behavior="com.example.md.mdview.RunBehavior"/> </android.support.design.widget.CoordinatorLayout>
佈局:兩個子view,操作viewgirl,viewuncle也會相應跟著走,這就要寫一個聯動關係,用自定義Behavior實現
public class RunBehavior extends CoordinatorLayout.Behavior<View>{ public RunBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { int top = dependency.getTop(); int left = dependency.getLeft(); ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) child.getLayoutParams(); params.topMargin = top - 400; params.leftMargin = left; child.setLayoutParams(params); return true; } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return true; } }
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) 方法:
根據條件過濾判斷返回值,返回true聯動,返回flase不聯動,即behavior不生效
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency)
當 dependency這個哥哥發生變化時, 另一個child弟弟也要跟著去玩
一個view根據另一個view的變化而變化, dependency被 child監聽
功能是child的y值永遠比dependency大400畫素(廢話,還用說嗎)
app:layout_behavior="com.example.md.mdview.RunBehavior"
這裡一定要寫上帶引數的構造方法,因為coordinatorlayout是根據反射(所以是包名.類名路徑)獲取這個behavior,是從這個構造方法獲得物件的,否則會報

image.png
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: params.leftMargin = (int) (event.getX() - viewGirl.getMeasuredWidth() / 2); params.topMargin = (int) (event.getY() - viewGirl.getMeasuredHeight() / 2); viewGirl.setLayoutParams(params); break; case MotionEvent.ACTION_MOVE: params.leftMargin = (int) (event.getX() - viewGirl.getMeasuredWidth() / 2); params.topMargin = (int) (event.getY() - viewGirl.getMeasuredHeight() / 2); viewGirl.setLayoutParams(params); break; } return true; }
最後是在介面監聽手指的位置,給viewGirl設定手指的位置,viewgril變化了,viewuncle也就隨之變化了。
好,在會了Coordinatorlayout的用法,最外層父佈局有了,該新增兩個子view了。這裡裡面分別加入AppbarLayout和NestedScrollView作子view,給NestedScrollView加上behavior,就可以讓AppbarLayout跟隨NestedScrollView的Behavior聯動。Android已經自帶了app:layout_behavior="@string/appbar_scrolling_view_behavior",只要滾動發生,就會給自己的子view(if
instance of Appbarlayout)新增滾動事件。不明白這倆控制元件緊接著看後面講解。
當前佈局變為:
<android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="50dp" android:background="#0e932e" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp" android:textColor="#000" android:padding="10dp" android:text=""/> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout>
NestedScrollView (viewgirl的角色)
NestedScrolling機制能夠讓父View和子View在滾動式進行配合,其基本流程如下:
當子view開始滾動之前,可以通知父View,讓其先於自己進行滾動;
子View自己進行滾動;子view滾動之後,還可以通知父view繼續滾動。
而要實現這樣的互動機制,首先父view要實現NestedScrollingParent介面,而子View需要實現NestedScrollingChild介面,在這套機制中子View是發起者,父view是接受回撥並做出響應的。
以下是幾個關鍵的類和介面
/** * NestedScrollView is just like {@link android.widget.ScrollView}, but it supports acting * as both a nested scrolling parent and child on both new and old versions of Android. * Nested scrolling is enabled by default. */ public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild, ScrollingView { static final int ANIMATED_SCROLL_GAP = 250; static final float MAX_SCROLL_FACTOR = 0.5f; private static final String TAG = "NestedScrollView"; /** * Interface definition for a callback to be invoked when the scroll * X or Y positions of a view change. * * <p>This version of the interface works on all versions of Android, back to API v4.</p> * * @see #setOnScrollChangeListener(OnScrollChangeListener) */ public interface OnScrollChangeListener { /** * Called when the scroll position of a view changes. * * @param v The view whose scroll position has changed. * @param scrollX Current horizontal scroll origin. * @param scrollY Current vertical scroll origin. * @param oldScrollX Previous horizontal scroll origin. * @param oldScrollY Previous vertical scroll origin. */ void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY); } private long mLastScroll; private final Rect mTempRect = new Rect(); private OverScroller mScroller; private EdgeEffect mEdgeGlowTop; private EdgeEffect mEdgeGlowBottom; ······
//主要介面
NestedScrollingChild
NestedScrollingParent
//幫助類
NestedScrollingChildHelper
NestedScrollingParentHelper
AppbarLayout (viewuncle的角色)
繼承自Linearlayout,且方向是vertical,它可以讓你定製當某個可滾動View的滾動手勢發生變化時,其內部的子View實現何種動作。
AppBarLayout子View的動作
內部的子View通過在佈局中加app:layout_scrollFlags設定執行的動作
·scroll :子view會跟隨滾動事件一起滾動,相當於新增到scrollview頭部
·enterAlways :只要螢幕下滑,view就會立即拉下出來。
·snap :這個屬性讓控制元件變得有彈性,如果控制元件下拉了75%的高度,就會自動展開,如果只有25%顯示,就會反彈回去關閉。(去試試支付寶首頁吧,就是加了彈性這個效果)
·exitUntilCollapsed :當scrollview滑到訂部,再將子view摺疊起來
·enterAlwaysCollapsed :當scrollview滑到底,再將子view展開
可以給ViewPager設定行為,就不需要使用NestedScrollView的滑動,實現與AppBarLayout聯動。
app:layout_behavior="@string/appbar_scrolling_view_behavior"
setExpande(boolean ) 設定展開和關閉狀態,預設有開關動畫
使用示例:

app:layout_scrollFlags="scroll"

app:layout_scrollFlags="scroll|enterAlways"
CollapsingToolbarLayout
CollapsingToolbarLayout作用是提供了一個可以摺疊的Toolbar,它繼承自FrameLayout。
CollapsingToolbarLayout屬性 含義
app:title 設定標題
app:collapsedTitleGravity="center" 設定標題位置
app:contentScrim 設定摺疊時toolbar的顏色,預設是colorPrimary的色值
app:statusBarScrim 設定摺疊時狀態列的顏色 ,預設是colorPrimaryDark的色值
app:layout_collapseParallaxMultiplier 設定視差
app:layout_collapseMode="parallax" 視差模式,在摺疊的時候會有個視差摺疊的效果
app:layout_collapseMode="pin" 固定模式,在摺疊的時候最後固定在頂端
使用示例:讓圖片摺疊,讓toolbar固定
<android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.design.widget.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="200dp" android:background="@mipmap/bg" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="50dp" android:background="#000" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout>

圖片摺疊,固定toolbar
新增flags可以設定系統狀態列為透明,如果最頂上是背景這樣用效果更佳
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); setContentView(R.layout.activity_main);
實現toolbar漸變顏色:AppbarLayout提供了滑動偏移監聽,偏移量除以appbar總高度可以得到當前滑動百分比。注意:這個verticalOffset是0或者負數,需要轉絕對值。
appbarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { //verticalOffset始終為0以下的負數 float percent = (Math.abs(verticalOffset * 1.0f)/appBarLayout.getTotalScrollRange()); } });
這Matial Design的設計真好,但是這名取得,一個個兒的也忒長了吧,google什麼時候把名字精簡了啊?
好了,以後會持續更新的,喜歡我就點我吧!