Android Design Library之四:BottomSheetDialog

##简介
在我们的开发中经常会遇到从底部弹出对话框的需求。在design包中,官方为我们提供了一种实现,就是BottomSheetDialog。它的使用和dialog一样,看下它的继承关系就知道了。
图片描述

  • 本文design包使用的版本为27.0.2

下面我们看下简单的使用代码。

1
2
3
BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(R.layout.bottom_dialog_view);
dialog.show();

是的。你没有看错,这些代码就够了。

BottomSheetDialog的兄弟还有个BottomSheetDialgFragment,来看下BottomSheetDialogFragment的代码。

1
2
3
4
5
6
7
8
public class BottomSheetDialogFragment extends AppCompatDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new BottomSheetDialog(getContext(), getTheme());
}
}

是的。你还是没有看错,这就是全部的BottomSheetDialogFragment的代码。内部就是个BottomSheetDialog,就不做过多说明了。

##代码实现
我们打开`BottomSheetDialog的代码查看的话会发现,实现也很简单。只有二百行代码。来看一下。
我们在使用的时候会调用。setContentView()方法,这里有三个重载的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void setContentView(@LayoutRes int layoutResId) {
super.setContentView(wrapInBottomSheet(layoutResId, null, null));
}
@Override
public void setContentView(View view) {
super.setContentView(wrapInBottomSheet(0, view, null));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(wrapInBottomSheet(0, view, params));
}

我们可以看到最后都调用了wrapInBottomSheet()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
// #1
final FrameLayout container = (FrameLayout) View.inflate(getContext(),
R.layout.design_bottom_sheet_dialog, null);
final CoordinatorLayout coordinator =
(CoordinatorLayout) container.findViewById(R.id.coordinator);
//#2
if (layoutResId != 0 && view == null) {
view = getLayoutInflater().inflate(layoutResId, coordinator, false);
}
//#3
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
mBehavior = BottomSheetBehavior.from(bottomSheet);
mBehavior.setBottomSheetCallback(mBottomSheetCallback);
mBehavior.setHideable(mCancelable);
if (params == null) {
bottomSheet.addView(view);
} else {
bottomSheet.addView(view, params);
}
//...省略部分代码
return container;
}

我们可以分两部分开,#2部分就是获取到设置的View这没什么好说的。 #1获取了个design_bottom_sheet_dialogFrameLayout。代码如下:

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
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<View
android:id="@+id/touch_outside"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:soundEffectsEnabled="false"
tools:ignore="UnusedAttribute"/>
<FrameLayout
android:id="@+id/design_bottom_sheet"
style="?attr/bottomSheetStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top"
app:layout_behavior="@string/bottom_sheet_behavior"/>
</android.support.design.widget.CoordinatorLayout>
</FrameLayout

这里就是一个FrameLayout嵌套了个CoordinatorLayout,再嵌套了一个View用于点击收起Dialog和底部的FrameLayout用来显示我们为BottomSheetDialog设置的视图。上面wrapInBottomSheet()也就大致完了。

那么到底是怎么做到从底部弹出的呢?这边就那几行没说的了嘛。还用想,肯定是他们“搞的鬼”。

1
mBehavior = BottomSheetBehavior.from(bottomSheet);

通过这一行我们创建了一个BottomSheetBehavior。而正式这个BottomSheetBehavior让对话框从底部弹出的。

1
2
3
4
5
6
7
@Override
protected void onStart() {
super.onStart();
if (mBehavior != null) {
mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}

Dialog展示的时候,就调用了。BottomSheetBehaviorsetState(BottomSheetBehavior.STATE_COLLAPSED),那为什么调用这个方法就能显示出来了呢?我们来看下BottomSheetBehavior的简单介绍,之后我们就明白了。

##BottomSheetBehavior

1
2
3
4
5
/**
* An interaction behavior plugin for a child view of {@link CoordinatorLayout} to make it work as
* a bottom sheet.
*/
public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {...}

这是官方的解释。主要是用来和CoordinatorLayout配合来实现底部展示效果的。
主要使用的函数是setState()方法,状态有如下几个:

  • STATE_DRAGGING: 被拖动的状态。
  • STATE_SETTLING: 拖拽松开之后到达终点位置(collapsed or expanded)前的状态
  • STATE_EXPANDED: 展开的状态
  • STATE_COLLAPSED: 折叠的状态
  • STATE_HIDDEN: 隐藏的状态。

在这里主要说一下STATE_COLLAPSEDSTATE_EXPANDED的区别。 再说区别之前我们需要先看一个参数,mPeekHeight这个参数就是用来设置STATE_COLLAPSED状态下,界面显示的高度的。当不设置的时候会显示 view的全部。STATE_EXPANDED状态下则会显示出View的全部。还有一点需要注意的是设置STATE_HIDDEN的时候需要将mHideable设置为true。设置mPeekHeightmHideable的方法都有两种,分别为:

  • set方法: setPeekHight(height) 和setHideable(hideable)
  • xml中定义参数:
    1
    2
    app:behavior_hideable="true"
    app:behavior_peekHeight="50dp"

通过设置不同的状态来展现出不同的效果。参考BottomSheetDialog的实现我们就可以实现一个自己的底部弹出效果了。获取Behavior我们可以使用:

1
mBehavior = BottomSheetBehavior.from(view);

BottomSheetDialog中我们在onStart()方法中调用了。mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED),这个时候,我们为dialog设置的界面就显示出来了。那么为什么没有折叠的效果呢?因为BottomSheetDialog中并没有设置mPeekHeight的值。前面说了。当没有设置peekHeight时,STATE_COLLAPSED状态下会显示View的全部。

参考:
BottomSheets的使用