Bottom Sheets 在 support design library v23.2 中提供支持。支持两种类型的bottom sheets:固定(persistent) 和 模态(modal)。固定的bottom sheets显示应用中的内容,modal sheets显示一个菜单或简单的对话框。
图 Persistent Modal Sheets(译者加)
图 Modal Sheets(译者加)
PERSISTENT MODAL SHEETS
这有两种方法创建persistent modal sheets。第一种方法是使用一个NestedScrollView
,然后把内容放到这个View中。第二种方法是使用一个RecyclerView嵌入到CoordinatorLayout
中。如果layout_behavior
使用的是预定义的@string/bottom_sheet_behavior
值,那RecyclerView
默认会隐藏。注意RecyclerView
应该使用wrap_content
而不是match_parent
,这可以让bottom sheet只出现在必要的空间而不是整个页面:
1 2 3 4 5 6 7 8 |
<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
包含一张图片和一个文本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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; } } |
然后创建adapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
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); } } |
bottom sheet默认情况下应该是隐藏的。我们需要点击事件触发显示和隐藏。注意: 不要尝试在OnCreate()
方法展开bottom sheet因为这个已知问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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
布局属性允许用户滑动bottom sheet隐藏。这还有其它的状态包括:STATE_DRAGGING
,STATE_SETTLING
和STATE_HIDDEN
。想要扩展阅读,你也可以看看另一篇bottom sheet教程。
MODAL SHEETS
Modal sheets基于Dialog Fragments可以从底部滑动。查看这篇指南学习怎样创建这些类型的fragments。不是继承自DialogFragment
,应该继承BottomSheetDialogFragment
。
高级BOTTOM SHEET示例
这有很多带有一个FAB复杂的bottom sheets的例子,可以跟随用户的滑动展开或收缩或状态过渡。最知名的例子就是Google地图的多相sheet:
下面的教程和示例应该可以帮助实现更复杂的效果:
- CustomBottomSheetBehavior Sample——演示了滑动bottom sheet时三态的改变。详细说明参考related stackoverflow post。
- Grafixartist Bottom Sheet Tutorial——关于bottom sheet滑动时怎样移动FAB的位置的教程。
- 你可以看看stackoverflow post关于怎样实现Google地图滚动时修改状态。
多多实验才能获取预期的效果。对于特定用例,你可以从下面列出的第三方类库中选择。
可选择的第三方BOTTOM SHEET
除了官方在design support library中提供的bottom sheet,这有几个非常受欢迎的可选择的第三方的类库,对于特定用例很方便使用和配置:
下面为最常见的选择和相关例子:
- AndroidSlidingUpPanel——广受欢迎的实现bottom sheet的方法被认为是最接近官方方法的选择。
- Flipboard/bottomsheet——除官方bottom sheet外另一个非常受欢迎的类库,在官方解决方案发布之前被广泛使用。
- ThreePhasesBottomSheet——利用第三方类库创建多相bottom sheet的示例代码。
- Foursquare BottomSheet Tutorial——概述了怎样使用第三方bottom sheets在Foursquare老版本中实现对应效果。
学习官方persistent modal sheets和第三方类库的实现,通过足够的实验你应该能实现任何你想要的效果。
Coordinated Layouts常见问题
CoordinatorLayout
很强大但刚开始很容易出错。如果你在使用过程中出现了问题,请查看下面的提示:
- 怎样有效地使用coordinator layout最好的例子是参考cheesesquare源码。这个仓库是Google保持更新的示例仓库代表coordinating behaviors的最佳实践。尤其是查看ViewPager list布局和详情页布局。拿你的源码和cheesesquare源码进行比较。
- 确保
app:layout_behavior="@string/appbar_scrolling_view_behavior"
属性应用到了CoordinatorLayout
的 直接子View。例如,这有一个下拉刷新布局SwipeRefreshLayout
中包含一个RecyclerView
,这个属性应该应用到SwipeRefreshLayout
而不是第二级子ViewRecyclerView
。 - 当coordinating发生在一个
ViewPager
包含fragment作为item的list和parent activity之间,你应该把app:layout_behavior
属性放在ViewPager
上(正如这个文件所示),因此pager内的滚动是向上突出的并且可以通过CoordinatorLayout
进行管理。注意你 不应该 把app:layout_behavior
属性放到fragment或list中的任何地方。 - 注意
ScrollView
无法和CoordinatorLayout
配合使用。你需要使用NestedScrollView
代替就像这个例子所示。把你的内容放到NestedScrollView
中并且应用app:layout_behavior
属性可以得到预期的效果。 - 确保你的activity或fragment的根布局是
CoordinatorLayout
。滚动不会响应到其它任何布局。
导致coordinating layouts出错的原因有很多种。当你遇到了请添加提示到这里。
自定义Behaviors
我们已经在CoordinatorLayout with Floating Action Buttons中讨论过一个自定义Behaviors的例子。
CoordinatorLayout是通过搜索在XML中定义了app:layout_behavior
属性或者在View类中添加@DefaultBehavior
注解包含CoordinatorLayout Behavior工作的。当发生滚动事件,CoordinatorLayout会尝试触发作为依赖声明的其它子View。
对于自定义CoordinatorLayout Behavior,应该实现layoutDependsOn() 和 onDependentViewChanged()。例如,AppBarLayout.Behavior定义了两个关键方法。这个behavior用于当滚动事件发生时触发AppBarLayout的改变。
1 2 3 4 5 6 7 8 9 10 11 |
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 } } |
理解怎样实现这些自定义行为最好的办法就是学习AppBarLayout.Behavior和FloatingActionButtion.Behavior的例子。
第三方滚动和视差
除了向上面所说使用CoordinatorLayout
,你也可以看看这些受欢迎的第三方类库对ScrollView
,ListView
,ViewPager
和RecyclerView
的滚动视差效果。
在AppBarLayout中引入Google地图
在这个issue已经明确目前无法在AppBarLayout
中支持Google Maps fragment。support design library v23.1.0中提供了setOnDragListener()
方法,如果在布局中需要拖拽效果这将会很有用。然而,正如这篇文章所说它并不会影响滚动。
参考
- http://android-developers.blogspot.com/2015/05/android-design-support-library.html
- http://android-developers.blogspot.com/2016/02/android-support-library-232.html
- http://code.tutsplus.com/articles/how-to-use-bottom-sheets-with-the-design-support-library--cms-26031