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

- 本文
design包使用的版本为27.0.2
下面我们看下简单的使用代码。
BottomSheetDialog dialog = new BottomSheetDialog(this);
dialog.setContentView(R.layout.bottom_dialog_view);
dialog.show();
是的。你没有看错,这些代码就够了。
BottomSheetDialog的兄弟还有个BottomSheetDialgFragment,来看下BottomSheetDialogFragment的代码。
public class BottomSheetDialogFragment extends AppCompatDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new BottomSheetDialog(getContext(), getTheme());
}
}
是的。你还是没有看错,这就是全部的BottomSheetDialogFragment的代码。内部就是个BottomSheetDialog,就不做过多说明了。
代码实现
我们打开``BottomSheetDialog的代码查看的话会发现,实现也很简单。只有二百行代码。来看一下。 我们在使用的时候会调用。setContentView()`方法,这里有三个重载的方法:
@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()方法。
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_dialog的FrameLayout。代码如下:
<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()也就大致完了。
那么到底是怎么做到从底部弹出的呢?这边就那几行没说的了嘛。还用想,肯定是他们“搞的鬼”。
mBehavior = BottomSheetBehavior.from(bottomSheet);
通过这一行我们创建了一个BottomSheetBehavior。而正式这个BottomSheetBehavior让对话框从底部弹出的。
@Override
protected void onStart() {
super.onStart();
if (mBehavior != null) {
mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
当Dialog展示的时候,就调用了。BottomSheetBehavior的setState(BottomSheetBehavior.STATE_COLLAPSED),那为什么调用这个方法就能显示出来了呢?我们来看下BottomSheetBehavior的简单介绍,之后我们就明白了。
BottomSheetBehavior
/**
* 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_COLLAPSED和STATE_EXPANDED的区别。 再说区别之前我们需要先看一个参数,mPeekHeight这个参数就是用来设置STATE_COLLAPSED状态下,界面显示的高度的。当不设置的时候会显示 view的全部。STATE_EXPANDED状态下则会显示出View的全部。还有一点需要注意的是设置STATE_HIDDEN的时候需要将mHideable设置为true。设置mPeekHeight和mHideable的方法都有两种,分别为:
set方法: setPeekHight(height) 和setHideable(hideable)xml中定义参数:
app:behavior_hideable="true"
app:behavior_peekHeight="50dp"
通过设置不同的状态来展现出不同的效果。参考BottomSheetDialog的实现我们就可以实现一个自己的底部弹出效果了。获取Behavior我们可以使用:
mBehavior = BottomSheetBehavior.from(view);
在BottomSheetDialog中我们在onStart()方法中调用了。mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED),这个时候,我们为dialog设置的界面就显示出来了。那么为什么没有折叠的效果呢?因为BottomSheetDialog中并没有设置mPeekHeight的值。前面说了。当没有设置peekHeight时,STATE_COLLAPSED状态下会显示View的全部。
参考: BottomSheets的使用