如何在回收器视图中使列表视图平滑滚动条



在ListView中,当您的项目大小不同时,滚动条在滚动过程中会更改大小,如果必须从10dp高度变为20dp,它将逐步进行(11dp,12dp等),因此滚动条看起来很平滑。

但是,在RecyclerView中,行为并不相同,滚动条将立即从10dp跳到20dp,导致它看起来很糟糕。

如何在ReyclerView中拥有与ListView相同的滚动条?

您可以通过扩展类并实现computeVerticalScrollRange()computeVerticalScrollOffset()computeVerticalScrollExtent()来更改 RecyclerView 中滚动条的行为。

这是我的做法:

package com.franckrj.jva.utils
import android.content.Context
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.util.AttributeSet
import android.view.View
class SmoothScrollbarRecyclerView : RecyclerView {
/* Saving these values only work because functions are (i think) always called in
* the order range > offset > extent. If, for whatever reason, it doesn't work
* you can still replace these by a call to the corresponding function. */
private var lastAverageSizeOfOneItemComputed: Double = 0.0
private var lastRangeComputed: Int = 0
private var lastOffsetComputed: Int = 0
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
override fun computeVerticalScrollRange(): Int {
val linearLm: LinearLayoutManager? = layoutManager as? LinearLayoutManager
return if (linearLm != null) {
if (scrollbarNeedToBeShown(linearLm)) {
lastAverageSizeOfOneItemComputed = computeAverageSizeOfOneItem(linearLm)
lastRangeComputed = (lastAverageSizeOfOneItemComputed * adapter.itemCount).toInt()
lastRangeComputed
} else {
0
}
} else {
super.computeVerticalScrollRange()
}
}
override fun computeVerticalScrollOffset(): Int {
val linearLm: LinearLayoutManager? = layoutManager as? LinearLayoutManager
return if (linearLm != null) {
if (scrollbarNeedToBeShown(linearLm)) {
val firstItemPosition: Int = linearLm.findFirstVisibleItemPosition()
val firstItem: View = linearLm.findViewByPosition(firstItemPosition)
lastOffsetComputed = ((firstItemPosition + getFractionOfItemTopNotVisible(firstItem)) * lastAverageSizeOfOneItemComputed).toInt()
lastOffsetComputed
} else {
0
}
} else {
super.computeVerticalScrollOffset()
}
}
override fun computeVerticalScrollExtent(): Int {
val linearLm: LinearLayoutManager? = layoutManager as? LinearLayoutManager
return if (linearLm != null) {
if (scrollbarNeedToBeShown(linearLm)) {
val lastItemPosition: Int = linearLm.findLastVisibleItemPosition()
val lastItem: View = linearLm.findViewByPosition(lastItemPosition)
lastRangeComputed - lastOffsetComputed - (((adapter.itemCount - 1 - lastItemPosition) + getFractionOfItemBottomNotVisible(lastItem)) * lastAverageSizeOfOneItemComputed).toInt()
} else {
0
}
} else {
super.computeVerticalScrollExtent()
}
}
private fun scrollbarNeedToBeShown(linearLm: LinearLayoutManager): Boolean {
val firstItemPosition: Int = linearLm.findFirstVisibleItemPosition()
return (firstItemPosition != NO_POSITION && (linearLm.findFirstCompletelyVisibleItemPosition() > 0 || linearLm.findLastCompletelyVisibleItemPosition() < (adapter.itemCount - 1)))
}
private fun computeAverageSizeOfOneItem(linearLm: LinearLayoutManager): Double {
var proportionnalSizeOfVisiblesItems = 0
var numberOfItemsComputed: Double
val firstItemPosition: Int = linearLm.findFirstVisibleItemPosition()
val firstItem: View = linearLm.findViewByPosition(firstItemPosition)
val lastItemPosition: Int = linearLm.findLastVisibleItemPosition()
if (firstItemPosition != lastItemPosition) {
val lastItem: View = linearLm.findViewByPosition(lastItemPosition)
val fractionOfScreenOccupedByFirstItem: Double = getFractionOfScreenOccupedByItem(firstItem)
val fractionOfScreenOccupedByLastItem: Double = getFractionOfScreenOccupedByItem(lastItem)
proportionnalSizeOfVisiblesItems += (getViewOutsideHeight(firstItem) * fractionOfScreenOccupedByFirstItem).toInt()
proportionnalSizeOfVisiblesItems += (getViewOutsideHeight(lastItem) * fractionOfScreenOccupedByLastItem).toInt()
numberOfItemsComputed = fractionOfScreenOccupedByFirstItem + fractionOfScreenOccupedByLastItem
for (posIndex: Int in (firstItemPosition + 1)..(lastItemPosition - 1)) {
proportionnalSizeOfVisiblesItems += getViewOutsideHeight(linearLm.findViewByPosition(posIndex))
numberOfItemsComputed += 1.0
}
} else {
/* Only one item is visible, so we assume that every items have his height. */
proportionnalSizeOfVisiblesItems = getViewOutsideHeight(firstItem)
numberOfItemsComputed = 1.0
}
return (proportionnalSizeOfVisiblesItems / numberOfItemsComputed)
}
private fun getFractionOfScreenOccupedByItem(item: View): Double {
val tmpFractionOfScreenOccupedByItem: Double = (minOf(getRecyclerViewInsideBottom(), getViewOutsideBottom(item)) - maxOf(getRecyclerViewInsideTop(), getViewOutsideTop(item))) / getRecyclerViewInsideBottom().toDouble()
/* Just in case, but it's probably useless. */
return tmpFractionOfScreenOccupedByItem.coerceIn(0.0, 1.0)
}
private fun getFractionOfItemTopNotVisible(item: View): Double {
val tmpFractionOfItemTopNotVisible: Double = maxOf(getRecyclerViewInsideTop() - getViewOutsideTop(item), 0) / getViewOutsideHeight(item).toDouble()
/* Just in case, but it's probably useless. */
return tmpFractionOfItemTopNotVisible.coerceIn(0.0, 1.0)
}
private fun getFractionOfItemBottomNotVisible(item: View): Double {
val tmpFractionOfItemBottomNotVisible: Double = maxOf(getViewOutsideBottom(item) - getRecyclerViewInsideBottom(), 0) / getViewOutsideHeight(item).toDouble()
/* Just in case, but it's probably useless. */
return tmpFractionOfItemBottomNotVisible.coerceIn(0.0, 1.0)
}
private fun getViewOutsideTop(view: View): Int {
val layoutParam = view.layoutParams as RecyclerView.LayoutParams
return view.top - layoutParam.topMargin
}
private fun getViewOutsideBottom(view: View): Int {
val layoutParam = view.layoutParams as RecyclerView.LayoutParams
return view.bottom + layoutParam.bottomMargin
}
private fun getViewOutsideHeight(view: View): Int {
return getViewOutsideBottom(view) - getViewOutsideTop(view)
}
private fun getRecyclerViewInsideTop(): Int {
return paddingTop
}
private fun getRecyclerViewInsideBottom(): Int {
return height - paddingBottom
}
}

我的实现只有在您使用LinearLayoutManager时才有效,否则它将仅使用默认实现。它还考虑了回收器视图中项目的边距和回收器视图的填充。

基本上,我只是根据当前可见的项目计算项目的平均大小,大小之间的平滑过渡仅使用部分可见的项目大小的百分比来完成,使用的百分比是(项目的可见高度)/(RecyclerView 的总高度)。

当您知道项目的平均大小时,您可以通过将其乘以项目数来了解列表的总大小,通过将完全隐藏在列表上方的项目数加上第一个项目的隐藏顶部的大小(在 0 到 1 之间)乘以项目的平均大小,您知道拇指的顶部偏移量, 通过计算拇指的底部偏移量,您可以像计算顶部偏移量一样计算拇指的大小。

最新更新