You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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(最简便有效)

  1. 将外层ScrollView替换为androidx.core.widget.NestedScrollView,并设置其高度为match_parent
  2. 给两个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实现:

  1. 定义Item类型枚举:
enum class ListItemType {
    REQUEST_TITLE, REQUEST_ITEM, GROUP_TITLE, GROUP_ITEM
}
  1. 适配器中重写getItemViewType(),根据位置返回对应类型
  2. 在onCreateViewHolder()中创建不同类型的ViewHolder
  3. 合并两个数据源,插入标题Item后统一提交给适配器

这种方案符合RecyclerView的设计初衷,既能解决显示问题,又能提升页面性能。


内容的提问来源于stack exchange,提问作者Sunbey13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 18:25:28