【Android】BottomSheet

一、BottomSheet概述

BottomSheet 是 Android 应用中的一个 从屏幕底部向上滑出的面板。你可以把它想象成一个从底部弹出的"抽屉"或"菜单",用于向用户展示更多的内容、选项或操作,而无需完全跳转到另一个界面。

它就像一个更优雅、更现代的"对话框"(Dialog),但出现的位置和交互方式更加符合手机屏幕的底部操作习惯。

BottomSheet主要分为两种类型:
1. Standard / Persistent BottomSheet( 标准 / 持久型底部表单): 它作为主界面的一部分,用于展示补充内容 ,它不会打断用户的操作,用户可以同时与主界面和底部表单进行交互。
2. Modal BottomSheet:(模态底部表单): 它像一个底部弹出的菜单或对话框 ,用于让用户做出一个选择或执行一个操作。它会打断用户,强制用户必须与之交互。背景内容会被一层半透明的遮罩(Scrim)变暗,用户必须通过点击一个选项、点击取消按钮、点击遮罩区域或向下拖动来关闭它,才能返回主界面。

二、BottomSheet的实现

BottomSheet有三种实现方式:BottomSheetBehaviorBottomSheetDialogBottomSheetDialogFragment

2.1 BottomSheetBehavior

用于实现Standard / Persistent BottomSheet,一般直接作用在view上,在xml布局文件中直接对view设置属性,适用于复杂页面下的半屏弹出效果。

使用步骤

  1. 导入依赖
js 复制代码
implementation 'com.google.android.material:material:1.9.0'
  1. 设置布局文件 需要定义一个作为BottomSheetView ,通常为FrameLayout 或其他可容纳内容的容器,这个容器要在CoordinatorLayout之下。

核心是使用app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"这个属性

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/button_1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Button 1"
            android:padding="16dp"
            android:layout_margin="8dp"
            android:textColor="@android:color/white"
            android:background="@android:color/holo_green_dark"/>
    </LinearLayout>
    <FrameLayout
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_orange_light"
        app:behavior_peekHeight="100dp"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="BottomSheet"
            android:padding="16dp"
            android:textSize="16sp"/>
    </FrameLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

这里给出一些重要的属性

app:behavior_hideable app:behavior_peekHeight
是否允许用户通过下滑关闭底部抽屉 设置BottomSheet在折叠时候的高度
app:behavior_skipCollapsed app:behavior_fitToContents
当此属性设置为 true 时,用户无法通过向下滑动将底部抽屉折叠到 peekHeight 以下的高度 当设置为 true 时,底部抽屉会固定在 peekHeight 所指定的高度,不会完全展开
  1. 在活动中调用
java 复制代码
public class MainActivity extends AppCompatActivity {
    private BottomSheetBehavior mBottomSheetBehavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        View bottomsheet = findViewById(R.id.bottom_sheet);
        //方法获取底部抽屉
        mBottomSheetBehavior = BottomSheetBehavior.from(bottomsheet);
        //设置底部抽屉可隐藏,即允许用户通过向下滑动关闭底部抽屉
        mBottomSheetBehavior.setHideable(true);
        //设置底部抽屉初始为隐藏状态
        mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
        Button button = findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //判断当前状态进行状态转换
                if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN ||
                        mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
                    mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                } else {
                    mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
                }
            }
        });
    }
}

BottomSheet有五种状态:

  • STATE_HIDDEN :隐藏状态,关联的View此时并不是GONE,而是此时在屏幕最下方之外,此时只是无法肉眼看到。
  • STATE_COLLAPSED :折叠状态,一般是一种半屏形态。
  • STATE_EXPANDED:完全展开,完全展开的高度是可配置,默认为屏幕高度。
  • STATE_DRAGGING:拖拽状态,标识人为手势拖拽中。
  • STATE_SETTLING :视图从脱离手指自由滑动到最终停下的这一小段时间,与STATE_DRAGGING差异在于当前并没有手指在拖拽。主要表达两种场景:初始弹出时动画状态、手指手动拖拽释放后的滑动状态。

BottomSheetBehavior 并没有直接提供点击视图外部进行关闭的功能或属性,可以设置监听底部抽屉以外的区域的点击事件,并在点击事件发生时隐藏底部抽屉来实现类似的功能。

java 复制代码
View rootLayout = findViewById(R.id.main); //获取根布局
rootLayout.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
        }
    }
});

2.2 BottomSheetDialog

用于实现 Modal BottomSheet,一般在ActivityFragment 中实例化 BottomSheetDialog,设置内容视图,并处理点击事件。

使用步骤

  1. 创建布局文件
    在xml文件中设置你希望在对话框中显示的内容布局。
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/textview_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TextView  1"
        android:padding="16dp"
        android:layout_margin="8dp"
        android:textColor="@android:color/white"
        android:background="@android:color/holo_green_dark"/>
</LinearLayout>
  1. ActivityFragment 中,实例化 BottomSheetDialog,设置内容视图,并处理点击事件。
java 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        // 初始化 BottomSheetDialog
        BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
        // 设置对话框的布局文件
        View bottomSheetView = getLayoutInflater().inflate(R.layout.bottomsheetdialog_layout, null);
        bottomSheetDialog.setContentView(bottomSheetView);
        // 设置点击外部区域关闭
        bottomSheetDialog.setCanceledOnTouchOutside(true);
        Button button2 = findViewById(R.id.button_1);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //         显示 BottomSheetDialog
                bottomSheetDialog.show();
                // 可选:设置行为,必须在show之后加载
                View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
                if (bottomSheetInternal != null) {
                    BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheetInternal);
                    behavior.setPeekHeight(500);
                }
            }
        });
    }
}

常见方法:

setContentView(int layoutResId): 设置底部抽屉对话框的布局文件。 setCanceledOnTouchOutside(boolean cancel): 设置是否允许点击对话框外部区域来关闭对话框。 show(): 显示底部抽屉对话框。 dismiss(): 关闭底部抽屉对话框。 setOnShowListener(DialogInterface.OnShowListener listener): 设置对话框显示时的监听器。 setOnDismissListener(DialogInterface.OnDismissListener listener): 设置对话框关闭时的监听器。

还可以对对话框进行优化,比如设置圆角:

先在drawable下新建一个文件,在其中设置shape

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="@android:color/white"/> <!-- 背景色 -->
    <corners
        android:topLeftRadius="16dp"
        android:topRightRadius="16dp"/>
</shape>

之后将响应视图的背景设置为它

xml 复制代码
android:background="@drawable/shape_bottom_sheet_background"

就可以使用了

2.3 BottomSheetDialogFragment

BottomSheetDialogFragment 是继承自 DialogFragment 的特殊 Fragment,它专门用于创建Modal BottomSheet 。它结合了 Fragment 的生命周期管理能力和 BottomSheet 的交互体验,适用于复杂场景。

相对于BottomSheetDialogBottomSheetDialogFragment有完整的生命周期,可以自动处理配置变更,保留数据,还可以高度复用。

使用步骤

  1. 创建布局文件
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:background="@color/white"
    android:layout_height="600dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这里是底边框中内容"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>
  1. 创建 BottomSheetDialogFragment 类
java 复制代码
public class MyBottomSheetDialogFragment extends BottomSheetDialogFragment {
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        //判断当前是否有关联活动,如果没有就调用父类方法创建一个默认对话框
        if (getActivity() == null) return super.onCreateDialog(savedInstanceState);

        //在这里创建对象返回在后续可以直接调用
        BottomSheetDialog dialog = new BottomSheetDialog(getActivity(), R.style.Base_Theme_Bottom);

        //加载这个布局
        dialog.setContentView(R.layout.bottomsheet_fragment);
        //android.R.id.content 是Android系统提供的一个预定义资源ID,它代表Activity或Dialog的内容区域的根视图。
        View bottomSheetView = dialog.findViewById(android.R.id.content);
        TextView textView=bottomSheetView.findViewById(R.id.text);
        textView.setText("1");

        return dialog;
    }
}
  1. 在Activity中使用
java 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        Button  button= findViewById(R.id.button_1);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //getSupportFragmentManager(),得到一个 FragmentManager 对象,用来管理 Fragment
                //"tag"是一个标识,方便FragmentManager使用findFragmentByTag(String tag)来找到这个Fragment,这个参数可以为空
                new MyBottomSheetDialogFragment().show(getSupportFragmentManager(), "tag");
            }
        });
    }

在 BottomSheetDialogFragment 中同样可以使用前面提到的常见方法和设置行为,也可以自定义对话框背景。

相关推荐
CYRUS_STUDIO2 小时前
一步步带你移植 FART 到 Android 10,实现自动化脱壳
android·java·逆向
练习时长一年2 小时前
Spring代理的特点
java·前端·spring
CYRUS_STUDIO2 小时前
FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器
android·java·逆向
MisterZhang6663 小时前
Java使用apache.commons.math3的DBSCAN实现自动聚类
java·人工智能·机器学习·自然语言处理·nlp·聚类
Swift社区3 小时前
Java 常见异常系列:ClassNotFoundException 类找不到
java·开发语言
一只叫煤球的猫4 小时前
怎么这么多StringUtils——Apache、Spring、Hutool全面对比
java·后端·性能优化
维基框架5 小时前
维基框架 (Wiki FW) v1.1.1 | 企业级微服务开发框架
java·架构
10km6 小时前
jsqlparser(六):TablesNamesFinder 深度解析与 SQL 格式化实现
java·数据库·sql·jsqlparser
是2的10次方啊6 小时前
Java多线程基础:进程、线程与线程安全实战
java