一文搞懂 BottomSheetBehavior

nanchen

共 3932字,需浏览 8分钟

 · 2021-02-05


1. 引言

BottomSheetBehavior能实现怎样的效果,一图胜千言。c82955f174147da58d9a187a1f12ac39.webp

如果仅仅是实现上下拖动和隐藏的功能。抛开BottomSheetBehavior自己实现也不难,在没有CoordinatorLayout的年代,这种效果往往是纯手工打造。既然如此为何Google要专门设计BottomSheetBehavior呢?为了搞清楚这个问题,我查阅源码探究了一番,确实发现了一些隐秘的角落。我将从以下几个方面讲解BottomSheetBehavior的设计思路

  1. 讲解BottomSheetBehavior的几种状态
  2. 讲解BottomSheetBehavior的事件分发
  3. 讲解BottomSheetBehavior如何处理嵌套滑动
  4. 实现高德地图首页效果,欢迎关注字节小站微信公众号号
2. BottomSheetBehavior的几种状态

BottomSheetBehavior一共有6种状态

  1. STATE_EXPANDED  全部展开状态
  2. STATE_COLLAPSED 收起状态
  3. STATE_DRAGGING  拖动状态
  4. STATE_SETTLING
  5. STATE_HIDDEN    隐藏状态
  6. STATE_HALF_EXPANDED 半展开状态
7b4ea4b236846fe759a204ecefc0d057.webp

系统通过哪种方式实现每种状态不同的偏移量呢?

  1. layout阶段通过ViewCompat.offsetTopAndBottom(child, offset)实现偏移量
  2. 用户触摸交互阶段通过ViewDragHelper.dragTo(left,top,dx,dy)实现偏移量

2.1 Layout阶段

5e639bdfa830bb9b2bd2730c482ca4ca.webpBottomSheetBehavior#onLayoutChild

Layout阶段最后会通过findScrollingChild方法,寻找开启了嵌套滑动的后代View。其实这就是Google单独研发出BottomSheetBehavior的主要考量。满足支持嵌套滑动的BottomSheet效果。

2.2 用户触摸交互阶段

f7f6566fc18a338d1bbb805201bd6d7e.webpb9ba59901a6a429522204ac15c2af7ec.webp

2.3 状态对应的偏移量

状态偏移量
STATE_COLLAPSEDcollapsedOffset
STATE_EXPANDEDgetExpandedOffset()
STATE_HALF_EXPANDEDhalfExpandedOffset
STATE_HIDDENparentHeight

1. 计算 collapsedOffset

5e27f1cf90f45b68438782ac17e31566.webp
变量名默认值
PEEK_HEIGHT_AUTO常量值-1
peekHeightMin默认值64dp,用户不可修改
peekHeightAuto默认值true,用户可设置
peekHeight默认值0,如果设置为PEEK_HEIGHT_AUTO peekHeightAuto为true否则为false,如果设置小于-1则为0
fitToContents默认值true,用户可设置
fitToContentOffsetMath.max(0, parentHeight - child.getHeight())

peekHeight默认值为0。设置逻辑如下

1358786e6c410a405a72f50ced423ec7.webp
  1. height为-1,则peekHeightAuto设置为true。
  2. 否则peekHeightAuto为false,而且peekHeight最小值为0。5e27f1cf90f45b68438782ac17e31566.webp

计算collapsedOffset值有四种情况

CasepeekHeightAutofitToContents
case1truetrue
case2truefalse
case3falsetrue
case4falsefalse

返回值

Case返回值
Case1Math.max(parentHeight - Math.max(peekHeightMin, parentHeight - parentWidth * 9 / 16), fitToContentsOffset)
Case2parentHeight-Math.max(peekHeightMin, parentHeight - parentWidth * 9 / 16)
Case3Math.max(parentHeight - peekHeight, fitToContentsOffset)
Case4parentHeight - peekHeight

2. 计算 halfExpandedOffset

e83f16fc1dc8be42414591ff96879aa4.webp

3. 计算 expandedOffset

3f337742ac191222d18b3c179b1e47e3.webp

4.如何固定BottomSheetBehavior的高度?

了解这些值的计算有什么好处。假设我想让BottomSheetBehavior,固定高度,不能向上滑也不能向下滑。那我们则需要将collapsedOffset和expandedOffset设置为一样的值才行。7f5be18b37f6df379a47abf697a51ed1.webp

代码如下5bdd42cf41898b3483bb08d0b127e082.webp

为了良好的阅读体验没有使用代码块呈现代码,如果你想获取代码请访问github代码库

3. 讲解BottomSheetBehavior的事件分发

学习Android事件分发是有方法的。我总结为"三板斧"分析法

  1. 源码分析
  2. 场景化
  3. 树形图分析

3.1 三板斧之源码分析

e85213b657c1013bc2504d0de3906aec.webp

从onInterceptTouchEvent的代码中,可以看到viewDragHelper.shouldInterceptTouchEvent(event),说明拦截方法会让ViewDragHelper方法处理。

ViewDragHelper的初始化,会传入ViewDragHelper.Callback dragCallback对象,该对象的boolean tryCaptureView(View child, int pointerId)方法决定viewDragHelper.shouldInterceptTouchEvent的返回值。

bfa191607929fad83eea616000c2e1cf.webp1d35e7acab90d784fb50fb54504b5480.webp

onInterceptTouchEvent的拦截逻辑如下

9b71d092171ee5a540b4cfb97202ac0f.webp

onTouchEvent主要交由ViewDragHelper#processTouchEvent处理,如果是Move事件最终会调用dragTo方法进行移动

b4e09f77c476a24e9babc0279f9cc306.webp

3.2 三板斧之场景化和树形图分析

假设有场景如下,用户可以在HeadLayout、NestedScrollingChild,TopMostLayout区域内上下滑动。这三个case,事件的处理路径如何呢?c0bbc7f26d74c36ccc8f2f1d30c82e3c.webp

转化成树形图如下

设置BottomSheetBehavior为LinearLayout的Behavior4817afbecfc98e76d8995c0b4a1f21ee.webp

3.2.1. 在HeadLayout区域内上下滑动

  1. Down事件处理,初始状态,在ViewDragHelper的shouldInterceptTouchEvent方法中不会调用tryCaptureViewForDrag方法,该方法返回false。在BottomSheetBehavior onInterceptTouchEvent中完整事件路径如下,红线表示事件的分发路径5b08d7823d7a2d2535ec4f3a8b9caefa.webp

结合树形图分析。由于BottomSheetBehavior不拦截事件。Down事件分发流程如下

d00112cef6fcc1da9989656477eb5c5a.webp

最终会调用到BottomSheetBehavior的onTouchEvent方法,会调用到ViewDragHelper的processTouchEvent方法

65aec3c6c6885fdb67f85a9a6eaffb43.webp

最终会将ViewDragHelper的dragState设置为STATE_DRAGGING

  1. MOVE事件在BottomSheetBehavior onInterceptTouchEvent分发流程如下
3c139bc093cce95b280ee0ca5c0a885d.webp

接下来直接调用 BottomSheetBehavior 的onTouchEvent方法。同样调用到ViewDragHelper的processTouchEvent方法90e74c6e8345cb2066a212f9b390db90.webp

3.2.2. 在NestedScrollingView区域内上下滑动

1.Down事件分发到BottomSheetBehavior的onInterceptTouchEvent分发流程如下cebc16694368975b982e01c1e5dc72f0.webp

由于不拦截。Down事件分发给NestedScrollingChild,NestedScrollingChild会启动嵌套滑动,与BottomSheetBehavior配合完成嵌套滑动

2.Move事件分发流程比较复杂,当在NSC中Move的距离没达到阈值时,MOVE会继续分发到BottomSheetBehavior的onInterceptTouchEvent中,当在NSC中MOVE距离达到阈值时,会调用parent.requestDisallowInterceptTouchEvent(true)从此直达NSC,就是纯粹的嵌套滑动了。

8614bba195a8a1ca80da3fdec654dfb6.webp

接下来事件交由NSC分发,当MOVE距离大于阈值时,事件直接交由NSC处理。

efa2f43984e3c441334763bd3d0d8170.webp

3.2.2. 在TopMostLayout区域内上下滑动,该区域与NSC区域没有交集

Down事件同上5b08d7823d7a2d2535ec4f3a8b9caefa.webp

MOVE事件,当距离>ViewDragHelper阈值时

abb859fccb7fc81ff2c5ac14e26549e2.webp

由于MOVE事件拦截了,会交由BottomSheetBehavior onTouchEvent处理,如下图895dccba487628c3521ca17353f10158.webp

4. 结束

至此已基本讲解完BottomSheetBehavior的事件分发机制。具体细节未能尽善尽美。纸上得来终觉浅,希望读者可以结合文章去探索源码。下次我将用BottomSheetBehavior来实现高德地图首页效果。欢迎持续关注

7605e70be440c079c12d0d122af9b9c9.webp

—————END—————


我是南尘,只做比心的公众号,欢迎关注我。

推荐阅读:

nanchen,是一个怎样的公众号?

Android 代码规范大全


欢迎关注南尘的公众号:nanchen
做不完的开源,写不完的矫情,只做比心的公众号,如果你喜欢,你可以选择分享给大家。如果你有好的文章,欢迎投稿,让我们一起来分享。
          长按上方二维码关注        做不完的开源,写不完的矫情        一起来看 nanchen 同学的成长笔记


浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报