一、BottomSheet概述
BottomSheet 是 Android 应用中的一个 从屏幕底部向上滑出的面板。你可以把它想象成一个从底部弹出的"抽屉"或"菜单",用于向用户展示更多的内容、选项或操作,而无需完全跳转到另一个界面。
它就像一个更优雅、更现代的"对话框"(Dialog),但出现的位置和交互方式更加符合手机屏幕的底部操作习惯。
BottomSheet主要分为两种类型:
1. Standard / Persistent BottomSheet( 标准 / 持久型底部表单): 它作为主界面的一部分,用于展示补充内容 ,它不会打断用户的操作,用户可以同时与主界面和底部表单进行交互。
2. Modal BottomSheet:(模态底部表单): 它像一个底部弹出的菜单或对话框 ,用于让用户做出一个选择或执行一个操作。它会打断用户,强制用户必须与之交互。背景内容会被一层半透明的遮罩(Scrim)变暗,用户必须通过点击一个选项、点击取消按钮、点击遮罩区域或向下拖动来关闭它,才能返回主界面。
二、BottomSheet的实现
BottomSheet有三种实现方式:BottomSheetBehavior
,BottomSheetDialog
和 BottomSheetDialogFragment
。
2.1 BottomSheetBehavior
用于实现Standard / Persistent BottomSheet,一般直接作用在view上,在xml布局文件中直接对view设置属性,适用于复杂页面下的半屏弹出效果。
使用步骤
- 导入依赖
js
implementation 'com.google.android.material:material:1.9.0'
- 设置布局文件 需要定义一个作为BottomSheet 的View ,通常为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 所指定的高度,不会完全展开 |
- 在活动中调用
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,一般在Activity 或Fragment 中实例化 BottomSheetDialog
,设置内容视图,并处理点击事件。
使用步骤
- 创建布局文件
在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>
- 在Activity 或Fragment 中,实例化
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 的交互体验,适用于复杂场景。
相对于BottomSheetDialog ,BottomSheetDialogFragment
有完整的生命周期,可以自动处理配置变更,保留数据,还可以高度复用。
使用步骤
- 创建布局文件
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>
- 创建 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;
}
}
- 在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 中同样可以使用前面提到的常见方法和设置行为,也可以自定义对话框背景。