使用Android Studio实现底片对话框
底部表单对话框似乎正在取代常规的Android对话框和菜单。底部工作表是一个组件,它从屏幕底部滑上来,展示你的应用程序中的额外内容。
底部工作表对话框就像一个由用户行为触发的消息框。谷歌、Facebook和Twitter等公司已经在其应用程序中实现了这一功能。
底部工作表对话框被用于音乐、支付和文件管理应用程序中。在应用方面,任何Android view ,包括TextView 、ImageView 、RecyclerViews 、Buttons 、Text inputs ,都可以包含在底层表单中。这使得它相当有活力。
例如,它可以帮助显示从数据库中检索的数据。只要适合你的应用周期,底部工作表可以在许多情况下应用。
底层工作表对话框的类型
底层表单的两种主要类型是模式化和持久化对话框。
模式化的底层表单对话框
它具有与警报对话框类似的特征。当被触发时(通过用户的操作),它从当前屏幕的底部滑上来。一个模态工作表可以包含一个项目列表。
这些元素在被点击时可以对应于一些动作。模态底部工作表阻止与屏幕其他部分的交互,以表示焦点的转移。当用户点击视图之外或按下返回键时,它就会被解除。
我们没有像持久性对话框那样用CoordinatorLayout 来包装模态底层表单,而是动态地创建它,就像一个普通的对话框。
一个优秀的模态底层表单对话框的例子是Google Drive应用程序。

或者这个支付底层表单对话框的例子。
.
模态底层表单是内联菜单和简单对话框的一个优秀替代方案。它们为更多的内容、图标和更多的屏幕操作提供了额外的空间。
持久的底层表单对话框
持久底层表单对话框提供关于当前屏幕的补充内容。它是作为CoordinatorLayout 的子女。
容器的一部分是可见的,为用户提供更多的内容。与Modal对话框不同,Persistent Bottom Sheet小组件对当前屏幕内容是永久性的。
下面是一个Google Maps应用程序中的Persistent Bottom Sheet对话框的例子。

实施
创建一个新的Android studio项目。为了实现底部表单对话框,你需要一个材质设计库。
在你的app.gradle 文件中包括以下库。
implementation 'com.google.android.material:material:1.2.1'
同步该项目以下载该库。这将使所有需要的功能在你的项目中可用。
我们将讨论如何使用Android studio实现两种类型的底部表单对话框。
实现模式化的底层页面对话框
- 使用BottomsheetDialog
准备布局
为了显示对话框,你需要一个XML文件来安排对话框的内容。你可以选择使用任何适合对话框的小部件。视图可以包括RecyclerView,ImageViews,Text,Inputs, 和Button 。
确保你生成一些矢量图像,就像下面XML文件的ImageView中展示的那样。
这是bottom_sheet_dialog_layout.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:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/download"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_cloud_download_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Download File"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/shareLinearLayout"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_share_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Share"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/uploadLinearLaySout"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_add_to_drive_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upload To Google Drive"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/copyLinearLayout"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_file_copy_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Copy Link"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/delete"
android:background="?android:attr/selectableItemBackground"
android:padding="8dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_baseline_delete_24"
android:layout_margin="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete File Selection"
android:layout_gravity="center_vertical"
android:padding="8dp"/>
</LinearLayout>
</LinearLayout>
对话框是由一个指定的用户动作触发的。在本教程中,我们将包括一个按钮,并使用它的onClick Listener来触发对话框。
继续并在你的activity_main.xml 文件中添加一个按钮。
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
android:backgroundTint="@color/purple_500"
android:fontFamily="serif"
android:text="Show Dialog"
android:textColor="#FFFFFF"
android:textSize="18sp"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="116dp"
tools:layout_editor_absoluteY="28dp"/>
在活动中初始化底片
初始化按钮并在onCreate 函数中设置onClick监听器。当按钮被点击时,我们将显示对话框。创建一个函数showBottomSheetDialog() ,并在按钮的onClick 监听器内调用它,如下所示。
Button mBottton = findViewById(R.id.button);
mBottton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showBottomSheetDialog()
}
});
在showBottomSheetDialog() 函数里面,初始化底片对话框。使用setContentView 方法初始化bottom_sheet_dialog_layout.xml 。
声明所有的视图,并按照Bottom Sheet布局中的规定通过id 来调用它们。最后,我们将使用bottomSheetDialog.show() 来显示对话框。
private void showBottomSheetDialog() {
final BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
bottomSheetDialog.setContentView(R.layout.bottom_sheet_dialog);
LinearLayout copy = bottomSheetDialog.findViewById(R.id.copyLinearLayout);
LinearLayout share = bottomSheetDialog.findViewById(R.id.shareLinearLayout);
LinearLayout upload = bottomSheetDialog.findViewById(R.id.uploadLinearLayout);
LinearLayout download = bottomSheetDialog.findViewById(R.id.download);
LinearLayout delete = bottomSheetDialog.findViewById(R.id.delete);
bottomSheetDialog.show();
}
运行该应用程序以测试Bottom Sheet是否正常工作。点击按钮应该触发对话框从底部滑到顶部。

设置onClick监听器
对话框布局中的每个元素都可以被分配一个动作。当一个项目被点击时,它将按照代码中的指定重定向用户。
在我们的应用程序中,当一个元素被点击时,我们将显示一个Toast消息。你可以在你未来的应用程序中进行修改,将用户引导到不同的活动。
在bottomSheetDialog.show() 的正上方添加以下OnClickListeners 。
copy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Copy is Clicked ", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Share is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
upload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Upload is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Download is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Delete is Clicked", Toast.LENGTH_LONG).show();
bottomSheetDialog.dismiss();
}
});
我们使用bottomSheetDialog.dismiss() ,一旦有元素被点击就关闭对话框。
你也可以设置一个更明显的动作,指示你的应用程序在对话框被驳回时做一些事情。例如,应用程序可以启动一个新的活动。
bottomSheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// Instructions on bottomSheetDialog Dismiss
}
});
测试应用程序

- BottomSheetDialogFragment 一个片段可以显示为一个Bottom Sheet对话框。继续创建一个新的片段,叫它
BottomSheetFragment。你可以选择启动一个新的项目。
创建一个新的片段将生成一个与之相关的XML文件。继续并在其中包括你的布局设计。使用与bottom_sheet_dialog_layout.xml 中指定的相同的布局。为这个片段膨胀布局,如下所示。
public class BottomSheetFragment extends Fragment {
public BlankFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_dialog, container, false);
return view;
}
}
在activity_main.xml 中添加一个按钮,声明它,并按照前面的步骤设置OnClick Listener。
我们想在按钮被点击的时候打开这个片段。
在按钮的OnClick Listener内指出以下代码块。
Button bottton = findViewById(R.id.button);
bottton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BlankFragment blankFragment = new BlankFragment();
blankFragment.show(getSupportFragmentManager(),blankFragment.getTag());
}
});
我们需要将该片段转换为一个底层表单。我们通过扩展BottomSheetDialogFragment 而不是Fragment 来实现。
public class BottomSheetFragment extends BottomSheetDialogFragment {
}
当你运行该应用程序时,它应该显示如下所示的对话框。

在GitHub上查看用于实现两个模态对话框的代码。
实现持久化底层表单对话框
铺设底层页面的设计
我们将使用一个简单的登录界面的例子。我们将使用一个持久化对话框将其滑入主屏幕,而不是在常规活动布局中显示它。
我已经创建了一个bottom_sheet_dialog_layout.xml文件,并包括以下简单的登录布局。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet_layout"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/bottom_sheet_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/purple_500"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="3"
android:fontFamily="serif"
android:padding="24dp"
android:textSize="18sp"
android:text="Welcome Back. Please Login"
android:textColor="@android:color/white" />
<ImageView
android:id="@+id/bottom_sheet_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
app:srcCompat="@drawable/ic_baseline_keyboard_arrow_up_24" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="@color/teal_200"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:filterTouchesWhenObscured="false"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="serif"
android:gravity="center_horizontal"
android:text="login"
android:textSize="36sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor= "@color/purple_500"
android:fontFamily="serif"
android:text="username"
android:textSize="24sp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:fontFamily="serif"
android:hint="enter_email_address"
android:inputType="textEmailAddress" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor= "@color/purple_500"
android:fontFamily="serif"
android:text="password"
android:textSize="24sp" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:fontFamily="serif"
android:hint="enter_password"
android:inputType="textPassword" />
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="50dp"
android:fontFamily="serif"
android:text="login"
android:backgroundTint="@color/purple_500"
android:textColor="#FFFFFF"
android:textSize="18sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

这还不是一个底层表单。它只是一个普通的布局。要把这个布局转变为一个Bottom Sheet对话框,应该在根布局中加入一些声明。
这些声明将控制 Bottom Sheet 的行为。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_sheet_layout"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content"
app:behavior_hideable="false"
app:behavior_peekHeight="62dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
底层工作表的行为标志包括。
-
app:layout_behavior- 将 到XML文件中。这被分配到 。这是最重要的 属性,因为它把一个给定的布局定义为Bottom Sheet对话框。BottomSheetBehaviorcom.google.android.material.bottomsheetBottomSheetBehavior -
app:behavior_hideable- 取一个布尔值。如果 ,用户可以通过向下滑动来拖动和隐藏对话框。如果是假的,对话框将漂浮在屏幕上,不能隐藏。true -
app:behavior_peekHeight- 它定义了用户可见的底层页面的高度。
记住要添加一个用于访问布局的ID。
为了有效地实现Bottom Sheet,它必须是CoordinatorLayout 。要做到这一点,请进入你的主XML文件。这可能是一个活动或片段。在我们的例子中,它将是activity_main.xml 。
下面是做这件事的代码。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/bottom_sheet_dialog_layout" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
记住要包括我们设计的Bottom Sheet。用CoordinatorLayout 来包裹它。

展开和折叠工作表对话框
为了控制对话框的滑动和折叠,我们使用状态。Bottom Sheet有几个状态,你需要了解它们。它们包括
STATE_EXPANDED- 对话框在其定义的最大高度上是可见的。STATE_COLLAPSED- 对话框根据设定的 ,是可见的。peekHeightSTATE_DRAGGING- 用户正在上下拖动对话框。STATE_SETTLING- 显示对话框沉淀在一个特定的高度。这可以是 ,展开的高度,如果对话框是隐藏的,则为零。peekHeightSTATE_HIDDEN- 对话框不可见。
我们要做的最后一件事是监听对话框的状态。我们使用BottomSheetCallback 来检测任何状态变化。
声明以下参数。
private LinearLayout mBottomSheetLayout;
private BottomSheetBehavior sheetBehavior;
private ImageView header_Arrow_Image;
初始化行为底片布局和箭头图像。
mBottomSheetLayout = findViewById(R.id.bottom_sheet_layout);
sheetBehavior = BottomSheetBehavior.from(mBottomSheetLayout);
header_Arrow_Image = findViewById(R.id.bottom_sheet_arrow);
我们将把OnClick Listener分配给arrow 矢量图像。当点击时,我们要展开或收拢对话框。
header_Arrow_Image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED){
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
实现一个BottomSheetCallback ,以监听BottomSheetBehavior 的状态。
sheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
header_Arrow_Image.setRotation(slideOffset * 180);
}
});
onStateChanged 告诉应用程序根据状态在对话框上发生了什么。 将旋转箭头图像(同时从下往上滑动),直到 达到其最大高度。onSlide STATE_EXPANDED
在另一边,当STATE_COLLAPSED 在peekHeight ,图像将旋转到它的原始状态。
运行应用程序

结论
Bottom Sheet对话框是显示菜单和对话框的一种独特方式。它提供了更多的空间来包含内容。底层表单对话框可以容纳不同的组件。