Kotlin中RecyclerView的LayoutManager设置stackFromEnd与reverseLayout不生效问题
问题原因与解决方案
1. 判断RecyclerView是否可滚动的正确实现
你注释中调用的canScrollVertically本身是系统提供的正确API,判断失效的核心原因是调用时机不对,必须等RecyclerView完成条目测量、布局之后调用才能得到正确结果。
也可以手动计算高度做判断,两种实现如下:
private fun isScrollable(recyclerView: RecyclerView, layoutManager: LinearLayoutManager): Boolean { // 方案1:系统API实现,1代表向下滚动、-1代表向上滚动,只要有一个方向可滚动就说明内容超出视图 return recyclerView.canScrollVertically(1) || recyclerView.canScrollVertically(-1) // 方案2:手动计算实现,逻辑更直观 /* // 所有条目总高度 val totalContentHeight = layoutManager.computeVerticalScrollRange() // RecyclerView 可展示内容的高度(减去上下padding) val visibleHeight = recyclerView.height - recyclerView.paddingTop - recyclerView.paddingBottom return totalContentHeight > visibleHeight */ }
2. 代码移动后判断失效的原因与修复方案
失效原因
你将LayoutManager设置移到setupAdapter中时,direction函数的执行时机是适配器刚初始化、还未提交列表数据的阶段,此时适配器中条目数量为0,RecyclerView还没有完成条目测量,所以不管调用哪种判断方法拿到的结果都是错误的。
同时你原有代码存在两个隐藏问题,也是导致配置偶发不生效的原因:
- 每次Observer收到数据变更都重新创建LayoutManager实例替换原有配置,不必要的重建会导致滚动状态丢失、配置异常
- 如果你用的是ListAdapter的
submitList方法,它本身是异步计算差分更新的,调用后立刻访问布局状态拿到的还是旧数据的计算结果
修复方案
调整初始化逻辑,LayoutManager只需要初始化一次,数据变更后等列表更新完成再执行调整逻辑即可,示例代码如下:
class activity : BaseActivity() { private var cAdapter: cdAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 其他初始化逻辑 initRecyclerView() setupObserver() } // RecyclerView 只做一次初始化,不要每次数据更新都重建配置 private fun initRecyclerView() { cAdapter = cdAdapter() binding.cRecyclerView.apply { adapter = cAdapter layoutManager = LinearLayoutManager(context) } } fun setupObserver(){ viewModel.list.observe(this, { value -> // submitList第二个参数是数据更新完成的回调,保证拿到最新数据的布局状态 cAdapter?.submitList(value) { binding.cRecyclerView.afterMeasured { val layoutManager = layoutManager as LinearLayoutManager direction(layoutManager) // 此处调用可滚动判断即可得到正确结果 if (isScrollable(this, layoutManager)) { logE("Yes can scroll") } else { logE("No scroll") } } } }) } private fun direction(linearLayoutManager: LinearLayoutManager) { linearLayoutManager.apply { if (viewModel.itemsAreMorethanTwo()) { stackFromEnd = true reverseLayout = true val index = viewModel.findLastBiggerValue() if (index != null && index >= 0) { scrollToPosition(index) } } else { stackFromEnd = false reverseLayout = true } } } // 优化afterMeasured方法,兼容已测量完成的视图直接触发回调 inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) { if (measuredWidth > 0 && measuredHeight > 0) { f() return } viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { if (measuredWidth > 0 && measuredHeight > 0) { viewTreeObserver.removeOnGlobalLayoutListener(this) f() } } }) } }
内容的提问来源于stack exchange,提问作者Compose Learner
相关产品推荐
相关产品推荐

