ChatGPT解决这个技术问题 Extra ChatGPT

RecyclerView - 如何平滑滚动到某个位置的项目顶部?

在 RecyclerView 上,我可以使用以下命令突然滚动到所选项目的顶部:

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

但是,这会突然将项目移动到顶部位置。我想顺利移动到项目的顶部。

我也试过:

recyclerView.smoothScrollToPosition(position);

但它不能很好地工作,因为它不会将项目移动到顶部选择的位置。它只是滚动列表,直到该位置上的项目可见。


S
Shishram

RecyclerView 设计为可扩展的,因此无需为了执行滚动而将 LayoutManager(如 droidev suggested)子类化。

相反,只需使用首选项 SNAP_TO_START 创建一个 SmoothScroller

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

现在您设置要滚动到的位置:

smoothScroller.setTargetPosition(position);

并将 SmoothScroller 传递给 LayoutManager:

layoutManager.startSmoothScroll(smoothScroller);

感谢这个更短的解决方案。在我的实现中我还需要考虑两件事:因为我有一个水平滚动视图,所以我必须设置 protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }。此外,我必须实现抽象方法 public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }
RecyclerView 可能被设计成可扩展的,但是像这样一个简单的东西只是懒得错过。很好的答案!谢谢你。
有什么方法可以让它快速启动,但偏移量为 X dp?
可以减慢它的速度吗?
还想知道是否可以修改滚动发生的速度(较慢)@AlirezaNoorali
d
droidev

为此,您必须创建一个自定义 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;
        }
    }
}

将此用于您的 RecyclerView 并调用 smoothScrollToPosition。

例子 :

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

这将滚动到指定位置的 RecyclerView 项目的顶部。

希望这可以帮助。


我在实施建议的 LayoutManager 时遇到了麻烦。这个答案对我来说更容易:stackoverflow.com/questions/28025425/…
经过数小时的痛苦后,只有有用的答案,这按设计工作!
@droidev我已经使用了平滑滚动的示例,通常 SNAP_TO_START 是我需要的,但它对我来说有一些问题。有时滚动会在我传递到 smoothScrollToPosition 之前 1、2、3 或 4 个位置停止。为什么会出现这个问题?谢谢。
您能以某种方式将您的代码发送给我们吗?我很确定这是您的代码问题。
尽管已接受,但这是正确的答案
v
vovahost

这是我在 Kotlin 中编写的与 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)

这行得通!平滑滚动的问题是当您有大列表然后滚动到底部或顶部需要很长时间
@vovahost 我怎样才能使所需位置居中?
@ysfcyln 检查这个 stackoverflow.com/a/39654328/1502079 答案
谢谢@vovahost,你救了我免于自杀。哈哈哈哈哈
S
Supto

我们可以这样尝试

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

u
user2526517

覆盖LinearSmoothScroller中的calculateDyToMakeVisible/calculateDxToMakeVisible函数实现偏移Y/X位置

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

这就是我一直在寻找的。感谢分享这个以实现 x/y 的偏移位置 :)
C
Criss

我这样使用:

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

最简单的一个
B
Bad Loser

我想更全面地解决滚动持续时间的问题,如果您选择任何较早的答案,实际上会根据从当前位置到达目标位置所需的滚动量而发生显着变化(并且不可接受)。

为了获得统一的滚动持续时间,速度(每毫秒像素数)必须考虑每个单独项目的大小 - 当项目具有非标准尺寸时,就会增加一个全新的复杂程度。

这可能是 RecyclerView 开发人员为平滑滚动这一重要方面部署太硬的篮子的原因。

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

/** 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:我诅咒我开始不分青红皂白地将 ListView 转换为 RecyclerView 的那一天。


有时 ms 返回 5000 ms,这会导致视图滚动非常慢,我建议使用 RecyclerView.SmoothScroller,正如我在回答中所暗示的那样。 stackoverflow.com/a/72668624/6039240
M
Mapsy

我发现滚动 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);

如果该位置已经可见,则不会滚动到该位置。它滚动最少需要使位置可见的量。因此,为什么我们需要具有指定偏移量的那个。
P
PANKAJ KUMAR MAKWANA

谢谢,@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
        }


    }

}

谢谢@pankaj,正在寻找 Kotlin 中的解决方案。
A
Abhijeet Prusty

我已经根据与回收站视图绑定的列表中项目的位置创建了一个扩展方法

大列表中的平滑滚动需要更长的滚动时间,使用它可以提高滚动速度并具有平滑的滚动动画。干杯!!

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)

H
HumbleBee

扩展“LinearLayout”类并覆盖必要的功能在您的片段或活动中创建上述类的实例调用“recyclerView.smoothScrollToPosition(targetPosition)

自定义线性布局.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方向,初始化时可以传入VERTICAL/HORIZONTAL。

如果将方向设置为 VERTICAL,则应将“calculateDxToMakeVisible”更改为“calculateDyToMakeVisible”(还要注意超类型调用返回值)

活动/片段.kt:

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

S
Stoycho Andreev

可能@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、我们需要得到recyclerview元素的顶坐标; 2.我们需要获取我们想要滚动到顶部的view item的顶部坐标; 3. 最后计算出我们需要做的偏移量

recyclerView.scrollBy(0, offset);

200 只是示例硬编码整数值,如果查看器项目不存在,您可以使用它,因为这也是可能的。


A
Amr

在这里您可以更改到您想要滚动的位置,更改 get**SnapPreference 中的 SNAP_TO_* 返回值。

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

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

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}")
  }
}

M
Mario

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

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