如何使用Android Studio实现底层表单对话框

628 阅读8分钟

使用Android Studio实现底片对话框

底部表单对话框似乎正在取代常规的Android对话框和菜单。底部工作表是一个组件,它从屏幕底部滑上来,展示你的应用程序中的额外内容。

底部工作表对话框就像一个由用户行为触发的消息框。谷歌、Facebook和Twitter等公司已经在其应用程序中实现了这一功能。

底部工作表对话框被用于音乐、支付和文件管理应用程序中。在应用方面,任何Android view ,包括TextViewImageViewRecyclerViewsButtonsText inputs ,都可以包含在底层表单中。这使得它相当有活力。

例如,它可以帮助显示从数据库中检索的数据。只要适合你的应用周期,底部工作表可以在许多情况下应用。

底层工作表对话框的类型

底层表单的两种主要类型是模式化和持久化对话框。

模式化的底层表单对话框

它具有与警报对话框类似的特征。当被触发时(通过用户的操作),它从当前屏幕的底部滑上来。一个模态工作表可以包含一个项目列表。

这些元素在被点击时可以对应于一些动作。模态底部工作表阻止与屏幕其他部分的交互,以表示焦点的转移。当用户点击视图之外或按下返回键时,它就会被解除。

我们没有像持久性对话框那样用CoordinatorLayout 来包装模态底层表单,而是动态地创建它,就像一个普通的对话框。

一个优秀的模态底层表单对话框的例子是Google Drive应用程序。

Google Drive Modal Bottom Sheet

或者这个支付底层表单对话框的例子。

Payment Bottom Sheet Dialog.

模态底层表单是内联菜单和简单对话框的一个优秀替代方案。它们为更多的内容、图标和更多的屏幕操作提供了额外的空间。

持久的底层表单对话框

持久底层表单对话框提供关于当前屏幕的补充内容。它是作为CoordinatorLayout 的子女。

容器的一部分是可见的,为用户提供更多的内容。与Modal对话框不同,Persistent Bottom Sheet小组件对当前屏幕内容是永久性的。

下面是一个Google Maps应用程序中的Persistent Bottom Sheet对话框的例子。

Google Maps Bottom Sheet

实施

创建一个新的Android studio项目。为了实现底部表单对话框,你需要一个材质设计库。

在你的app.gradle 文件中包括以下库。

implementation 'com.google.android.material:material:1.2.1'

同步该项目以下载该库。这将使所有需要的功能在你的项目中可用。

我们将讨论如何使用Android studio实现两种类型的底部表单对话框。

实现模式化的底层页面对话框

  1. 使用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是否正常工作。点击按钮应该触发对话框从底部滑到顶部。

Modal 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
    }
});

测试应用程序

Modal Dialog

  1. 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 {

}

当你运行该应用程序时,它应该显示如下所示的对话框。

Modal Bottom Sheet Fragment

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对话框,应该在根布局中加入一些声明。

这些声明将控制 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对话框。BottomSheetBehavior com.google.android.material.bottomsheet BottomSheetBehavior

  • 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 Peek Height

展开和折叠工作表对话框

为了控制对话框的滑动和折叠,我们使用状态。Bottom Sheet有几个状态,你需要了解它们。它们包括

  • STATE_EXPANDED - 对话框在其定义的最大高度上是可见的。
  • STATE_COLLAPSED - 对话框根据设定的 ,是可见的。peekHeight
  • STATE_DRAGGING - 用户正在上下拖动对话框。
  • STATE_SETTLING - 显示对话框沉淀在一个特定的高度。这可以是 ,展开的高度,如果对话框是隐藏的,则为零。peekHeight
  • STATE_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_COLLAPSEDpeekHeight ,图像将旋转到它的原始状态。

运行应用程序

Persistent Dialog

结论

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