BottomSheetDialogFragment高度设置

1,241 阅读3分钟

文章中间部分是问题的背景和解决过程,结尾可以查看设置BottomSheetDialogFragment高度的代码。

这篇文章算是对我上篇文章BottomSheetBehavior + ViewPager2 + MultipleRecyclerView的滑动问题的一个补充。在实现ViewPager2嵌套RecyclerView滑动后,我把自己mock的假数据换成真数据,发现dialog的高度会随着翻页动态调整,这样可不行,高度来回跳很影响用户使用。

于是开始上网找资料,同时mock不同数量的数据观察case,我发现dialog的高度并不是“随机”的,它会根据dialog中当前页的内容的多少来适配高度。

image.png 如图,黄色为展开高度expandedHeight,也就是吸顶,蓝色为弹出高度peekHeight,大概是在屏幕高度60%的位置,假设dialog中的内容高度为contentHeight,那么dialog高度会根据contentHeight的大小出现以下几种情况:

  • contentHeight > expandedHeight:那么弹出的高度会在peekHeight位置,上划会达到expandedHeight。
  • expandedHeight > contentHeight > peekHeight:弹出高度会在peekHeight,上划后不会达到expandedHeight位置,而是完全显示内容(也就是到达contentHeight位置)。
  • peekHeight > contentHeight:弹出的dialog高度就是内容高度(contentHeight),这个时候由于内容已经全部显示出来了,所以上划是没有效果的,这个位置即是它的弹出高度(peekHeight),也是它的“展开高度”。

总的来说,如果内容足够多,那么既可以达到弹出高度,上划也可以达到展开高度。如果内容不够多,则以展示全部内容为主,也就造成了dialog高度不固定。

查看源码也可以佐证这些情况,我们都知道BottomSheetDialogFragment的滑动逻辑是依靠BottomSheetBehavior实现的,先来看弹出高度的计算源码:

private int calculatePeekHeight() {
  if (peekHeightAuto) {
    int desiredHeight = max(peekHeightMin, parentHeight - parentWidth * 9 / 16);
    return min(desiredHeight, childHeight) + insetBottom;
  }
  // Only make sure the peek height is above the gesture insets if we're not applying system
  // insets.
  if (!gestureInsetBottomIgnored && !paddingBottomSystemWindowInsets && gestureInsetBottom > 0) {
    return max(peekHeight, gestureInsetBottom + peekHeightGestureInsetBuffer);
  }
  return peekHeight + insetBottom;
}

如果没有设置peekHeight,peekHeightAuto为true。可以看到在true逻辑中,首先取peekHeightMinparentHeight - parentWidth * 9 / 16中的最大值,peekHeightMin的值固定为64dp,也就保证最小弹出高度为64dp,parentHeight - parentWidth * 9 / 16也就是前面所说的大约屏幕高度60%的值。得到desiredHeight,接着再从desiredHeightchildHeight中取最小值即可得到peekHeight,childHeight可以理解为内容高度。

展开高度同理,主要计算逻辑如下:

public int getExpandedOffset() {
  return fitToContents
      ? fitToContentsOffset
      : Math.max(expandedOffset, paddingTopSystemWindowInsets ? 0 : insetTop);
}

回到随着翻页高度调整的问题上,由于每次翻页我都会调用requestLayout()方法重新渲染布局,并且每一页内容的多少也不固定,就会出现高度变化的问题。

最后上解决代码,把该段代码添加到我们继承了BottomSheetDialogFragment的类中。

val bottomSheet = dialog?.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
if (bottomSheet != null) {
    val params = bottomSheet.layoutParams as CoordinatorLayout.LayoutParams
    params.width = FrameLayout.LayoutParams.MATCH_PARENT
    params.height = FrameLayout.LayoutParams.MATCH_PARENT
    bottomSheet.layoutParams = params
    BottomSheetBehavior.from(bottomSheet).apply {
        isFitToContents = false
        expandedOffset = 100.dp.toInt()
        peekHeight = 500.dp.toInt()
    }
}

有两个需要注意的点,一是要把高度设为MATCH_PARENT,二是一定要调用setFitToContents()方法并设为false,否则如果内容很少的话会出现上半部分显示内容,下半部分透明的情况。