当前位置: 代码迷 >> 综合 >> Bottom Sheets
  详细解决方案

Bottom Sheets

热度:60   发布时间:2024-01-11 22:07:59.0

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_DRAGGINGSTATE_SETTLINGSTATE_HIDDEN。想要扩展阅读,你也可以看看另一篇bottom sheet教程。

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,你也可以看看这些受欢迎的第三方类库对ScrollViewListViewViewPagerRecyclerView的滚动视差效果。

在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
  相关解决方案