RecyclerView - Bagaimana cara menggulir halus ke atas item pada posisi tertentu?

125

Di RecyclerView, saya bisa tiba-tiba menggulir ke atas item yang dipilih dengan menggunakan:

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

Namun, ini tiba-tiba memindahkan item ke posisi teratas. Saya ingin pindah ke atas item dengan lancar .

Saya juga mencoba:

recyclerView.smoothScrollToPosition(position);

tetapi tidak berfungsi dengan baik karena tidak memindahkan item ke posisi yang dipilih ke atas. Ini hanya menggulung daftar sampai item pada posisi terlihat.

Tiago
sumber

Jawaban:

225

RecyclerViewdirancang agar dapat diperluas, jadi tidak perlu membuat subkelas LayoutManager(seperti yang disarankan droidev ) hanya untuk melakukan scrolling.

Sebagai gantinya, buat saja SmoothScrollerdengan preferensi SNAP_TO_START:

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

Sekarang Anda mengatur posisi di mana Anda ingin menggulir ke:

smoothScroller.setTargetPosition(position);

dan teruskan SmoothScroller itu ke LayoutManager:

layoutManager.startSmoothScroll(smoothScroller);
Paul Woitaschek
sumber
9
Terima kasih untuk solusi yang lebih singkat ini. Hanya dua hal lagi yang harus saya pertimbangkan dalam implementasi saya: Karena saya memiliki tampilan scrolling horizontal yang harus saya atur protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }. Selanjutnya saya harus menerapkan metode abstrak public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }.
AustrianDude
2
RecyclerView mungkin didesain agar dapat diperluas, tetapi hal sederhana seperti ini terasa malas untuk dilewatkan. Jawaban bagus tho! Terima kasih.
Michael
8
Apakah ada cara untuk membiarkannya memulai, tetapi dengan offset X dp?
Mark Buikema
5
apakah mungkin untuk memperlambat kecepatannya?
Alireza Noorali
3
Juga bertanya-tanya apakah kecepatan terjadinya scroll dapat diubah (lebih lambat) @AlirezaNoorali
vikzilla
112

untuk ini, Anda harus membuat LayoutManager kustom

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

gunakan ini untuk RecyclerView Anda dan panggil smoothScrollToPosition.

contoh:

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

ini akan menggulir ke atas item RecyclerView dari posisi yang ditentukan.

semoga ini membantu.

droidev
sumber
Saya mengalami masalah saat menerapkan LayoutManager yang disarankan. Jawaban ini di sini bekerja lebih mudah bagi saya: stackoverflow.com/questions/28025425/…
arberg
3
Hanya jawaban yang berguna setelah berjam-jam sakit, ini berfungsi seperti yang dirancang!
wblaschko
1
@droidev Saya telah menggunakan contoh Anda dengan pengguliran yang mulus dan umumnya SNAP_TO_START adalah yang saya butuhkan tetapi berfungsi dengan beberapa masalah bagi saya. Terkadang scrolling menghentikan 1,2,3 atau 4 posisi sebelumnya yang saya lewati smoothScrollToPosition. Mengapa masalah ini bisa terjadi? Terima kasih.
Natasha
dapatkah Anda memberikan kode Anda kepada kami? Saya cukup yakin ini adalah masalah kode Anda.
droidev
1
Ini adalah jawaban yang benar meskipun ada yang diterima
Alessio
26

Ini adalah fungsi ekstensi yang saya tulis di Kotlin untuk digunakan dengan RecyclerView(berdasarkan jawaban @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)
}

Gunakan seperti ini:

myRecyclerView.smoothSnapToPosition(itemPosition)
vovahost
sumber
Ini bekerja! Masalah dengan pengguliran halus adalah ketika Anda memiliki daftar besar dan kemudian menggulir ke bawah atau atas membutuhkan waktu yang sangat lama
portofolio
@vovahost bagaimana cara memusatkan posisi yang diinginkan?
ysfcyln
@ysfcyln Periksa stackoverflow.com/a/39654328/1502079 ini jawaban
vovahost
12

Kita bisa coba seperti ini

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

Ganti fungsi countDyToMakeVisible / countDxToMakeVisible di LinearSmoothScroller untuk mengimplementasikan posisi offset Y / X

override fun calculateDyToMakeVisible(view: View, snapPreference: Int): Int {
    return super.calculateDyToMakeVisible(view, snapPreference) - ConvertUtils.dp2px(10f)
}
pengguna2526517
sumber
Inilah yang saya cari. Terima kasih telah membagikan ini untuk implementasi posisi offset x / y :)
Shan Xeeshi
3

Cara termudah yang saya temukan untuk menggulir a RecyclerViewadalah sebagai berikut:

// 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);
Mapsy
sumber
2
Ini tidak akan menggulir ke posisi jika posisi itu sudah terlihat. Ini menggulung paling sedikit yang dibutuhkan untuk membuat posisi terlihat. Jadi mengapa kita membutuhkan yang ditentukan offset.
TatiOverflow
3

Saya Digunakan Seperti Ini:

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

Terima kasih, @droidev untuk solusinya. Jika ada yang mencari solusi Kotlin, lihat ini:

    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 KUMAR MAKWANA
sumber
1
Terima kasih @pankaj, sedang mencari solusi di Kotlin.
Akshay Kumar Keduanya
1
  1. Perluas kelas "LinearLayout" dan timpa fungsi yang diperlukan
  2. Buat instance dari kelas di atas dalam fragmen atau aktivitas Anda
  3. Panggil "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)
    }
  }

Catatan: Contoh di atas diatur ke arah HORIZONTAL, Anda dapat melewati VERTICAL / HORIZONTAL selama inisialisasi.

Jika Anda menyetel arah ke VERTICAL, Anda harus mengubah " countDxToMakeVisible " menjadi " countDyToMakeVisible " (perhatikan juga nilai pengembalian panggilan supertipe)

Aktivitas / Fragment.kt :

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

Mungkin pendekatan @droidev adalah yang benar, tetapi saya hanya ingin mempublikasikan sesuatu yang sedikit berbeda, yang pada dasarnya melakukan pekerjaan yang sama dan tidak memerlukan ekstensi dari LayoutManager.

CATATAN di sini - ini akan berfungsi dengan baik jika item Anda (yang ingin Anda gulir di bagian atas daftar) terlihat di layar dan Anda hanya ingin menggulirnya ke atas secara otomatis. Ini berguna ketika item terakhir dalam daftar Anda memiliki beberapa tindakan, yang menambahkan item baru dalam daftar yang sama dan Anda ingin memfokuskan pengguna pada item baru yang ditambahkan:

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

Idenya sederhana: 1. Kita perlu mendapatkan koordinat teratas dari elemen recyclerview; 2. Kita perlu mendapatkan koordinat teratas dari item tampilan yang ingin kita gulir ke atas; 3. Di akhir dengan perhitungan offset yang perlu kita lakukan

recyclerView.scrollBy(0, offset);

200 hanyalah contoh nilai integer kode keras yang dapat Anda gunakan jika item viewholder tidak ada, karena itu juga mungkin.

Stoycho Andreev
sumber
0

Saya ingin membahas lebih lengkap masalah durasi gulir , yang, jika Anda memilih jawaban sebelumnya, sebenarnya akan bervariasi secara dramatis (dan tidak dapat diterima) sesuai dengan jumlah pengguliran yang diperlukan untuk mencapai posisi target dari posisi saat ini.

Untuk mendapatkan durasi gulir yang seragam, kecepatan (piksel per milidetik) harus memperhitungkan ukuran masing-masing item - dan bila item tersebut berdimensi non-standar maka tingkat kompleksitas yang sama sekali baru ditambahkan.

Ini mungkin alasan mengapa pengembang RecyclerView menggunakan keranjang yang terlalu keras untuk aspek vital pengguliran yang mulus ini.

Dengan asumsi Anda menginginkan durasi scroll semi-seragam , dan daftar Anda berisi item semi-seragam maka Anda memerlukan sesuatu seperti ini.

/** 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: Saya mengutuk pada hari saya mulai tanpa pandang bulu mengubah ListView ke RecyclerView .

Pecundang buruk
sumber
-1

Anda dapat membalikkan daftar Anda dengan list.reverse()dan akhirnya meneleponRecylerView.scrollToPosition(0)

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