RecyclerView的Item可见性检测助手
序言
ItemVisibilityHelper
可以计算itemView
在RecyclerView
中的可见范围,帮助我们实现在RecyclerView
上播放视频等其他功能。
基本逻辑
首先我们有一个前置条件,即RecyclerView
中同一时刻只允许有一个itemView
处于活跃状态。然后我们来梳理一下RecyclerView
中某一个itemView
在什么时候会被激活。
这里我们只在RecyclerView
滑动停止后进行激活操作,滑动中我们会去做关闭的操作。所以,
- 滑动中:有
itemview
被移出显示范围,就关闭它。 - 滑动停止:如果激活中的
itemview
可见范围大于50%则保持不变,否则关闭。同时激活显示范围中可见范围最大并且不小于50%的itemview
,如果可见范围最大的依然是激活中的itemview
,那么可见范围大于50%则保持不变,否则不会关闭,会进入暂停状态,等可见范围不小于50%时恢复。 另外,如果激活中的itemview
可见范围小于50%,但是显示范围中没有其他itemview
需要激活, 此时这个itemview
也进入会暂停状态,等可见范围不小于50%时恢复。
这便是ItemVisibilityHelper
激活和关闭itemview
的基本逻辑。
核心代码
ItemVisibilityHelper
的工作原理是通过获取itemView
在显示范围中的位置,从而计算出itemView
的可见百分比,进而可以实现我们上述的基本逻辑。在实现基本逻辑的同时,也需要考虑一些其他细节。例如,当显示范围中有多个可见百分比一样的itemView
时,我们应该激活哪一个呢?在ItemVisibilityHelper
中的规则是激活离上一个被关闭的itemView
最近的那一个。那么这时我们就需要判断被关闭的itemView
是从顶部还是底部被移出显示范围的。当然,还有其他我们需要考虑的情况,RecyclerView
是竖向还是横向、是否是反向布局等。
/*计算itemView可见百分比*/
fun calculateVisiblePercent(itemview: View?, orientation: Int): Int {
if (view == null) return 0
if (view.getLocalVisibleRect(outRect)) {
val viewHeight: Int = if (orientation == RecyclerView.HORIZONTAL)
view.width else view.height
val displayHeight: Int = if (orientation == RecyclerView.HORIZONTAL) {
_recyclerView?.computeHorizontalScrollExtent() ?: 0
} else {
_recyclerView?.computeVerticalScrollExtent() ?: 0
}
//outRect.top==outRect.left
val top: Int = if (orientation == RecyclerView.HORIZONTAL)
outRect.left else outRect.top
//outRect.bottom==outRect.right
val bottom: Int = if (orientation == RecyclerView.HORIZONTAL)
outRect.right else outRect.bottom
/*哪一边被遮挡的多则认为更靠近那一边。例如,顶部被遮挡的大于底部被遮挡的,则认为view的位置 更靠近顶部。
有可能顶部和底部被遮挡的一样则默认view更靠近顶部。
实际上也无关紧要,因为出现相等的情况发生在,
1、view高度大于显示高度且view正好在显示范围的正中,顶部和底部的遮挡范围相同,
2、view高度小于等于显示范围高度,此时顶部和底部都没有被遮挡,顶部和底部的遮挡范围都为0,
此时不会发生激活Item动作,不影响我们去判断是更靠近顶部还是底部。
而随着view的滑动总有一边会被遮挡或遮挡范围不一致,进而能准确判断出是更靠近顶部还是底部。*/
//top等于顶部被遮挡的高度,(viewHeight - bottom)等于底部被遮挡的高度。
_isTopCloser = if (_isReverseLayout)
top < (viewHeight - bottom) else top >= (viewHeight - bottom)
//view高度大于等于显示范围高度的情况。
if (viewHeight >= displayHeight) {
//(bottom - top)等于view在显示范围中的高度。
return (bottom - top) * 100 / displayHeight
}
/*(top > 0)顶部被遮挡。
(viewHeight - bottom > 0)底部被遮挡。
以上条件有可能都不成立,此时view的高度小于或等于显示范围高度,并且完全在显示范围中,
因此percent默认为100。*/
var percent = 100
if (top > 0) {
percent = (viewHeight - top) * 100 / viewHeight
} else if (viewHeight - bottom > 0) {
percent = bottom * 100 / viewHeight
}
return percent
}
return 0
}
依据被关闭的itemView
是从哪个方向被移出显示范围的来决定从哪个方向遍历RecyclerView
显示范围中可见百分比最大的itemView
。
//简略代码
fun findNextMostVisibleItem(firstPosition: Int, lastPosition: Int) {
...
//从前往后
for (index in firstPosition..lastPosition) {
val item = getItem(index)
val percent = getItemVisiblePercent(item)
//得到itemView的可见百分比
}
...
}
//简略代码
fun findLastMostVisibleItem(lastPosition: Int, firstPosition: Int) {
...
//从后往前
for (index in lastPosition downTo firstPosition) {
val item = getItem(index)
val percent = getItemVisiblePercent(item)
//得到itemView的可见百分比
}
...
}
最后
ItemVisibilityHelper
实现了一个非常简单的功能,完全脱离相关业务代码,只负责激活和关闭itewView
。同时使用也非常简单,只需要绑定到RecyclerView
即可,相关业务功能在对应的时机处理就好。
helper.attachToRecyclerView(recyclerView) {
activateItem { view, position ->
//itemView被激活
}
deactivateItem { view, position ->
//itemView被关闭
}
pauseItem { view, position ->
//itemView被暂停
}
resumeItem { view, position ->
//itemView被恢复
}
}
更多细节和使用方法欢迎查看项目主页。项目地址 谢谢你读到这里。
转载自:https://juejin.cn/post/7230595863692329019