Android RecyclerView滚动时未重新测量高度问题求助
问题
我的应用包含多个嵌套RecyclerView,其中一个横向RecyclerView的Item布局包含ImageView和作为新课程指示器的TextView。该指示器会在用户打开对应课程后设为View.GONE隐藏,仅在部分课程中显示(设为View.VISIBLE)。
当前该RecyclerView的layout_height设为wrap_content,当屏幕中仅显示无指示器的Item时,RecyclerView高度会适配ImageView;但横向滚动到带有指示器的Item时,RecyclerView无法重新计算高度,导致指示器视图被裁切。
我已尝试设置setHasFixedSize(false)等方法,但未解决问题。目前仅能通过固定RecyclerView高度解决,但希望获得更具扩展性的最优方案。请问如何让RecyclerView重新计算高度?
相关代码
RecyclerView布局代码
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_video_packs" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/margin_16dp" android:clipToPadding="false" android:paddingEnd="16dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/rv_continue_watching" />
Item布局代码
<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="wrap_content" android:layout_height="wrap_content" android:focusable="true" android:clickable="true" android:orientation="vertical"> <ImageView android:id="@+id/iv_learn_item" android:visibility="invisible" android:layout_width="134dp" android:layout_height="200dp" android:adjustViewBounds="true" android:layout_marginStart="16dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" android:foreground="?attr/selectableItemBackground" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/video_update_tv_guideline" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintGuide_begin="195dp" android:orientation="horizontal" /> <TextView android:id="@+id/video_update_tv" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="@id/iv_learn_item" app:layout_constraintEnd_toEndOf="@id/iv_learn_item" app:layout_constraintTop_toBottomOf="@+id/video_update_tv_guideline" android:layout_marginStart="@dimen/dimen_11dp" android:layout_marginEnd="@dimen/dimen_11dp" style="@style/content_update_pill" /> </androidx.constraintlayout.widget.ConstraintLayout>
Adapter初始化代码
rvVideoPacks.layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false) rvVideoPacks.setHasFixedSize(false) rvVideoPacks.itemAnimator = null rvVideoPacks.isNestedScrollingEnabled = true rvVideoPacks.adapter = videoPackAdapter
补充:尝试将指示器设为invisible会导致底部留白,影响显示效果,希望RecyclerView能动态适配高度。
解决方案
方案1:自定义横向LinearLayoutManager重写测量逻辑
RecyclerView的wrap_content在横向布局下默认只会测量初始可见的Item高度,滚动后不会重新计算。可以自定义LinearLayoutManager,强制每次滚动后重新测量RecyclerView的高度:
class DynamicHeightLinearLayoutManager(context: Context) : LinearLayoutManager(context, HORIZONTAL, false) { override fun onLayoutCompleted(state: RecyclerView.State?) { super.onLayoutCompleted(state) val recyclerView = recyclerView ?: return recyclerView.post { recyclerView.requestLayout() } } override fun onScrollStateChanged(state: Int) { super.onScrollStateChanged(state) if (state == RecyclerView.SCROLL_STATE_IDLE) { val recyclerView = recyclerView ?: return recyclerView.post { recyclerView.requestLayout() } } } }
替换原有的LayoutManager:
rvVideoPacks.layoutManager = DynamicHeightLinearLayoutManager(itemView.context)
方案2:在Adapter中更新指示器状态时主动触发重测量
当在Adapter的onBindViewHolder中设置指示器可见性时,主动通知RecyclerView重新计算高度:
override fun onBindViewHolder(holder: VideoPackViewHolder, position: Int) { val item = dataList[position] holder.videoUpdateTv.visibility = if (item.hasNewCourse) View.VISIBLE else View.GONE holder.itemView.post { rvVideoPacks.requestLayout() } }
方案3:优化Item布局结构(推荐)
调整Item布局,让根容器始终占据包含指示器时的最大高度,避免RecyclerView高度频繁变化:
修改Item根布局,设置minHeight为ImageView高度加上指示器的高度与间距之和(比如假设指示器总占用高度为20dp,ImageView高度200dp,则设为220dp):
<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="wrap_content" android:layout_height="wrap_content" android:minHeight="220dp" android:focusable="true" android:clickable="true" android:orientation="vertical"> <!-- 原有控件保持不变 --> </androidx.constraintlayout.widget.ConstraintLayout>
这样RecyclerView初始就会以最大高度渲染,滚动时不会出现裁切,同时指示器设为GONE时也不会产生留白。
内容的提问来源于stack exchange,提问作者Saneen K P

