CoordinatorLayout 擴展了完成 Google's Material Design 中的多種滾動效果的能力。目前,此框架提供了幾種不須要寫任何自定義動畫代碼就能夠(使動畫)工做的方式。這些效果包括:html
來自 Google 的 Chris Banes 將 CoordinatorLayout
和 design support library 中其餘的特性放在一塊兒作了一個酷炫的 demo。前端
在 github 上能夠查看完整源碼。這個項目是最容易理解 CoordinatorLayout
的方式之一。java
首先要確保遵循 Design Support Library 的說明。react
CoordinatorLayout 能夠經過使用 layout_anchor
和 layout_gravity
屬性來建立懸浮效果。更多信息請參見 Floating Action Buttons 指南。android
當渲染一個 Snackbar 時,它一般出如今可見屏幕的底部。Floating action button 必須上移以便騰出空間。ios
只要 CoordinatorLayout 被用做主佈局,這個動畫效果就會自動出現。Float action button 有一個默認的 behavior 能夠在檢測到 Snackbar 被加入的同時將這個 button 向上移動 Snackbar 的高度。git
<android.support.design.widget.CoordinatorLayout
android:id="@+id/main_content"
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.v7.widget.RecyclerView
android:id="@+id/rvToDoList"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="16dp"
android:src="@mipmap/ic_launcher"
app:layout_anchor="@id/rvToDoList"
app:layout_anchorGravity="bottom|right|end"/>
</android.support.design.widget.CoordinatorLayout>
複製代碼
首先確保你使用的不是過期的 ActionBar。並確保遵循了 將 ToolBar 用做 ActionBar 指南。還要確保的是以 oordinatorLayout 做爲主佈局容器。github
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.CoordinatorLayout>
複製代碼
接下來,咱們必須使用一個叫作 AppBarLayout 的容器佈局來使 ToolBar 響應滾動事件:編程
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="@dimen/detail_backdrop_height"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
複製代碼
注意:根據官方的 Google 文檔,目前 AppBarLayout 須要做爲直接子元素被嵌入 CoordinatorLayout 中。後端
而後,咱們須要在 AppBarLayout 和 指望被滾動的 View 之間定義一個關聯。在 RecyclerView 或其餘相似 NestedScrollView 這樣的能夠嵌套滾動的 View 中加入 app:layout_behavior
。支持庫中有一個映射到 AppBarLayout.ScrollingViewBehavior 的特殊字符串資源 @string/appbar_scrolling_view_behavior
,它能夠在某個特定的 view 上發生滾動事件時通知 AppBarLayout
。Behavior 必須創建在觸發(滾動)事件的 view 上。
<android.support.v7.widget.RecyclerView
android:id="@+id/rvToDoList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
複製代碼
當 CoordinatorLayout 發現 RecyclerView 中聲明瞭這一屬性,它就會搜索包含在其下的其餘 view 看有沒有與這個 behavior 關聯的任何相關 view。在這種特殊狀況下 AppBarLayout.ScrollingViewBehavior
描述了 RecyclerView 和 AppBarLayout 之間的依賴關係。RecyclerView 上的任何滾動事件都將觸發 AppBarLayout 或任何包含在其中的 view 的佈局發生變化。
RecyclerView 的滾動事件觸發了 AppBarLayout
中用 app:layout_scrollFlags
屬性聲明的 view 發生變化:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
複製代碼
若要使任一滾動效果生效,必須啓用 app:layout_scrollFlags
屬性中的 scroll
標誌。這個標誌必須與enterAlways
、enterAlwaysCollapsed
、 exitUntilCollapsed
或者 snap
一同使用:
enterAlways
:向上滾動時 view 變得可見。此標誌在從一個列表的底部滑動而且但願只要一貫上滑動 Toolbar
就顯示這種狀況下是頗有用的。
Ps:這裏所說的 scrolling up 應該指的是 list 的滾動條向上滑動而不是上滑的手勢。
一般,只有當 list 滑到頂部的時候 Toolbar
纔會顯示,以下所示:
enterAlwaysCollapsed
:一般只有當使用了 enterAlways
,Toolbar
纔會在你向下滑的時候繼續展開:
假設你聲明瞭 enterAlways
而且已經設置了一個 minHeight
,你也可使用 enterAlwaysCollapsed
。若是這樣設置了,你的 view 只會顯示出這個最低高度。只有當滑到頭的時候那個 view 纔會展開到它的徹底高度:
exitUntilCollapsed
:當設置了 scroll
標誌時,下滑一般會引發所有內容的移動:
經過指定 minHeight
和 exitUntilCollapsed
,剩餘內容開始滾動以前將首先達到 Toolbar
的最小高度,而後退出屏幕:
snap
:使用這一選項將由其決定在 view 只有部分減時所執行的功能。若是滑動結束時 view 的高度減小的部分小於原始高度的 50%,那麼它將回到最初的位置。若是這個值大於它的 50%,它將徹底消失。
注意:在你腦海中要將使用了 scroll
標誌位的 view 放在首位。這樣,被摺疊的 view 將會首先退出,留下在頂部固定着的元素。
至此,你應該意識到這個 ToolBar 響應了滾動事件。
若是想建立摺疊 ToolBar 的效果,咱們必須將 ToolBar 包含在 CollapsingToolbarLayout 中:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"></android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
複製代碼
如今結果應該顯示爲:
一般,咱們會設置 Toolbar 的標題。如今,咱們須要在 CollapsingToolBarLayout 而不是 Toolbar 上設置標題。
CollapsingToolbarLayout collapsingToolbar =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbar.setTitle("Title");
複製代碼
注意,在使用 CollapsingToolbarLayout
的時候,應該如此文檔所述,將狀態欄設置成半透明(API 19)或者透明(API 21)的。特別是,應該在 res/values-xx/styles.xml
中設置如下樣式:
<!-- res/values-v19/styles.xml -->
<style name="AppTheme" parent="Base.AppTheme">
<item name="android:windowTranslucentStatus">true</item>
</style>
<!-- res/values-v21/styles.xml -->
<style name="AppTheme" parent="Base.AppTheme">
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
複製代碼
經過像上面那樣啓用系統欄的半透明效果,你的佈局會將內容填充到系統欄後面,所以你還必須在那些不想被系統欄覆蓋的佈局上使用 android:fitsSystemWindow
。另一種爲 API 19 添加內邊距來避免系統欄覆蓋 view 的方案能夠在這裏查看。
CollapsingToolbarLayout 可讓咱們作出更高級的動畫,例如使用一個在摺疊的同時能夠漸隱的 ImageView。在用戶滑動時,標題的高度也能夠改變。
要想建立這種效果的話,咱們須要添加一個 ImageView 並在 ImageView 標籤中聲明 app:layout_collapseMode="parallax"
屬性。
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways" />
<ImageView
android:src="@drawable/cheese_1"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"
android:minHeight="100dp" />
</android.support.design.widget.CollapsingToolbarLayout>
複製代碼
在 support design library 的 v23.2
版本中已經支持底部表了。支持的底部表有兩種類型:persistent 和 modal。Persistent 類型的底部表顯示應用內的內容,而 modal 類型的則顯示菜單或者簡單的對話框。
有兩種方法來建立 Persistent 形式的底部表。第一種是用 NestedScrollView
,而後就簡單地將內容嵌到裏面。第二種是額外建立一個嵌入 CoordinatorLayout
中的 RecyclerView
。若是 layout_behavior
是預約義好的 @string/bottom_sheet_behavior
,那麼這個 RecyclerView
默認是隱藏的。還要注意的是 RecyclerView
應該使用 wrap_content
而不是 match_parent
,這是一個新修改,爲的是讓底部欄只佔用必要的而不是所有空間:
<CoordinatorLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/design_bottom_sheet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="@string/bottom_sheet_behavior">
</CoordinatorLayout>
複製代碼
下一步是建立 RecyclerView
。咱們能夠建立一個簡單的只包含一張圖片和文字的 Item
,和一個能夠填充這些 items 的適配器。
public class Item {
private int mDrawableRes;
private String mTitle;
public Item(@DrawableRes int drawable, String title) {
mDrawableRes = drawable;
mTitle = title;
}
public int getDrawableResource() {
return mDrawableRes;
}
public String getTitle() {
return mTitle;
}
}
複製代碼
接着,建立適配器:
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
private List<Item> mItems;
public ItemAdapter(List<Item> items, ItemListener listener) {
mItems = items;
mListener = listener;
}
public void setListener(ItemListener listener) {
mListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.adapter, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.setData(mItems.get(position));
}
@Override
public int getItemCount() {
return mItems.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageView imageView;
public TextView textView;
public Item item;
public ViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
imageView = (ImageView) itemView.findViewById(R.id.imageView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
public void setData(Item item) {
this.item = item;
imageView.setImageResource(item.getDrawableResource());
textView.setText(item.getTitle());
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onItemClick(item);
}
}
}
public interface ItemListener {
void onItemClick(Item item);
}
}
複製代碼
底部表默認是被隱藏的。咱們須要用一個點擊事件來觸發顯示和隱藏。注意:因爲這個已知的 issue,所以不要嘗試在OnCreate()
方法中展開底部表。
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.design_bottom_sheet);
// Create your items
ArrayList<Item> items = new ArrayList<>();
items.add(new Item(R.drawable.cheese_1, "Cheese 1"));
items.add(new Item(R.drawable.cheese_2, "Cheese 2"));
// Instantiate adapter
ItemAdapter itemAdapter = new ItemAdapter(items, null);
recyclerView.setAdapter(itemAdapter);
// Set the layout manager
recyclerView.setLayoutManager(new LinearLayoutManager(this));
CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.main_content);
final BottomSheetBehavior behavior = BottomSheetBehavior.from(recyclerView);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(behavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
複製代碼
你能夠設置佈局屬性 app:behavior_hideable=true
來容許用戶也能夠經過滑動而隱藏底部表。還有一些其餘的屬性,包括:STATE_DRAGGING
,STATE_SETTLING
,和 STATE_HIDDEN
。更多內容,請看 底部表的另外一篇教程。
Modal 形式的底部表基本上是從底部滑入的 Dialog Fragments。關於如何建立這種類型的 fragment 能夠查看本文。你應該繼承 BottomSheetDialogFragment
而不是 DialogFragment
。
有不少複雜的使用了 floating action button 的底部表的例子,button 隨着用戶滑動或展開或收縮或改變表狀態。最著名的例子就是使用了多階表的 Google Maps:
下述教程和代碼示例能夠幫助你實現這些更加複雜的效果:
CustomBottomSheetBehavior Sample - 描述了在底部表滑動時三種狀態來回切換。參考相關 stackoverflow 博文。
Grafixartist Bottom Sheet Tutorial - 關於在底部表滑動時如何定位 floating action button 以及對其使用動畫的教程。
你能夠閱讀本文來進一步討論如何模擬 Google Map 滑動期間狀態改變的效果。
爲了獲得預期的效果可能須要至關多的實驗。對於某些特定的用例,你可能會發現下面列出的第三方庫是一種更簡單的選擇。
除了 design support library 中提供的官方底部表,有幾個可選的很是流行的第三方庫,他們在某些特定用法下更容易配置和使用:
如下是最多見的選擇和相關的例子:
在官方的 persistent modal 表和這些第三方的替代方案之間,你應該能夠經過足夠的實驗來實現任何想要的效果。
CoordinatorLayout
很是強大但容易出錯。若是你在使用 behavior 時遇到了問題,請查看下面的建議:
CoordinatorLayout
的直接子 view 上使用了 app:layout_behavior="@string/appbar_scrolling_view_behavior"
屬性。例如,在一個下拉刷新的例子中,這個屬性應該放在包含了 RecyclerView
的 SwipeRefreshLayout
中而不是第二層如下的後代中。ViewPager
的 fragment 和一個父 activity 之間使用協調時,你想像這裏描述的那樣在ViewPager
上添加 app:layout_behavior
屬性,認爲這樣就能夠將 pager 中的滾動事件向上傳遞而後就能夠被CoordinatorLayout
管理。可是,記住,你不該該將 app:layout_behavior
屬性放到 fragment 或者它內部列表上的任何一個位置。ScrollView
不能與 CoordinatorLayout
一塊兒使用。你將須要像這個示例中展現的那樣用 NestedScrollView
來代替。將你的內容包含在 NestedScrollView
中,而後在其上添加 app:layout_behavior
就會使你的滾動行爲預期工做。CoordinatorLayout
。滾動事件不會響應其餘任何佈局。使用 CoordinatorLayout 時出錯的方式有不少種,當你發現出錯時能夠在這裏添加提示。
CoordinatorLayout with Floating Action Buttons 這篇文章中討論了一個自定義 behavior 例子。
CoordinatorLayout 的工做方式是經過搜索全部在 XML 中靜態地使用 app:layout_behavior
標籤或者以編程的方式在 View 類中使用 @DefaultBehavior
註解裝飾而定義 CoordinatorLayout Behavior 的子 View。當滾動事件發生時,CoorinatorLayout 嘗試去觸發那些被聲明爲依賴項的子 View。
爲了定義你本身的 CoordinatorLayout Behavior,你應該實現 layoutDependsOn() 和 onDependentViewChanged() 這兩個方法。例如 AppBarLayout.Behavior 就定義了這兩個關鍵方法。此 behavior 用來在滾動事件發生時觸發 AppBarLayout 上的改變。
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
// check the behavior triggered
android.support.design.widget.CoordinatorLayout.Behavior behavior = ((android.support.design.widget.CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior();
if(behavior instanceof AppBarLayout.Behavior) {
// do stuff here
}
}
複製代碼
理解如何實現這些自定義的 behavior 最好方法是研究 AppBarLayout.Behavior 和 FloatingActionButtion.Behavior 這兩個示例。
除了使用上述的 CoordinatorLayout
,還能夠查看這些流行的第三方庫來實現 ScrollView
, ListView
, ViewPager
和RecyclerView
間的滾動和視差效果。
因爲這個已被確認的 issue,目前在 AppBarLayout
中還不支持使用 Google Map。在 v23.1.0 版本的 support design library 的更新中提供了一個 setOnDragListener()
方法,若是在此佈局中須要拖拽效果的話,這個方法將很是有用。然而,它彷佛不影響滾動,如這篇博文所述。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋Android、iOS、React、前端、後端、產品、設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。