RecyclerView - Làm cách nào để cuộn mượt mà lên đầu mục ở một vị trí nhất định?


125

Trên RecyclerView, tôi có thể đột ngột cuộn lên đầu mục đã chọn bằng cách sử dụng:

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

Tuy nhiên, điều này đột ngột di chuyển mục lên vị trí hàng đầu. Tôi muốn chuyển đến đầu một mục một cách suôn sẻ .

Tôi cũng đã thử:

recyclerView.smoothScrollToPosition(position);

nhưng nó không hoạt động tốt vì nó không di chuyển mục đến vị trí đã chọn lên trên cùng. Nó chỉ cuộn danh sách cho đến khi mục trên vị trí hiển thị.

Câu trả lời:


225

RecyclerViewđược thiết kế để có thể mở rộng, vì vậy không cần phải phân lớp LayoutManager(như droidev đã đề xuất ) chỉ để thực hiện thao tác cuộn.

Thay vào đó, chỉ cần tạo một SmoothScrollervới tùy chọn SNAP_TO_START:

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

Bây giờ bạn đặt vị trí mà bạn muốn cuộn đến:

smoothScroller.setTargetPosition(position);

và chuyển SmoothScroller đó cho LayoutManager:

layoutManager.startSmoothScroll(smoothScroller);

9
Cảm ơn vì giải pháp ngắn hơn này. Chỉ có hai điều nữa tôi phải xem xét trong quá trình triển khai của mình: Vì tôi có chế độ xem cuộn ngang, tôi đã phải đặt protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }. Hơn nữa, tôi phải triển khai phương thức trừu tượng public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }.
AustrianDude

2
RecyclerView có thể được thiết kế để có thể mở rộng, nhưng một thứ đơn giản như thế này khiến bạn cảm thấy lười biếng khi thiếu. Câu trả lời hay tho! Cảm ơn bạn.
Michael

8
Có cách nào để cho phép nó bắt đầu hoạt động, nhưng với phần bù X dp không?
Mark Buikema

5
là nó có thể làm chậm tốc độ của nó?
Alireza Noorali

3
Cũng tự hỏi nếu tốc độ mà tại đó các cuộn xảy ra có thể được sửa đổi (chậm hơn) @AlirezaNoorali
vikzilla

112

vì điều này, bạn phải tạo một LayoutManager tùy chỉnh

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;
        }
    }
}

sử dụng điều này cho RecyclerView của bạn và gọi SmoothScrollToPosition.

thí dụ :

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

điều này sẽ cuộn lên đầu mục RecyclerView ở vị trí được chỉ định.

hi vọng điêu nay co ich.


Tôi đã gặp sự cố khi triển khai LayoutManager được đề xuất. Câu trả lời này ở đây làm việc dễ dàng hơn nhiều cho tôi: stackoverflow.com/questions/28025425/...
Arberg

3
Chỉ có câu trả lời hữu ích sau hàng giờ đau đớn, điều này hoạt động như thiết kế!
wblaschko

1
@droidev Tôi đã sử dụng ví dụ của bạn với khả năng cuộn mượt mà và nói chung SNAP_TO_START là thứ tôi cần nhưng nó hoạt động với một số vấn đề đối với tôi. Đôi khi việc cuộn dừng lại 1,2,3 hoặc 4 vị trí trước đó mà tôi chuyển đến smoothScrollToPosition. Tại sao vấn đề này có thể xảy ra? Cảm ơn.
Natasha

bằng cách nào đó bạn có thể liên hệ với mã của bạn cho chúng tôi không? Tôi khá chắc đó là vấn đề mã của bạn.
droidev

1
Đây là câu trả lời đúng cho dù một trong những được chấp nhận
Alessio

26

Đây là một chức năng mở rộng tôi đã viết trong Kotlin để sử dụng với RecyclerView(dựa trên câu trả lời @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)
}

Sử dụng nó như thế này:

myRecyclerView.smoothSnapToPosition(itemPosition)

Những công việc này! Vấn đề với việc cuộn trơn tru là khi bạn có các danh sách lớn và sau đó cuộn qua xuống dưới cùng hoặc trên cùng sẽ mất một thời gian dài
portfoliobuilder

@vovahost làm cách nào để căn giữa vị trí mong muốn?
ysfcyln

@ysfcyln Kiểm tra câu trả lời stackoverflow.com/a/39654328/1502079 này
vovahost

12

Chúng ta có thể thử như thế này

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

5

Ghi đè hàm tính toán có thể tính toán được / tính toán được trong LinearSmoothScroller để triển khai vị trí Y / X bù đắp

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

Đây là những gì tôi đang tìm kiếm. Cám ơn chia sẻ này để thực hiện các vị trí offset của x / y :)
Shan Xeeshi

3

Cách dễ nhất mà tôi đã tìm thấy để cuộn a RecyclerViewlà như sau:

// 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);

2
Thao tác này sẽ không cuộn đến một vị trí nếu vị trí đó đã hiển thị. Nó cuộn với số lượng ít nhất cần thiết để hiển thị vị trí. Vì vậy, tại sao chúng ta cần một cái có bù được chỉ định.
TatiOverflow

3

tôi đã sử dụng như thế này:

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

2

Cảm ơn, @droidev về giải pháp. Nếu ai đang tìm kiếm giải pháp Kotlin, hãy tham khảo phần này:

    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
        }


    }

}

1
Cảm ơn @pankaj, đã tìm kiếm giải pháp trong Kotlin.
Akshay Kumar Cả hai

1
  1. Mở rộng lớp "LinearLayout" và ghi đè các chức năng cần thiết
  2. Tạo một phiên bản của lớp trên trong phân đoạn hoặc hoạt động của bạn
  3. Gọi "RecyclerView.smoothScrollToPosition (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)
    }
  }

Lưu ý: Ví dụ trên được đặt thành hướng NGANG, bạn có thể chuyển hướng VERTICAL / HORIZONTAL trong quá trình khởi tạo.

Nếu bạn đặt hướng để ĐỨNG bạn nên thay đổi " calculateDxToMakeVisible " thành " calculateDyToMakeVisible " (còn nhớ những giá trị siêu kiểu gọi cửa sổ mới)

Activity / Fragment.kt :

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

0

Có lẽ cách tiếp cận @droidev là phương pháp chính xác, nhưng tôi chỉ muốn xuất bản một cái gì đó khác một chút, về cơ bản thực hiện cùng một công việc và không yêu cầu mở rộng LayoutManager.

LƯU Ý ở đây - điều này sẽ hoạt động tốt nếu mục của bạn (mục mà bạn muốn cuộn trên đầu danh sách) hiển thị trên màn hình và bạn chỉ muốn tự động cuộn lên đầu. Sẽ rất hữu ích khi mục cuối cùng trong danh sách của bạn có một số hành động, điều này sẽ thêm các mục mới trong cùng một danh sách và bạn muốn tập trung người dùng vào các mục mới được thêm vào:

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);

Ý tưởng rất đơn giản: 1. Chúng ta cần lấy tọa độ cao nhất của phần tử tái chế; 2. Chúng ta cần lấy tọa độ trên cùng của mục xem mà chúng ta muốn cuộn lên trên cùng; 3. Khi kết thúc với phần bù được tính toán, chúng ta cần làm

recyclerView.scrollBy(0, offset);

200 chỉ là ví dụ về giá trị số nguyên được mã hóa cứng mà bạn có thể sử dụng nếu mục trình xem không tồn tại, vì điều đó cũng có thể xảy ra.


0

Tôi muốn giải quyết đầy đủ hơn vấn đề về thời lượng cuộn , nếu bạn chọn bất kỳ câu trả lời nào sớm hơn, trên thực tế sẽ thay đổi đáng kể (và không thể chấp nhận được) theo số lượng cuộn cần thiết để đạt được vị trí mục tiêu từ vị trí hiện tại.

Để có được thời lượng cuộn đồng nhất, tốc độ (pixel trên mili giây) phải tính đến kích thước của từng mục riêng lẻ - và khi các mục có kích thước không chuẩn thì một mức độ phức tạp hoàn toàn mới sẽ được thêm vào.

Đây có thể là lý do tại sao các nhà phát triển RecyclerView đã triển khai giỏ quá khó cho khía cạnh quan trọng này của việc cuộn mượt mà.

Giả sử rằng bạn muốn thời lượng cuộn bán đồng nhất và danh sách của bạn chứa các mục bán đồng nhất thì bạn sẽ cần một cái gì đó như thế này.

/** 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);
}

Tái bút: Tôi nguyền rủa ngày tôi bắt đầu chuyển đổi bừa bãi ListView thành RecyclerView .


-1

Bạn có thể đảo ngược danh sách của mình bằng cách list.reverse()gọi finalyRecylerView.scrollToPosition(0)

    list.reverse()
    layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true)  
    RecylerView.scrollToPosition(0)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.