ScrollView嵌套RecyclerView时列表最后项被截断问题求助
RecyclerView嵌套ScrollView后内容截断问题解决方案
问题描述
Fragment内包含两个RecyclerView,通过API拉取数据更新视图后,RecyclerView最后几项内容被截断:8条数据仅显示5条,小屏设备显示数量更少。已禁用RecyclerView自身滚动,期望通过外层ScrollView实现页面整体滚动,对应的XML结构如下:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout 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=".screens.view.MainFragment"> <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/onDutyMainRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingHorizontal="@dimen/margin_m" android:paddingVertical="@dimen/margin_m"> <TextView android:id="@+id/requestTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:fontFamily="@font/sfprotext_semibold" android:textStyle="bold" android:textSize="@dimen/on_duty_main_subtitle_font_size" android:textColor="@color/dark_liver" android:text="Requests" tools:text="Requests"/> <Button android:id="@+id/showAllRequestsButton" style="@style/ShowAllButtonDark" app:layout_constraintBottom_toBottomOf="@+id/requestTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/requestTitle" tools:ignore="TouchTargetSizeCheck" android:text="Show All" tools:text="Show All" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/requestsRecyclerView" android:layout_width="match_parent" android:layout_marginTop="@dimen/on_duty_recycler_view_margin_top" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/requestTitle" /> <TextView android:id="@+id/groupTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:fontFamily="@font/sfprotext_semibold" android:textColor="@color/dark_liver" android:textSize="@dimen/on_duty_main_subtitle_font_size" android:textStyle="bold" android:layout_marginTop="@dimen/margin_m" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/requestsRecyclerView" android:text="Groups" tools:text="Groups" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/groupsRecyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/on_duty_recycler_view_margin_top" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/groupTitle" /> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.constraintlayout.widget.ConstraintLayout>
核心原因
RecyclerView的android:layout_height="wrap_content"在普通ScrollView中无法正确计算自身高度:ScrollView的测量逻辑会强制子视图压缩空间,而RecyclerView的异步布局机制会导致高度计算不完整,最终出现内容截断。
解决方案
方案1:替换为NestedScrollView(最简便有效)
- 将外层
ScrollView替换为androidx.core.widget.NestedScrollView,并设置其高度为match_parent - 给两个RecyclerView添加
android:nestedScrollingEnabled="false"属性,禁用其自身嵌套滚动能力
修改后的核心代码片段:
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/onDutyMainRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingHorizontal="@dimen/margin_m" android:paddingVertical="@dimen/margin_m"> <!-- 标题、按钮等视图保留不变 --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/requestsRecyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" android:layout_marginTop="@dimen/on_duty_recycler_view_margin_top" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/requestTitle" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/groupsRecyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false" android:layout_marginTop="@dimen/on_duty_recycler_view_margin_top" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/groupTitle" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.core.widget.NestedScrollView> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
方案2:手动计算RecyclerView高度(备选,仅适用于固定高度Item)
如果方案1无效,可在数据加载完成后手动计算RecyclerView总高度:
fun calculateAndSetRecyclerViewHeight(recyclerView: RecyclerView) { val adapter = recyclerView.adapter ?: return var totalHeight = 0 val widthSpec = View.MeasureSpec.makeMeasureSpec(recyclerView.width, View.MeasureSpec.EXACTLY) for (i in 0 until adapter.itemCount) { val viewHolder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i)) adapter.onBindViewHolder(viewHolder, i) viewHolder.itemView.measure(widthSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)) totalHeight += viewHolder.itemView.measuredHeight } val layoutParams = recyclerView.layoutParams layoutParams.height = totalHeight + recyclerView.paddingTop + recyclerView.paddingBottom recyclerView.layoutParams = layoutParams }
调用时机:在notifyDataSetChanged()之后执行:
requestsRecyclerView.adapter?.notifyDataSetChanged() calculateAndSetRecyclerViewHeight(requestsRecyclerView) groupsRecyclerView.adapter?.notifyDataSetChanged() calculateAndSetRecyclerViewHeight(groupsRecyclerView)
注意:此方法仅适用于Item高度固定的场景,动态高度Item会出现计算偏差。
方案3:合并为单RecyclerView+多类型Item(最优性能方案)
从根本上避免嵌套滚动问题,将两个列表合并为一个RecyclerView,通过多类型Item实现:
- 定义Item类型枚举:
enum class ListItemType { REQUEST_TITLE, REQUEST_ITEM, GROUP_TITLE, GROUP_ITEM }
- 适配器中重写
getItemViewType(),根据位置返回对应类型 - 在
onCreateViewHolder()中创建不同类型的ViewHolder - 合并两个数据源,插入标题Item后统一提交给适配器
这种方案符合RecyclerView的设计初衷,既能解决显示问题,又能提升页面性能。
内容的提问来源于stack exchange,提问作者Sunbey13
相关产品推荐
相关产品推荐

