Material Design组件 - 使用BottomSheet展现扩展内容(一)

Material Design组件 - 使用BottomSheet展现扩展内容(一))

BottomSheet历史

BottomSheetBehavior最早是在Android的Design支持库中出现的,这个库中提供了许多Material Design 组件,BottomSheetBehavior即是对Material Design中Sheets: bottom即底部扩展菜单的一种实现,是对Material Design的支持,正如Material Design里面的Sheets: bottom中写的那样,其可以在屏幕的底部为整个画面提供一个补充视图增强用户体验性

在这里插入图片描述

BottomSheet的实现首次出现是在com.android.support.design-23.2.0的支持库中,而现在已经被Jetpack的AndroidX吞并,并且官方也推荐我们将项目迁移到AndroidX,而不是使用以前的支持库,因为以前的支持库已经不更新维护了,所以我们会直接从AndroidX的BottomSheet来学习

在这里插入图片描述

上表左边的是旧的支持库,右边的是AndroidX的支持库,其在com.google.android.material:material库中,所以我们使用会引用这个库来使用,另外Jetpack Compose里面也有对其的支持,本篇文章主要还是以原始XML的形式来进行说明,想要Compose的可以看BottomSheetScaffold API ReferenceModalBottomSheetLayout API Reference自行先学习下哈,后续我也会对这方面的内容进行学习分享

Bottom Sheets的展现方式

首先我们先来了解一下官方口中的Bottom Sheets到底是什么样的,我们在Material Design的BottomSheet描述中可以看到,BottomSheet有三种类型,分别是

  • Standard bottom sheets
  • Modal bottom sheets
  • Expanding bottom sheets

Standard bottom sheets是最基本的bottom sheet,其就是在底部可以展开一个菜单来,并且你的主页面不会被遮挡,也能在展开菜单的时候使用者也能与主页面进行正常交互
在这里插入图片描述

Modal bottom sheets是模态化的bottom sheet,其就和Dialog类似,只是是从底部可以滑动出现,展开的时候会将主页面遮挡,并且其展开的时候,使用者就不能与主页面进行交互了

在这里插入图片描述

Expanding bottom sheets提供了一个小小的展开画面,用户可以点击这个画面进而出现一个扩展页面来

在这里插入图片描述

然而,第三种BottomSheet官方并没有都提供给我们开箱即用的组件,只提供给我们了两种,Standard bottom sheets对应了AndroidX中material库里的BottomSheetBehavior类

Modal bottom sheets对应了库里的BottomSheetDialog和BottomSheetDialogFragment其两者分别继承自AppCompatDialog和AppCompatDialogFragment,其关系类似于AppCompatDialog和AppCompatDialogFragment,在BottomSheetDialogFragment中也会去给我们创建BottomSheetDialog实例,让我们能够更好更便捷的使用BottomSheet

至于Expanding bottom sheets,并没有找到有提供给我们直接使用的组件,然而如果真要使用这种效果,我们可以参考Material的github上的OWL示例来实现,不过在实现这个之前,我建议可以先从最基本的BottomSheet来学习

非模态BottomSheetBehavior

我们先来学习BottomSheetBehavior,即非模态的组件,我们可以先来看看效果,
在这里插入图片描述

接下来我们实现它,我们直接使用官方为我们提供的开箱即用组件BottomSheetBehavior来做,首先我们肯定先要创建一个XML布局文件,将我们的主页面先画上,那么这边会存在一个问题,我们的顶层布局是不是可以随便使用呢?答案是不行的,这边官方为我们提供的组件都有一个特点,其需要作为CoordinatorLayout的子视图来使用,对CoordinatorLayout有所了解的同学看到这个Behavior肯定不陌生,所以其离不开CoordinatorLayout布局

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

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

</androidx.coordinatorlayout.widget.CoordinatorLayout>

这样,我们就有了主画面的布局,那么接下来我们就来添加BottomSheet部分的内容,在上面的主页面下面添加

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 
   ...>

    <LinearLayout
        ...
    </LinearLayout>


    <androidx.core.widget.NestedScrollView
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        android:background="@android:color/holo_orange_light"
     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"/>

    </androidx.core.widget.NestedScrollView>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

我们这边想让我们的BottomSheet全部展开的时候高度达到400dp,所以我们设置了NestedScrollView的高度为400dp,内部则简单的就是一个TextView,这边我们使用NestedScrollView是因为我们想让里面的TextView在内容非常多的时候,可以让其滚动显示,所以我们会为textview设定wrap_content,这样当内容超过了这个高度后,就能滚动显示了(当然你也可以使用普通的布局实现自己想要的效果,这边想要说明的是,不是一定要NestedScrollView才行),另外在NestedScrollView的属性上我们添加了 a p p : l a y o u t b e h a v i o r = " c o m . g o o g l e . a n d r o i d . m a t e r i a l . b o t t o m s h e e t . B o t t o m S h e e t B e h a v i o r " app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" app:layoutbehavior="com.google.android.material.bottomsheet.BottomSheetBehavior"这个属性,这就是关键点了,添加后,我们就能看看效果如何了
在这里插入图片描述

你会发现其一出来就是全部展开的并且还不能拖动收起来,为何不能收起来呢?我们先不急着看这个问题,下面我们会来详细说明一下,这边我们先实现我们的页面,这个页面现在的高度显示的都是正确的,但是我们不想要我们的子View初始的时候就显示这么大,我们只要其显示一部分内容,其余部分就让用户展开即可,所以BottomSheet为我们提供了peek height属性,该属性就表示了BottomSheet能够为我们露出一部分的高度是多少,即折叠的时候的高度,这样我们就会和我们子视图的总大小有一段距离产生,用户就可以拖动来控制BottomSheet的展示,我们在xml中添加上这个属性,代码片段如下

<androidx.core.widget.NestedScrollView
        ...
        app:behavior_peekHeight="100dp"
   app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
        >

        ...

    </androidx.core.widget.NestedScrollView>

看看效果

在这里插入图片描述

这样,一个最简单的底部扩展菜单就完成了,很简单吧,当然官方给我们提供的组件还有很多自定义的功能,我们可以做如下设定:

  • behavior_peekHeight:如上面的演示所示,表示BottomSheet在折叠时候的高度

  • behavior_hideable:true表示BottomSheet可以完全隐藏,默认为false

  • behavior_skipCollapsed:这个属性只有在behavior_hideable设置为true的时候才有效果,其效果就是在展开状态下用户往下滑动BottomSheet收缩的时候,会直接跳过折叠状态,即不会停留在折叠高度的设置,而是直接隐藏起来,默认值是false,大家可以看下面的效果,

在这里插入图片描述

  • behavior_draggable:表示BottomSheet是否可以拖动,默认为true,即可以拖动

以上属性是一些通用的基础属性,另外官方还为我们提供了半展开的到全展开的三段式功能,以下属性是用来设定该功能的,behavior_fitToContents为开关属性,并且这些属性也可以和上面的属性进行结合使用

  • behavior_fitToContents:设定为false后,即可实现三段式的展开效果,即可以分为两个阶段展开(父容器高度的一半即半展开阶段,父容器全高即全展开阶段),默认为true,即只能展开到和其设定的内容高度一样的位置,比如上面的例子只能展开到400dp的高度,而设置了false后,你向上滑动就可以从折叠状态到父容器一半高度再到父容器的全部高度来展开

在这里插入图片描述

  • behavior_halfExpandedRatio:这个属性在behavior_fitToContents为false时候有效,用来设置其半展开的时候的高度比例,如下设置0.7的效果

在这里插入图片描述

  • behavior_expandedOffset:用来设置其全展开状态下到父容器的偏移量,上面例子如果设定100dp的话,那么其展开就只能到达距离父容器顶部100dp的地方,而不能到达父容器顶部位置,默认为0,即可以到达父容器的顶部位置

我们来设定一个三段式的效果看看,

<androidx.core.widget.NestedScrollView
        android:id="@+id/bottom_sheet"
        android:layout_width="match_parent"
        android:layout_height="400dp"
        app:behavior_peekHeight="100dp"
        app:behavior_fitToContents="false"
        app:behavior_halfExpandedRatio="0.7"
        app:behavior_expandedOffset="50dp"
   app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
        >

        ...

    </androidx.core.widget.NestedScrollView>

我们来看看效果

在这里插入图片描述

这边你会发现底部怎么会有个空白哈,这是由于我们设定了NestedScrollView的高度为400dp,所以它就只能是400dp这么大啦,我们只要将其高度设定为match_parent,就能解决这个问题,看效果

在这里插入图片描述

所以,在设定这些属性的同时,合理的设定子视图的高度,也能够达到不同的效果,如我们将NestedScrollView高度设定为wrap_content,这个时候,如果你内容比较少,并且你的behavior_peekHeight设定的比较小的话,那么就会出现没法展开的问题了,所以这些都是灵活的,这些参数在理解了后,则你可以做出各种各样的效果来了

最后对于属性还有个隐藏参数,还记得我们刚开始的时候什么属性都不设置的时候,是什么情况呢?这就是这个隐藏参数导致的,我们上面说了可以通过behavior_peekHeight来设定BottomSheet的折叠高度,那么我们如果不设定,这个值会是什么呢?又为什么会导致上面的那种情况出现呢?

这就是隐藏参数PEEK_HEIGHT_AUTO捣的鬼,我们不设定behavior_peekHeight默认就会给其设定PEEK_HEIGHT_AUTO这个值,在这种模式下,BottomSheet会自动计算出一个peekHeight,其计算规则如下:

在这里插入图片描述

官方的说明也是说会以父布局的16比9的比例关系来作为关键,这边在我手机上我的parentHeight为1840,即充满了整个屏幕,parentWidth为1080也是整个屏幕,那么其结果Int值就是 1840 − 1080 ∗ 9 / 16 = 1233 1840 - 1080 * 9 / 16 = 1233 184010809/16=1233,这个值更像是以宽度在16比9的比例下去计算了一个多余空间出来,然后用父布局的高度减去这个高度得到的一个值,这个值可能会很高,然后其又和childHeight取了最小的那个,这边的childHeight就是我们的NestedScrollView,我们设置的400dp,在我手机上则为1200px,而之后会拿这个值去计算折叠偏移量(只是去计算,不会给peekHeight真正赋值,这边源码有点小复杂大家感兴趣可以去研究,这边不做研究源码,就不贴了),所以最终导致了这边我们的NestedScrollView的400dp == 计算出来的1200px,相当于peekHeight高度已经等于了我们的子视图的高度,那当然不给你往下滑动了哈,所以在这边我们唯一的办法就是重新设定我们的子视图的高度,比1200px大就行,比如设定500dp即我手机上1500px,那就可以展开一小段距离(300px)

在这里插入图片描述

所以,这边建议还是自己需要设定behavior_peekHeight,不然其自动计算的结果往往无法很好的控制

由于篇幅比较长,这篇文章暂时先说到这里,接下来我们会以在Java代码中如何去设置这些参数以及针对其状态和回调做一个总结,并且会说明模态化的BottomSheet

ok,这样的BottomSheet基础知识你理解了吗?

最后还望各位兄弟姐妹们点个赞,关个注,更多的我理解的内容我还会陆续和大家分享的,谢谢大家!