回收视图-如何平滑滚动到顶部的项目在一定的位置?

在回收视图中,我可以通过以下方法突然滚动到选定项的顶部:

((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(position, 0);

但是,这个 突然会将项目移动到顶部位置。我想移动到项目 平稳地的顶部。

我也试过:

recyclerView.smoothScrollToPosition(position);

但它不工作,因为它没有移动项目的位置选定的顶部。它只是滚动列表,直到位置上的项可见为止。

151014 次浏览

为此,您必须创建一个自定义 LayoutManager

public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager {


public LinearLayoutManagerWithSmoothScroller(Context context) {
super(context, VERTICAL, false);
}


public LinearLayoutManagerWithSmoothScroller(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}


@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}


private class TopSnappedSmoothScroller extends LinearSmoothScroller {
public TopSnappedSmoothScroller(Context context) {
super(context);


}


@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return LinearLayoutManagerWithSmoothScroller.this
.computeScrollVectorForPosition(targetPosition);
}


@Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}
}

使用它来创建回收视图,并调用 moothScrollToposition。

例如:

 recyclerView.setLayoutManager(new LinearLayoutManagerWithSmoothScroller(context));
recyclerView.smoothScrollToPosition(position);

这将滚动到指定位置的回收视图项的顶部。

希望这能帮上忙。

RecyclerView的设计是可扩展的,所以不需要为了执行滚动而将 LayoutManager子类化(作为 德罗伊德夫建议)。

相反,只需创建一个首选项为 SNAP_TO_STARTSmoothScroller:

RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(context) {
@Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
};

现在将要滚动的位置设置为:

smoothScroller.setTargetPosition(position);

并将 SmoothScroll 传递给 LayoutManager:

layoutManager.startSmoothScroll(smoothScroller);

我找到的滚动 RecyclerView的最简单方法如下:

// Define the Index we wish to scroll to.
final int lIndex = 0;
// Assign the RecyclerView's LayoutManager.
this.getRecyclerView().setLayoutManager(this.getLinearLayoutManager());
// Scroll the RecyclerView to the Index.
this.getLinearLayoutManager().smoothScrollToPosition(this.getRecyclerView(), new RecyclerView.State(), lIndex);

我们可以试试这样

    recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView,new RecyclerView.State(), recyclerView.getAdapter().getItemCount());

也许@droidev 方法是正确的,但是我只是想发布一些不同的东西,它们基本上做同样的工作,并且不需要 LayoutManager 的扩展。

注意 在这里-这将工作得很好,如果你的项目(你想滚动在列表的顶部)是可见的屏幕上,你只是想滚动到顶部自动。当列表中的最后一个项目有一些操作时,它非常有用,这些操作会在同一列表中添加新项目,并且您希望将用户的注意力集中在新添加的项目上:

int recyclerViewTop = recyclerView.getTop();
int positionTop = recyclerView.findViewHolderForAdapterPosition(positionToScroll) != null ? recyclerView.findViewHolderForAdapterPosition(positionToScroll).itemView.getTop() : 200;
final int calcOffset = positionTop - recyclerViewTop;
//then the actual scroll is gonna happen with (x offset = 0) and (y offset = calcOffset)
recyclerView.scrollBy(0, offset);

这个想法很简单: 1. 得到回收视图元素的顶点坐标; 2. 我们需要得到想要滚动到顶部的视图项的顶部坐标; 3. 最后用计算出的偏移量来做

recyclerView.scrollBy(0, offset);

200只是一个硬编码的整数值示例,如果视图持有者项不存在,您可以使用它,因为这也是可能的。

在 LinearSmoothScroll 中重写 CalculateDyToMakeVisible/CalculateDxToMakeVisible 函数以实现偏移量 Y/X 位置

override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int {
return super.calculateDyToMakeVisible(view, snapPreference) - ConvertUtils.dp2px(10f)
}

这是我在 科特林中写的 扩展函数扩展函数,用于 RecyclerView(基于@Paul Woitaschek 的回答) :

fun RecyclerView.smoothSnapToPosition(position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START) {
val smoothScroller = object : LinearSmoothScroller(this.context) {
override fun getVerticalSnapPreference(): Int = snapMode
override fun getHorizontalSnapPreference(): Int = snapMode
}
smoothScroller.targetPosition = position
layoutManager?.startSmoothScroll(smoothScroller)
}

像这样使用它:

myRecyclerView.smoothSnapToPosition(itemPosition)

我以前是这样的:

recyclerView.getLayoutManager().smoothScrollToPosition(recyclerView, new RecyclerView.State(), 5);

感谢@droidev 提供的解决方案,如果有人在寻找 Kotlin 的解决方案,请参考以下内容:

    class LinearLayoutManagerWithSmoothScroller: LinearLayoutManager {
constructor(context: Context) : this(context, VERTICAL,false)
constructor(context: Context, orientation: Int, reverseValue: Boolean) : super(context, orientation, reverseValue)


override fun smoothScrollToPosition(recyclerView: RecyclerView?, state: RecyclerView.State?, position: Int) {
super.smoothScrollToPosition(recyclerView, state, position)
val smoothScroller = TopSnappedSmoothScroller(recyclerView?.context)
smoothScroller.targetPosition = position
startSmoothScroll(smoothScroller)
}


private class TopSnappedSmoothScroller(context: Context?) : LinearSmoothScroller(context){
var mContext = context
override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
return LinearLayoutManagerWithSmoothScroller(mContext as Context)
.computeScrollVectorForPosition(targetPosition)
}


override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}




}


}

我想更全面地解决 滚动时间的问题,如果你选择任何更早的答案,事实上将会有很大的变化(不可接受)根据滚动的数量必要到达目标位置从目前的立场。

为了获得一个统一的滚动持续时间的速度(像素每毫秒)必须考虑到每个单独的项目的大小-当项目是非标准的维度,然后一个全新的复杂性水平添加。

这可能就是为什么 回收视图开发人员部署了 太用力了篮子来实现平滑滚动这一重要方面的原因。

假设您想要一个 半制服滚动持续时间,并且您的列表包含 半制服项目,那么您将需要这样的东西。

/** Smoothly scroll to specified position allowing for interval specification. <br>
* Note crude deceleration towards end of scroll
* @param rv        Your RecyclerView
* @param toPos     Position to scroll to
* @param duration  Approximate desired duration of scroll (ms)
* @throws IllegalArgumentException */
private static void smoothScroll(RecyclerView rv, int toPos, int duration) throws IllegalArgumentException {
int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;     // See androidx.recyclerview.widget.LinearSmoothScroller
int itemHeight = rv.getChildAt(0).getHeight();  // Height of first visible view! NB: ViewGroup method!
itemHeight = itemHeight + 33;                   // Example pixel Adjustment for decoration?
int fvPos = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
int i = Math.abs((fvPos - toPos) * itemHeight);
if (i == 0) { i = (int) Math.abs(rv.getChildAt(0).getY()); }
final int totalPix = i;                         // Best guess: Total number of pixels to scroll
RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(rv.getContext()) {
@Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}
@Override protected int calculateTimeForScrolling(int dx) {
int ms = (int) ( duration * dx / (float)totalPix );
// Now double the interval for the last fling.
if (dx < TARGET_SEEK_SCROLL_DISTANCE_PX ) { ms = ms*2; } // Crude deceleration!
//lg(format("For dx=%d we allot %dms", dx, ms));
return ms;
}
};
//lg(format("Total pixels from = %d to %d = %d [ itemHeight=%dpix ]", fvPos, toPos, totalPix, itemHeight));
smoothScroller.setTargetPosition(toPos);
rv.getLayoutManager().startSmoothScroll(smoothScroller);
}

PS: 我诅咒我开始 不分青红皂白转换 列表视图回收视图的那一天。

  1. 扩展“ LinearLayout”类并重写必要的函数
  2. 在片段或活动中创建上述类的实例
  3. 调用“ replerView.moothScrollToposition (targetPosition)”

CustomLinearLayout.kt:

class CustomLayoutManager(private val context: Context, layoutDirection: Int):
LinearLayoutManager(context, layoutDirection, false) {


companion object {
// This determines how smooth the scrolling will be
private
const val MILLISECONDS_PER_INCH = 300f
}


override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State, position: Int) {


val smoothScroller: LinearSmoothScroller = object: LinearSmoothScroller(context) {


fun dp2px(dpValue: Float): Int {
val scale = context.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}


// change this and the return super type to "calculateDyToMakeVisible" if the layout direction is set to VERTICAL
override fun calculateDxToMakeVisible(view: View ? , snapPreference : Int): Int {
return super.calculateDxToMakeVisible(view, SNAP_TO_END) - dp2px(50f)
}


//This controls the direction in which smoothScroll looks for your view
override fun computeScrollVectorForPosition(targetPosition: Int): PointF ? {
return this @CustomLayoutManager.computeScrollVectorForPosition(targetPosition)
}


//This returns the milliseconds it takes to scroll one pixel.
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi
}
}
smoothScroller.targetPosition = position
startSmoothScroll(smoothScroller)
}
}

注意: 上面的示例被设置为 HORIZONTAL 方向,您可以在初始化期间传递 VERTENT/HORIZONTAL。

如果将方向设置为 垂直,则应将“ 可见”更改为“ CalculateDyToMakeVisible”(同时要注意超类型调用返回值)

活动/片段.kt :

...
smoothScrollerLayoutManager = CustomLayoutManager(context, LinearLayoutManager.HORIZONTAL)
recyclerView.layoutManager = smoothScrollerLayoutManager
.
.
.
fun onClick() {
// targetPosition passed from the adapter to activity/fragment
recyclerView.smoothScrollToPosition(targetPosition)
}

您可以通过 list.reverse()反转您的列表,并最终调用 RecylerView.scrollToPosition(0)

    list.reverse()
layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true)
RecylerView.scrollToPosition(0)

我已经创建了一个扩展方法,该方法基于与回收者视图绑定的列表中项目的位置

在大列表中平滑滚动需要较长的时间来滚动,利用这一点来提高滚动速度,同时也具有平滑滚动动画。干杯!

fun RecyclerView?.perfectScroll(size: Int,up:Boolean = true ,smooth: Boolean = true) {
this?.apply {
if (size > 0) {
if (smooth) {
val minDirectScroll = 10 // left item to scroll
//smooth scroll
if (size > minDirectScroll) {
//scroll directly to certain position
val newSize = if (up) minDirectScroll else size - minDirectScroll
//scroll to new position
val newPos = newSize  - 1
//direct scroll
scrollToPosition(newPos)
//smooth scroll to rest
perfectScroll(minDirectScroll, true)


} else {
//direct smooth scroll
smoothScrollToPosition(if (up) 0 else size-1)
}
} else {
//direct scroll
scrollToPosition(if (up) 0 else size-1)
}
}
} }

在任何地方使用

rvList.perfectScroll(list.size,up=true,smooth=true)

在这里,您可以更改到想要滚动的位置,并在 get * * SnapPreferences 中更改 SNAP _ TO _ * 返回值。

持续时间将始终用于滚动到最近的项目,以及最远的项目在您的列表中。

用于在滚动快要完成时执行某些操作。

fun RecyclerView.smoothScroll(toPos: Int, duration: Int = 500, onFinish: () -> Unit = {}) {
try {
val smoothScroller: RecyclerView.SmoothScroller = object : LinearSmoothScroller(context) {
override fun getVerticalSnapPreference(): Int {
return SNAP_TO_END
}


override fun calculateTimeForScrolling(dx: Int): Int {
return duration
}


override fun onStop() {
super.onStop()
onFinish.invoke()
}
}
smoothScroller.targetPosition = toPos
layoutManager?.startSmoothScroll(smoothScroller)
} catch (e: Exception) {
Timber.e("FAILED TO SMOOTH SCROLL: ${e.message}")
}
}