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、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。