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 Reference和ModalBottomSheetLayout API Reference自行先学习下哈,后续我也会对这方面的内容进行学习分享
首先我们先来了解一下官方口中的Bottom Sheets到底是什么样的,我们在Material Design的BottomSheet描述中可以看到,BottomSheet有三种类型,分别是
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来做,首先我们肯定先要创建一个XML布局文件,将我们的主页面先画上,那么这边会存在一个问题,我们的顶层布局是不是可以随便使用呢?答案是不行的,这边官方为我们提供的组件都有一个特点,其需要作为CoordinatorLayout的子视图来使用,对CoordinatorLayout有所了解的同学看到这个Behavior肯定不陌生,所以其离不开CoordinatorLayout布局
<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部分的内容,在上面的主页面下面添加
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
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_fitToContents为开关属性,并且这些属性也可以和上面的属性进行结合使用
我们来设定一个三段式的效果看看,
<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 1840−1080∗9/16=1233,这个值更像是以宽度在16比9的比例下去计算了一个多余空间出来,然后用父布局的高度减去这个高度得到的一个值,这个值可能会很高,然后其又和childHeight取了最小的那个,这边的childHeight就是我们的NestedScrollView,我们设置的400dp,在我手机上则为1200px,而之后会拿这个值去计算折叠偏移量(只是去计算,不会给peekHeight真正赋值,这边源码有点小复杂大家感兴趣可以去研究,这边不做研究源码,就不贴了),所以最终导致了这边我们的NestedScrollView的400dp == 计算出来的1200px,相当于peekHeight高度已经等于了我们的子视图的高度,那当然不给你往下滑动了哈,所以在这边我们唯一的办法就是重新设定我们的子视图的高度,比1200px大就行,比如设定500dp即我手机上1500px,那就可以展开一小段距离(300px)
所以,这边建议还是自己需要设定behavior_peekHeight,不然其自动计算的结果往往无法很好的控制
由于篇幅比较长,这篇文章暂时先说到这里,接下来我们会以在Java代码中如何去设置这些参数以及针对其状态和回调做一个总结,并且会说明模态化的BottomSheet
ok,这样的BottomSheet基础知识你理解了吗?
最后还望各位兄弟姐妹们点个赞,关个注,更多的我理解的内容我还会陆续和大家分享的,谢谢大家!