嵌套垂直RecyclerView无法显示全部内容,改为水平则正常
问题描述
实现一个垂直RecyclerView,其列表项包含一个TextView和一个垂直子RecyclerView,数据来源于API。目前遇到的问题是:子RecyclerView无法滚动,且仅能显示前4条左右的内容;但将子RecyclerView设置为水平方向时,该问题消失,可正常滚动并查看所有条目。
父RecyclerView Adapter代码
class TitlesAdapter( private val titlesList: List<Title>, private val articlesList: List<Article> ): RecyclerView.Adapter<TitlesAdapter.Holder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { val binding = TitlesBinding.inflate(LayoutInflater.from(parent.context),parent,false) return Holder(binding) } override fun onBindViewHolder(holder: Holder, position: Int) { val itemRow = titlesList.get(position) holder.bindRow(itemRow, articlesList) } override fun getItemCount(): Int { return titlesList.size } class Holder(var binding: TitlesBinding): RecyclerView.ViewHolder(binding.root){ fun bindRow( title: Title, articles: List<Article> ) { binding.tvTitleName.text = title.name val filteredList = articles.filter { it.category == title.id } binding.rvChild.adapter = ChildRVAdapter(filteredList) binding.rvChild.layoutManager = LinearLayoutManager(itemView.context) } } }
子RecyclerView Adapter代码
class ChildRVAdapter(private val articleList: List<Article>): RecyclerView.Adapter<ChildRVAdapter.Holder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { val binding = RowArticleBinding.inflate(LayoutInflater.from(parent.context),parent,false) return Holder(binding) } override fun onBindViewHolder(holder: Holder, position: Int) { val itemRow = articleList.get(position) holder.bindRow(itemRow) } override fun getItemCount(): Int { return articleList.size } class Holder(var binding: RowArticleBinding): RecyclerView.ViewHolder(binding.root), View.OnClickListener{ fun bindRow( article: Article ) { binding.tvTitle.text = article.title binding.tvDesc.text = article.description } } }
父RecyclerView列表项布局
<?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="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_title_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:textColor="@color/textBlack" android:fontFamily="@font/playfair_bold" android:layout_marginBottom="18dp" android:text="Introduction"/> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_child" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
子RecyclerView列表项布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Introduction from Annabel Karmel" android:textColor="@color/textBlack" android:textSize="14sp" android:fontFamily="@font/montserrat_bold"/> <TextView android:id="@+id/tv_desc" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/dustyGrey" android:layout_marginTop="6dp" android:textSize="14sp" android:fontFamily="@font/montserrat_medium"/> </LinearLayout>
解决方案
问题根源
垂直嵌套RecyclerView时,父RecyclerView的默认LayoutManager无法准确计算子RecyclerView的实际高度(子RecyclerView设为wrap_content),导致子RecyclerView被限制高度,无法滚动且仅显示部分内容;而水平方向时,宽度计算不受父垂直RecyclerView的布局逻辑影响,因此能正常显示和滚动。
方案1:自定义LayoutManager让子RecyclerView正确测量高度
创建自定义LinearLayoutManager,强制计算所有子项的总高度,确保子RecyclerView能完整展示内容:
class WrapContentLinearLayoutManager(context: Context) : LinearLayoutManager(context) { override fun onMeasure(recycler: RecyclerView.Recycler, state: RecyclerView.State, widthSpec: Int, heightSpec: Int) { var totalHeight = 0 val widthSize = View.MeasureSpec.getSize(widthSpec) // 遍历所有子项计算总高度 for (i in 0 until itemCount) { val childView = recycler.getViewForPosition(i) measureChild(childView, widthSpec, heightSpec) totalHeight += childView.measuredHeight recycler.recycleView(childView) } // 重新构造高度测量参数 val newHeightSpec = if (View.MeasureSpec.getMode(heightSpec) == View.MeasureSpec.EXACTLY) { heightSpec } else { View.MeasureSpec.makeMeasureSpec(totalHeight, View.MeasureSpec.EXACTLY) } super.onMeasure(recycler, state, widthSpec, newHeightSpec) } }
然后在父Adapter的Holder中替换原LayoutManager:
// 在bindRow方法中修改 binding.rvChild.layoutManager = WrapContentLinearLayoutManager(itemView.context)
方案2:禁用嵌套滚动+优化Adapter复用
- 在子RecyclerView的布局中添加禁用嵌套滚动的属性:
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_child" android:layout_width="match_parent" android:layout_height="wrap_content" android:nestedScrollingEnabled="false"/>
- 优化父Adapter,避免每次绑定都创建新的子Adapter和LayoutManager,提升性能同时解决测量问题:
class TitlesAdapter( private val titlesList: List<Title>, private val articlesList: List<Article> ): RecyclerView.Adapter<TitlesAdapter.Holder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { val binding = TitlesBinding.inflate(LayoutInflater.from(parent.context),parent,false) return Holder(binding) } override fun onBindViewHolder(holder: Holder, position: Int) { val itemRow = titlesList[position] holder.bindRow(itemRow, articlesList) } override fun getItemCount(): Int { return titlesList.size } class Holder(var binding: TitlesBinding): RecyclerView.ViewHolder(binding.root){ private val childAdapter = ChildRVAdapter(emptyList()) init { // 初始化时就设置LayoutManager和Adapter,避免重复创建 binding.rvChild.layoutManager = LinearLayoutManager(itemView.context) binding.rvChild.adapter = childAdapter binding.rvChild.isNestedScrollingEnabled = false } fun bindRow( title: Title, articles: List<Article> ) { binding.tvTitleName.text = title.name val filteredList = articles.filter { it.category == title.id } // 更新子Adapter数据而非创建新Adapter childAdapter.updateData(filteredList) } } } // 给子Adapter添加数据更新方法 class ChildRVAdapter(private var articleList: List<Article>): RecyclerView.Adapter<ChildRVAdapter.Holder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { val binding = RowArticleBinding.inflate(LayoutInflater.from(parent.context),parent,false) return Holder(binding) } override fun onBindViewHolder(holder: Holder, position: Int) { val itemRow = articleList[position] holder.bindRow(itemRow) } override fun getItemCount(): Int { return articleList.size } // 添加数据更新方法 fun updateData(newList: List<Article>) { articleList = newList notifyDataSetChanged() } class Holder(var binding: RowArticleBinding): RecyclerView.ViewHolder(binding.root), View.OnClickListener{ fun bindRow( article: Article ) { binding.tvTitle.text = article.title binding.tvDesc.text = article.description } } }
方案3:使用单一RecyclerView+多类型布局(推荐)
嵌套RecyclerView容易带来性能问题和布局冲突,推荐使用单一RecyclerView,通过多类型Item实现相同效果:
- 定义两种ItemType:标题类型、文章类型
- 在Adapter中根据位置判断ItemType,加载对应的布局和数据
- 这种方式避免了嵌套,性能更优且无布局测量问题
内容的提问来源于stack exchange,提问作者Alex Petev
相关产品推荐
相关产品推荐

