如何用BottomSheetDialogFragment实现谷歌地图路线底部弹窗扩展行为?
实现谷歌地图风格的固定顶部BottomSheetDialogFragment
这个需求我之前做导航类APP的时候刚好实现过,用BottomSheetDialogFragment完全能搞定谷歌地图那种「点击展开时顶部固定、底部延伸至屏幕下(内容可滚动)」的效果,核心思路是把BottomSheet的布局拆成固定顶部栏和可扩展滚动内容区两部分,再配合BottomSheetBehavior来控制展开/收起状态。
一、先写布局文件:拆分固定顶部与可滚动内容
首先创建BottomSheet的布局layout_bottom_sheet_route.xml,用LinearLayout或者ConstraintLayout都可以,关键是把顶部栏和内容区分离,顶部栏直接挂在最外层容器顶部:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 固定顶部栏:始终停在BottomSheet顶部,不会随内容滚动 --> <LinearLayout android:id="@+id/top_fixed_bar" android:layout_width="match_parent" android:layout_height="60dp" android:background="#ffffff" android:gravity="center_vertical" android:paddingHorizontal="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="路线规划" android:textSize="18sp" android:textStyle="bold" /> <!-- 展开/收起切换按钮 --> <ImageView android:id="@+id/btn_toggle_expand" android:layout_width="24dp" android:layout_height="24dp" android:layout_marginStart="auto" android:src="@drawable/ic_expand_more" /> </LinearLayout> <!-- 可扩展内容区:超出屏幕时允许滚动,底部会延伸到屏幕下方 --> <androidx.core.widget.NestedScrollView android:id="@+id/content_scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:fillViewport="true"> <!-- 这里放你的路线详情内容,比如多个路线卡片、距离/时间信息等 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="8dp"> <View android:layout_width="match_parent" android:layout_height="120dp" android:background="#f5f5f5" android:layout_marginVertical="4dp"/> <View android:layout_width="match_parent" android:layout_height="120dp" android:background="#f5f5f5" android:layout_marginVertical="4dp"/> <View android:layout_width="match_parent" android:layout_height="120dp" android:background="#f5f5f5" android:layout_marginVertical="4dp"/> <View android:layout_width="match_parent" android:layout_height="120dp" android:background="#f5f5f5" android:layout_marginVertical="4dp"/> </LinearLayout> </NestedScrollView> </LinearLayout>
二、实现BottomSheetDialogFragment子类:控制展开行为
接下来创建RouteBottomSheetDialogFragment类,加载上面的布局,通过BottomSheetBehavior设置初始高度、展开状态,以及切换按钮的逻辑:
class RouteBottomSheetDialogFragment : BottomSheetDialogFragment() { private lateinit var bottomSheetBehavior: BottomSheetBehavior<View> private var isExpanded = false override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.layout_bottom_sheet_route, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initBottomSheetBehavior(view) setupToggleButton(view) } private fun initBottomSheetBehavior(view: View) { // 获取BottomSheet的根布局(Material Design自带的ID) val bottomSheetRoot = dialog?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) bottomSheetRoot?.let { bottomSheetBehavior = BottomSheetBehavior.from(it) // 设置初始折叠高度:比如屏幕高度的1/3,刚好显示顶部栏+部分内容 val screenHeight = resources.displayMetrics.heightPixels bottomSheetBehavior.peekHeight = screenHeight / 3 // 监听BottomSheet状态变化,更新按钮图标 bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { isExpanded = newState == BottomSheetBehavior.STATE_EXPANDED val toggleBtn = view.findViewById<ImageView>(R.id.btn_toggle_expand) toggleBtn.setImageResource(if (isExpanded) R.drawable.ic_expand_less else R.drawable.ic_expand_more) } override fun onSlide(bottomSheet: View, slideOffset: Float) { // 滑动过程中的自定义逻辑,可选 } }) } } private fun setupToggleButton(view: View) { val toggleBtn = view.findViewById<ImageView>(R.id.btn_toggle_expand) toggleBtn.setOnClickListener { // 切换展开/收起状态 if (isExpanded) { bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED } else { // 展开时让BottomSheet占满屏幕高度,内容超出部分通过NestedScrollView滚动 bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED val screenHeight = resources.displayMetrics.heightPixels bottomSheetBehavior.maxHeight = screenHeight } } } }
三、关键逻辑解析
- 固定顶部栏:因为顶部栏直接放在最外层LinearLayout的顶部,没有嵌套在滚动容器里,所以无论BottomSheet怎么展开,它都会始终固定在BottomSheet的顶部位置。
- peekHeight属性:设置折叠状态下BottomSheet的显示高度,比如设为屏幕1/3,刚好让用户看到顶部栏和部分路线内容,引导点击展开。
- STATE_EXPANDED状态:展开时BottomSheet会自动延伸到屏幕顶部,此时内容区的NestedScrollView会处理超出屏幕的内容,用户可以滚动查看,而顶部栏保持不动。
- 切换按钮:通过点击箭头图标手动切换展开/收起状态,同时更新图标反馈当前状态。
四、注意事项
- 内容区必须用
NestedScrollView或RecyclerView:如果用普通LinearLayout,超出屏幕的内容会被截断,无法滚动查看。 - 可自定义展开高度:如果不想让BottomSheet占满整个屏幕,可以把
maxHeight设为屏幕高度的80%之类的数值,按需调整。 - 禁止拖动展开:如果只想让用户通过按钮切换,不想允许拖动,可以设置
bottomSheetBehavior.isDraggable = false。
内容的提问来源于stack exchange,提问作者Angelina
相关产品推荐
相关产品推荐

