约束于RecyclerView的TextView无法随列表项消失而上移的问题求助
问题描述
我现在遇到一个布局动态调整的问题,想请大家帮忙看看:我有两个RecyclerView和一个TextView,需求是点击顶部的recyclerViewNotClickedItems里的条目后,该条目会从顶部列表消失并出现在底部的recyclerViewClickedItems中。同时,我希望中间的settingsYouAlreadySeeTxt这个TextView能随着顶部列表的条目减少逐渐上移,直到顶部列表空了之后,TextView能顶到屏幕最上方。
目前我的布局代码是这样的:
<RecyclerView android:id="@+id/recyclerViewNotClickedItems" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@+id/settingsYouAlreadySeeTxt" /> <TextView android:id="@+id/settingsYouAlreadySeeTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/settings_you_already_seen" android:textSize="40sp" android:visibility="invisible" app:layout_constraintStart_toStartOf="@+id/recyclerViewNotClickedItems" app:layout_constraintTop_toBottomOf="@+id/recyclerViewNotClickedItems"> </TextView> <RecyclerView android:id="@+id/recyclerViewClickedItems" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@+id/settingsYouAlreadySeeTxt" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" />
对应的逻辑代码部分:
adapterCheckedList = RecyclerViewListAdapter() adapterNotCheckedList = RecyclerViewListAdapter(clickListener = { view: View, listItem: Any, position: Int -> binding.settingsYouAlreadySeeTxt.visibility = View.VISIBLE // 这里除了设置可见性,我还想在unCheckedList每次变小时让这个TextView上移 val removedItem = unCheckedList.removeAt(position) checkedList.add(removedItem) adapterNotCheckedList.submitList(unCheckedList) adapterCheckedList.submitList(removedItem) })
现在的问题是,TextView虽然能显示出来,但不会随着顶部RecyclerView的高度变化而上移,请问该怎么调整布局和逻辑才能实现需求呢?
我的解决方案
嘿,这个问题我之前做类似的动态列表布局时也碰到过!核心问题是ConstraintLayout对wrap_content的View不会自动实时重算约束位置,再加上你的代码里还有几个小细节没处理好,我给你一步步拆解解决方案:
1. 先给布局加个关键属性,让顶部RecyclerView的高度真正能动态收缩
首先修改顶部RecyclerView的布局,加上app:layout_constrainedHeight="true"这个属性,它能强制RecyclerView严格遵守上下约束的限制,不会因为内部内容变化就“乱跑”:
<RecyclerView android:id="@+id/recyclerViewNotClickedItems" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constrainedHeight="true" <!-- 就是这个属性! --> app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@+id/settingsYouAlreadySeeTxt" />
另外给中间的TextView也加个底部约束,让它和下方的RecyclerView建立依赖,这样整个布局的链条更完整:
<TextView android:id="@+id/settingsYouAlreadySeeTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/settings_you_already_seen" android:textSize="40sp" android:visibility="invisible" app:layout_constraintStart_toStartOf="@+id/recyclerViewNotClickedItems" app:layout_constraintTop_toBottomOf="@+id/recyclerViewNotClickedItems" app:layout_constraintBottom_toTopOf="@+id/recyclerViewClickedItems" /> <!-- 新增这个约束 -->
2. 修正适配器更新逻辑,主动触发布局刷新
你代码里的adapterCheckedList.submitList(removedItem)是错的哦!submitList需要传入整个列表的实例,不是单个item,不然底部RecyclerView根本不会正确显示所有已点击的条目。另外,每次更新数据后,我们要主动通知布局重新计算,这样TextView才会跟着上移:
adapterCheckedList = RecyclerViewListAdapter() adapterNotCheckedList = RecyclerViewListAdapter(clickListener = { view: View, listItem: Any, position: Int -> // 先处理数据移动 val removedItem = unCheckedList.removeAt(position) checkedList.add(removedItem) // 注意:submitList要传新的ArrayList实例,不然适配器可能检测不到数据变化 adapterNotCheckedList.submitList(ArrayList(unCheckedList)) adapterCheckedList.submitList(ArrayList(checkedList)) // 控制TextView的可见性和位置 binding.settingsYouAlreadySeeTxt.visibility = View.VISIBLE // 当顶部列表为空时,直接把TextView顶到屏幕顶部 if (unCheckedList.isEmpty()) { // 用ConstraintSet动态修改约束 ConstraintSet().apply { clone(binding.root) // 把TextView的顶部绑定到父布局顶部 connect(binding.settingsYouAlreadySeeTxt.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) applyTo(binding.root) // 顺便把空的顶部RecyclerView设为GONE,避免占位置 binding.recyclerViewNotClickedItems.visibility = View.GONE } } else { // 顶部还有条目时,恢复原来的约束关系 ConstraintSet().apply { clone(binding.root) connect(binding.settingsYouAlreadySeeTxt.id, ConstraintSet.TOP, binding.recyclerViewNotClickedItems.id, ConstraintSet.BOTTOM) applyTo(binding.root) binding.recyclerViewNotClickedItems.visibility = View.VISIBLE } // 主动通知布局刷新,让约束生效 binding.root.requestLayout() } })
3. 为什么这样能行?
layout_constrainedHeight="true"让顶部RecyclerView的高度会随着条目减少而真实缩小,不会保留之前的高度- 用
ConstraintSet动态切换约束关系,既能在列表非空时保持TextView在RecyclerView下方,又能在列表为空时直接顶到顶部 - 每次数据更新后调用
requestLayout(),强制ConstraintLayout重新计算所有View的位置,保证TextView实时跟着上移 - 修正了
submitList的用法,确保两个RecyclerView都能正确刷新内容
你可以试试看这个方案,应该就能解决你的问题啦!
内容来源于stack exchange

