如何让BottomSheet展开时平滑占据BottomNavigationView的底部空间?
问题描述
Android开发中,需要实现BottomSheet展开时平滑占据BottomNavigationView所在的底部剩余空间。目前已完成部分效果:BottomSheet展开时,BottomNavigationView可缩放并向上偏移,但BottomSheet收起时无法平滑恢复其初始状态。尝试多种方案均未解决,恳请提供完整解决方案及示例代码。
当前效果
BottomSheet展开时,BottomNavigationView向上偏移并缩小;但收起时,BottomNavigationView无法平滑恢复到初始位置和大小,状态切换生硬。
初始代码
Activity布局文件
<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" tools:context="JavaClassForActivity.Profile" android:id="@+id/ProfileLayout" android:background="@drawable/main_back_ground"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/profileFrameLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toTopOf="@id/bottomNavigation" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> </FrameLayout> <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/mainCoordinatorLayout" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@id/bottomNavigation" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> <include android:id="@+id/bottom_sheet_parent" layout="@layout/bottom_sheet_music" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" app:menu="@menu/bottom_nav_menu" android:background="@color/teal_700" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
Java代码
public class Profile extends AppCompatActivity{ BottomNavigationView bottomnavigation; private BottomSheetBehavior<View> bottomSheetBehavior; private LinearLayout layoutTop, layoutExpanded; int peekHeight; private View bottomSheet; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_your_profile); bottomSheet = findViewById(R.id.bottom_sheet_parent); bottomnavigation=findViewById(R.id.bottomNavigation); bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); layoutTop = findViewById(R.id.layout_top); layoutExpanded = findViewById(R.id.layout_expanded); // 设置BottomSheet初始参数 setupBottomSheet(); // 初始状态为收起 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); } private void setupBottomSheet() { ViewTreeObserver vto = layoutTop.getViewTreeObserver(); vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { layoutTop.getViewTreeObserver().removeOnPreDrawListener(this); peekHeight = layoutTop.getHeight(); bottomSheetBehavior.setPeekHeight(peekHeight); bottomSheetBehavior.setHideable(false); bottomSheetBehavior.setFitToContents(true); return true; } }); bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { handleBottomSheetState(newState); } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { handleSlideOffset(slideOffset); } }); } private void handleBottomSheetState(int newState) { if (newState == BottomSheetBehavior.STATE_EXPANDED) { layoutTop.setVisibility(View.GONE); layoutExpanded.setVisibility(View.VISIBLE); } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) { layoutTop.setVisibility(View.VISIBLE); layoutExpanded.setVisibility(View.GONE); } else if (newState == BottomSheetBehavior.STATE_HALF_EXPANDED) { layoutTop.setVisibility(View.GONE); layoutExpanded.setVisibility(View.VISIBLE); // 直接切换到展开状态 if (bottomSheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) { bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); } } } private void handleSlideOffset(float slideOffset) { float topAlpha = 1 - slideOffset; float expandedAlpha = slideOffset; layoutTop.setAlpha(topAlpha); layoutExpanded.setAlpha(expandedAlpha); if (layoutTop.getVisibility() == View.VISIBLE && !layoutTop.isActivated()) { layoutExpanded.setVisibility(View.VISIBLE); } else if (layoutExpanded.getVisibility() == View.VISIBLE && !layoutExpanded.isActivated()) { layoutTop.setVisibility(View.VISIBLE); } bottomnavigation.setTranslationY(bottomnavigation.getHeight() * slideOffset); ViewGroup.LayoutParams params = bottomSheet.getLayoutParams(); params.height = bottomSheet.getHeight() + (int) (bottomnavigation.getHeight() * slideOffset); bottomSheet.setLayoutParams(params); } }
bottom_sheet_music布局文件
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 展开状态布局 --> <LinearLayout android:id="@+id/layout_expanded" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:visibility="gone" android:background="@color/black"> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
更新后的代码
改进后的handleSlideOffset方法(需在类中添加private float previousSlideOffset = 0f;变量)
private float previousSlideOffset = 0f; private void handleSlideOffset(float slideOffset) { float topAlpha = 1 - slideOffset; float expandedAlpha = slideOffset; layoutTop.setAlpha(topAlpha); layoutExpanded.setAlpha(expandedAlpha); // 确保两个布局在滑动过程中可见,实现淡入淡出过渡 if (layoutTop.getVisibility() == View.VISIBLE && !layoutTop.isActivated()) { layoutExpanded.setVisibility(View.VISIBLE); } else if (layoutExpanded.getVisibility() == View.VISIBLE && !layoutExpanded.isActivated()) { layoutTop.setVisibility(View.VISIBLE); } // 统一计算BottomNavigationView的状态,兼容展开/收起双向平滑过渡 float translationY = bottomnavigation.getHeight() * slideOffset; float scale = 1 - slideOffset; bottomnavigation.setTranslationY(translationY); bottomnavigation.setScaleX(scale); bottomnavigation.setScaleY(scale); // 动态调整BottomSheet高度,使其占据底部导航栏空间 CoordinatorLayout.LayoutParams bottomSheetParams = (CoordinatorLayout.LayoutParams) bottomSheet.getLayoutParams(); int originalBottomNavHeight = getResources().getDimensionPixelSize(R.dimen.bottom_nav_height); bottomSheetParams.height = getResources().getDisplayMetrics().heightPixels - peekHeight + (int)(originalBottomNavHeight * slideOffset); bottomSheet.setLayoutParams(bottomSheetParams); previousSlideOffset = slideOffset; }
更新后的Activity布局
<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" tools:context="JavaClassForActivity.Profile" android:id="@+id/ProfileLayout" android:background="@drawable/main_back_ground"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/profileFrameLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toTopOf="@id/bottomNavigationContainer" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/mainCoordinatorLayout" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@id/bottomNavigationContainer" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> <include android:id="@+id/bottom_sheet_parent" layout="@layout/bottom_sheet_music" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> <FrameLayout android:id="@+id/bottomNavigationContainer" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="@dimen/bottom_nav_height" app:menu="@menu/bottom_nav_menu" android:background="@color/teal_700" /> </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
更新后效果
BottomSheet展开时,BottomNavigationView平滑向上偏移并缩小;收起时,BottomNavigationView平滑恢复到初始位置和大小,过渡自然无生硬感。
完整解决方案说明
- 移除分支判断:取消展开/收起的条件分支,直接根据
slideOffset计算状态,保证双向过渡平滑执行。 - 固定导航栏高度:在
dimens.xml中定义BottomNavigationView的固定高度,避免动态修改布局参数导致卡顿,同时保证计算准确性。 - 优化BottomSheet高度计算:基于屏幕总高度和peekHeight计算目标高度,确保展开时完全覆盖底部导航栏空间。
- 修复布局错误:移除原bottom_sheet布局中多余的标签,避免解析异常。
补充配置
在res/values/dimens.xml中添加底部导航栏高度:
<dimen name="bottom_nav_height">56dp</dimen>
内容的提问来源于stack exchange,提问作者Bastanov Airat
相关产品推荐
相关产品推荐

