Android开发如何实现页面滚动时按钮固定悬浮不随内容滚动
问题描述
在XML布局中实现两个功能按钮时,尝试多种写法都无法做到页面滚动时按钮固定在屏幕上不跟随移动。
当前布局逻辑:最外层为ConstraintLayout,内部嵌套CardView承载页面内容,两个设置了按钮样式背景的TextView作为功能按钮,放在CardView内部的子ConstraintLayout(id:add_cart_buy_now)中。
需求效果:页面滚动时,两个按钮始终悬浮固定在屏幕位置,不跟随内容滚动。
原有实现代码:
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/add_cart_buy_now" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_8" android:layout_marginBottom="@dimen/margin_16" android:paddingTop="@dimen/margin_8" android:paddingEnd="@dimen/margin_8" android:paddingBottom="@dimen/margin_8" app:layout_constraintBottom_toTopOf="@id/horizontal_line" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_selling_price"> <TextView android:id="@+id/add_to_cart" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_8" android:layout_marginEnd="@dimen/margin_8" android:background="@{isPharma ? @drawable/pharma_add_to_cart : @drawable/fashion_add_to_cart}" android:onClick="@{(v) -> addClick.onIncrement(ivItemImage)}" android:paddingTop="@dimen/margin_16" android:paddingEnd="@dimen/margin_8" android:paddingBottom="@dimen/margin_16" android:text="@{variant.quantity == 0 ? @string/add_to_cart : @string/go_to_cart}" android:textAlignment="center" android:textAllCaps="true" android:textColor="@color/white" android:visibility="@{variant.quantity >= 0 ? View.VISIBLE : View.GONE}" app:enableView="@{!(variant.getQuantity() >= variant.getStock())}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/buy_now_1" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:background="@drawable/pharma_add_to_cart" /> <TextView android:id="@+id/buy_now_1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_8" android:layout_marginEnd="@dimen/margin_8" android:background="@drawable/fashion_buy_now" android:onClick="@{buyNow}" android:paddingStart="@dimen/margin_8" android:paddingTop="@dimen/margin_16" android:paddingEnd="@dimen/margin_8" android:paddingBottom="@dimen/margin_16" android:text="@string/buy_now" android:textAlignment="center" android:textAllCaps="false" android:textColor="@color/white" android:visibility="@{(!isPharma || isFashion ) ? View.VISIBLE : View.GONE}" app:enableView="@{!(variant.getQuantity() >= variant.getStock())}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toEndOf="@id/add_to_cart" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
注:原代码中app:layout_constraintTop_toTopOf属性粘贴时混入了冗余的enter code here文本,上述代码已做修正。
失效原因
当前按钮容器放在了可滚动组件(NestedScrollView/RecyclerView/ScrollView)的视图树内部,属于滚动内容的一部分,只要控件在滚动容器内部,不管设置什么约束、什么gravity属性,都会跟着内容一起滚动,不可能实现固定悬浮效果。
实现方案
最稳定、性能开销最低的方式是调整布局层级,把按钮容器从滚动区域移出来,和滚动容器平级,通过最外层父布局约束固定位置:
- 最外层统一用
ConstraintLayout作为根布局,高度设为match_parent - 把原来承载所有内容的可滚动组件高度设为
0dp(自适应约束),顶部约束到根布局顶部,底部约束到按钮容器的顶部,避免内容被按钮遮挡 - 把id为
add_cart_buy_now的按钮整体从CardView内部剪切出来,放到和可滚动组件平级的位置,约束到根布局底部,给按钮容器设置不透明背景,防止滚动内容透到按钮区域
调整后的布局结构参考:
<!-- 根布局 --> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" 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"> <!-- 可滚动内容区域 --> <androidx.core.widget.NestedScrollView android:id="@+id/scroll_area" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/add_cart_buy_now"> <!-- 滚动区域内部放原来的CardView和所有业务内容,记得把之前放在CardView里的按钮容器删掉 --> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- 原有页面内容,包括售价、分割线等所有需要滚动的控件 --> </androidx.cardview.widget.CardView> </androidx.core.widget.NestedScrollView> <!-- 固定在底部的按钮容器,和滚动区域平级 --> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/add_cart_buy_now" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_8" android:layout_marginBottom="@dimen/margin_16" android:paddingTop="@dimen/margin_8" android:paddingEnd="@dimen/margin_8" android:paddingBottom="@dimen/margin_8" android:background="@color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"> <!-- 两个按钮的原有代码完全不用改,直接放这里就行 --> <TextView android:id="@+id/add_to_cart" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_8" android:layout_marginEnd="@dimen/margin_8" android:background="@{isPharma ? @drawable/pharma_add_to_cart : @drawable/fashion_add_to_cart}" android:onClick="@{(v) -> addClick.onIncrement(ivItemImage)}" android:paddingTop="@dimen/margin_16" android:paddingEnd="@dimen/margin_8" android:paddingBottom="@dimen/margin_16" android:text="@{variant.quantity == 0 ? @string/add_to_cart : @string/go_to_cart}" android:textAlignment="center" android:textAllCaps="true" android:textColor="@color/white" android:visibility="@{variant.quantity >= 0 ? View.VISIBLE : View.GONE}" app:enableView="@{!(variant.getQuantity() >= variant.getStock())}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/buy_now_1" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:background="@drawable/pharma_add_to_cart" /> <TextView android:id="@+id/buy_now_1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/margin_8" android:layout_marginEnd="@dimen/margin_8" android:background="@drawable/fashion_buy_now" android:onClick="@{buyNow}" android:paddingStart="@dimen/margin_8" android:paddingTop="@dimen/margin_16" android:paddingEnd="@dimen/margin_8" android:paddingBottom="@dimen/margin_16" android:text="@string/buy_now" android:textAlignment="center" android:textAllCaps="false" android:textColor="@color/white" android:visibility="@{(!isPharma || isFashion ) ? View.VISIBLE : View.GONE}" app:enableView="@{!(variant.getQuantity() >= variant.getStock())}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_weight="1" app:layout_constraintStart_toEndOf="@id/add_to_cart" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
避坑提示
- 不要尝试用
layout_gravity、固定偏移等方式在滚动容器内部实现固定悬浮,这类方案要么失效,要么会出现滑动错位、触摸事件冲突的问题 - 如果需要按钮悬浮在内容中间而不是底部,逻辑完全一致:只要把按钮放到滚动容器外部,按需求约束位置即可
- 如果页面用的是
RecyclerView承载列表内容,实现逻辑相同,把RecyclerView的底部约束到按钮顶部即可,不需要做额外适配
内容的提问来源于stack exchange,提问作者Akash Gupta
相关产品推荐
相关产品推荐

