Android 开发之 BottomsheetDialogFragment 的使用

BottomSheetDialogFragment 继承自 AppCompatDialogFragment,官方解释为模态底部表,是 DialogFragment 的一个版本,它使用的是 BottomSheetDialog,而不是浮动对话框。BottomSheetDialogFragment 相对于其它对话框有着以下的优势:

1、拥有自己的生命周期;
2、可对整个页面进行折叠、展开和销毁;
3、可灵活使用自定义样式。

类继承关系

类继承关系

源码分析

package android.support.design.widget;

public class BottomSheetDialogFragment extends AppCompatDialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new BottomSheetDialog(getContext(), getTheme());
    }

}

BottomSheetDialogFragment 的父类是 AppCompatDialogFragment,重写了 onCreateDialog() 方法,返回了一个 BottomSheetDialog 实例,该 dialog 的主题形式来自于 getTheme() 方法,getTheme() 是 DialogFragment 的公共方法。
进入 BottomSheetDialog 的源码,dialog 的装载发生在 setContentView() 中:
setContentView()
wrapInBottomSheet()
并且在 onStart 方法里:
onStart()
因此,重写该方法可以定义 BottomSheetDialog 的初始状态。

布局、样式

在 design 包下,我们找到 design_bottom_sheet_dialog.xml,如下:
design_bottom_sheet_dialog

<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>

由上可知,装载 dialog 的容器为”design_bottom_sheet”,它指定了一个 style 。

我们可以通过自定义如下 Theme 来调整 BottomSheetDialog 的样式:bottomSheetStyle,ctrl + 单击进入该样式,再在打开的文件中搜索”bottomSheetStyle”,显示如下:
bottomSheetStyle
由图片可知,它引用于”@style/Widget.Design.BottomSheet.Modal”,继续进入,发现它包含了如下属性:
BottomSheet.Modal
很明显,background 属性定义了 dialog 的背景色,因此,我们可以重新定义这些属性,如下(注意 parent=”android:Widget”):

<style name="TransparentBottomSheetStyle" parent="Theme.Design.Light.BottomSheetDialog">
    <item name="bottomSheetStyle">@style/SheetStyle</item>
</style>

<style name="SheetStyle" parent="android:Widget">
    <item name="android:background">@android:color/transparent</item>
    <item name="behavior_peekHeight">auto</item>
    <item name="behavior_hideable">true</item>
    <item name="behavior_skipCollapsed">false</item>
</style>

基类封装

有以上的分析,我们可以封装一个基类,方便后续使用。

public class BaseFullBottomSheetFragment extends BottomSheetDialogFragment {
    /**
     * 顶部向下偏移量
     */
    private int topOffset = 0;
    private BottomSheetBehavior<FrameLayout> behavior;
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        if (getContext() == null) {
            return super.onCreateDialog(savedInstanceState);
        }
        return new BottomSheetDialog(getContext(), R.style.TransparentBottomSheetStyle);
    }
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }
    @Override
    public void onStart() {
        super.onStart();
        // 设置软键盘不自动弹出
        getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
        BottomSheetDialog dialog = (BottomSheetDialog) getDialog();
        FrameLayout bottomSheet = dialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
        if (bottomSheet != null) {
            CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) bottomSheet.getLayoutParams();
            layoutParams.height = getHeight();
            behavior = BottomSheetBehavior.from(bottomSheet);
            // 初始为展开状态
            behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    }
    /**
     * 获取屏幕高度
     *
     * @return height
     */
    private int getHeight() {
        int height = 1920;
        if (getContext() != null) {
            WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
            Point point = new Point();
            if (wm != null) {
                // 使用Point已经减去了状态栏高度
                wm.getDefaultDisplay().getSize(point);
                height = point.y - getTopOffset();
            }
        }
        return height;
    }
    public int getTopOffset() {
        return topOffset;
    }
    public void setTopOffset(int topOffset) {
        this.topOffset = topOffset;
    }
    public BottomSheetBehavior<FrameLayout> getBehavior() {
        return behavior;
    }
}

在使用时,只需重写 onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) 方法,传入自己的 dialog 界面:

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View dialogView = inflater.inflate(R.layout.dialog_monitor_detail, container, false);
    return dialogView;
}

可通过 Behavior 的方式关闭它:

if (getBehavior() != null) {
    getBehavior().setState(BottomSheetBehavior.STATE_HIDDEN);
}

设置全部展开时距离顶部的偏移量:

setTopOffset(100);

预览效果

全展开,无顶部偏移量:
全展开,无顶部偏移量
全展开,顶部偏移量为 200:
全展开,顶部偏移量为200

由于篇幅较小,暂不上传源码至 Github,本文将对持续更新。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐