BottomSheet 的使用介绍(BottomSheetBeahvior,BottomSheetDialog,BottomSheetDialogFragment)

目录

1.什么是BottomSheet

BottomSheet是一种从屏幕底部向上滑出一个对话框的效果,可以用来显示内容或者提供与用户相关的操作,在开发过程中十分常见,Bottom Sheet 具体实现主要包含:BottomSheetBeahvior 、BottomSheetDialog、BottomSheetDialogFragment,这三个组件均可以实现半屏弹出效果,区别点在于接入和使用方式上的差异

三个组件的概述

  • BottomSheetBeahvior 一般直接作用在view上,一般在xml布局文件中直接对view设置属性,轻量级、代码入侵低、灵活性高,适用于复杂页面下的半屏弹出效果。app:layout_behavior=“@string/bottom_sheet_behavior”
  • BottomSheetDialog 的使用和对话框的使用基本上是一样的。通过setContentView()设定布局,调用show()展示即可。因为必须要使用Dialog,使用上局限相对多,因此一般适用于底部弹出的轻交互弹窗,如底部说明弹窗等。
  • BottomSheetDialogFragment 的使用同普通的Fragment一样,可以将交互和UI写到Fragment内,适合一些有简单交互的弹窗场景,如底部分享弹窗面板等。

1、BottomSheetBeahvior

Behavior是Android Support Design库里面新增的布局概念,主要的作用是用来协调CoordinatorLayout布局(Jetpack中的一个FrameLayout优化(第一行代码中只是简单说了他是FrameLayout优化没有进一步拓展))直接Child Views之间布局及交互行为的,包含拖拽、滑动等各种手势行为。

CoordinatorLayout布局:一个可协调子视图(Child Views)之间交互行为的布局容器。而Behavior则定义了每个子视图(Child View)与CoordinatorLayout之间的特定交互行为。这里注意Behavior只能应用于CoordinatorLayout布局中的子视图,而且需要CoordinatorLayout配合使用,以实现预期的效果.

从名字即可以看出,BottomSheetBehavior继承CoordinatorLayout.Behavior,借用behavior的布局和事件分发能力来实现底部弹出动画及手势拖拽效果。下面首先分析下bottomsheet初始弹出时是如何实现弹出动画。一个简单的半屏滑动布局如下:

ae4bd5418688e1edda8c75301c44b9a0.jpeg

3.1 BottomSheetBehavior的几种状态

  • STATE_HIDDEN :隐藏状态,关联的View此时并不是GONE,而是此时在屏幕最下方之外,此时只是无法肉眼看到

c22df1d8a3b75f41e1b671afd2fcebe4.jpeg

  • STATE_COLLAPSED :折叠状态,一般是一种半屏形态

78d0f8dc11119f689958cd82f8bd6961.jpeg

  • STATE_EXPANDED:完全展开,完全展开的高度是可配置,默认即屏幕高度。类似地图首页一般完全展开态的高度配置为距离屏幕高差一小截距离。

e417a9b0deaff7b56c3452bf4e0bec29.jpeg

  • STATE_DRAGGING:拖拽状态,标识人为手势拖拽中(手指未离开屏幕)
  • STATE_SETTLING :视图从脱离手指自由滑动到最终停下的这一小段时间,与STATE_DRAGGING差异在于当前并没有手指在拖拽。主要表达两种场景:初始弹出时动画状态、手指手动拖拽释放后的滑动状态。

这些图片来自CSDN探索BottomSheet的背后秘密,这位博主讲的十分细致需要一些知识积累,如果你看完发现有些东西不足以满足你的需要,你可以看看这位博主的内容,我这里仅仅讲解了如何简单使用

3.2 BottomSheetBehavior的简单使用

1.设置CoordinatorLayout作为父布局,并设置需要弹出的View的layout_behavior为BottomSheetBehavior。

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    
    tools:context=".MainActivity">

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingTop="24dp">

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

    <Button
        android:id="@+id/button_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:layout_margin="8dp"
        android:text="Button 2"
        android:textColor="@android:color/white"
        android:background="@android:color/holo_blue_light"/>

    <Button
        android:id="@+id/button_3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp"
        android:layout_margin="8dp"
        android:text="Button 3"
        android:textColor="@android:color/white"
        android:background="@android:color/holo_red_dark"/>
     </LinearLayout>
    <LinearLayout
        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="fsadfdasfgr....(数据太长,不贴出来了,自行添加长数据试验)"
            android:padding="16dp"
            android:textSize="16sp"/>

    </LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

这里主要设置了一个主页面,一个相应页面,使用的核心是在响应页中app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"这个属性,这里设置了,behavior的一个对象,这里一般在使用中会要求直接实现behavior类来实现一些方法从而实现一些功能,这里使用了一个官方的实现类,他为我们实现了很多方法,这给出一些相关的属性

 app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
 //用于控制是否允许用户通过向下滑动关闭底部抽屉。设置为 true 表示可以关闭,false 表示不可关闭。
 app:behavior_hideable="true"    
 //设置BottomSheet在折叠时候的高度
 app:behavior_peekHeight="100dp"
  //当此属性设置为 true 时,用户无法通过向下滑动将底部抽屉折叠到 peekHeight 以下的高度。 
 app:behavior_skipCollapsed="true"
  //当设置为 true 时,底部抽屉会固定在 peekHeight 所指定的高度,不会完全展开。
 app:behavior_fitToContents="true"

2.活动中设置调用

public class MainActivity extends AppCompatActivity {
    private BottomSheetBehavior mBottomSheetBehavior;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        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);
                }
            }
        });
    }
}

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

   View rootLayout = findViewById(R.id.ttt); // 这里使用 ttt 作为根布局的 id
    rootLayout.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
            }
        }
    });

2.BottomSheetDialog

我个人感觉他的使用比上面的BottomSheetBehavior简单一些,

这里首先将简单使用

创建布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">
    <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>

在布局方面就是一个简单的布局没有特殊之处

   // 初始化 BottomSheetDialog
        BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
        // 设置对话框的布局文件
        bottomSheetDialog.setContentView(R.layout.ttt);
        // 设置点击外部区域关闭
        bottomSheetDialog.setCanceledOnTouchOutside(true);
        // 可选:设置行为
        //用于从底部抽屉对话框中找到具体的布局视图,这个布局视图通常包含了底部抽屉中显示的内容,比如按钮、文本、列表等
        View bottomSheetView = bottomSheetDialog.findViewById(R.id.tttt);
        BottomSheetBehavior<View> behavior = BottomSheetBehavior.from(bottomSheetView);
//        // 设置最小高度
        behavior.setPeekHeight(500);
        Button button2 = findViewById(R.id.button_2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //         显示 BottomSheetDialog
                bottomSheetDialog.show();
            }
        });

这里就是他的使用了,这里用注释标注的比较清楚了,常见方法如下

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

这里就是他的基本使用了,现在给出一个他的优化(直接弹出的是直角的,现在优化为带弧度的,好看一点)

首先在Drawable下创建一shape_bottom_sheet_dialog.xml,中设置一个shape

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/white"/>
    <corners android:topLeftRadius="10dp"
        android:topRightRadius="10dp"/>
</shape>

然后将响应视图的背景设置为他

   android:background="@drawable/shape_bottom_sheet_dialog"

在设计俩个style,在themes.xml(res->values->themes)

    <!--实现BottomSheetDialog圆角效果-->
    <style name="BottomSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
        <item name="bottomSheetStyle">@style/bottomSheetStyleWrapper</item>
    </style>
    <style name="bottomSheetStyleWrapper" parent="Widget.Design.BottomSheet.Modal">
        <item name="android:background">@android:color/transparent</item>
    </style>

最后在构造函数中使用它

 BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this, R.style.BottomSheetDialog);

就可以使用了

3.BottomSheetDialogFragment

BottomSheetDialogFragment 是 Android 中的一个类,它是 Fragment 的子类(这里存在多段继承关系,并不是直接继承)。主要用于实现 Material Design 风格的底部弹出窗口(Bottom Sheet),

这里他使用主要有三个部分,一个是设置一个fragment 继承于BottomSheetDialogFragment,一个是设置相应的布局,一个是在活动中调用他的相关操作

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

        //在这里创建对象返回在后续可以直接调用
        BottomSheetDialog dialog = new BottomSheetDialog(getActivity(), R.style.Theme_MaterialComponents_BottomSheetDialog);
        
        //加载这个布局
        dialog.setContentView(R.layout.dialog_fragment_layout);

        return dialog;

    }

 
}

他对布局没有额外要求

<?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/purple_700"
    android:layout_height="600dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这里是底边框中内容"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>

在主函数中调用

   Button  button3= findViewById(R.id.button_3);
        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //前面的调用简单,这里解释一下参数的含义,
                //getSupportFragmentManager(),得到一个 FragmentManager 对象,用来管理 Fragment 
                //"tag"是一个标识,方便FragmentManager使用findFragmentByTag(String tag)来找到这个Fragment,这个参数可以为空
                new MyBottomSheetDialogFragment().show(getSupportFragmentManager(), "tag");
            }
        });