RecyclerView: come spostare in modo uniforme la parte superiore dell'elemento in una determinata posizione?


125

Su un RecyclerView, sono in grado di scorrere improvvisamente fino all'inizio di un elemento selezionato utilizzando:

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

Tuttavia, questo sposta bruscamente l'elemento nella posizione superiore. Voglio spostarmi all'inizio di un elemento senza problemi .

Ho anche provato:

recyclerView.smoothScrollToPosition(position);

ma non funziona bene in quanto non sposta l'elemento nella posizione selezionata in alto. Si limita a scorrere l'elenco finché l'elemento nella posizione non è visibile.

Risposte:


225

RecyclerViewè progettato per essere estensibile, quindi non è necessario sottoclassare LayoutManager(come suggerito da droidev ) solo per eseguire lo scorrimento.

Invece, crea semplicemente un SmoothScrollercon la preferenza SNAP_TO_START:

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

Ora imposti la posizione in cui vuoi scorrere:

smoothScroller.setTargetPosition(position);

e passa quello SmoothScroller al LayoutManager:

layoutManager.startSmoothScroll(smoothScroller);

9
Grazie per questa soluzione più breve. Solo altre due cose che dovevo considerare nella mia implementazione: Dato che ho una visualizzazione a scorrimento orizzontale che ho dovuto impostare protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }. Inoltre ho dovuto implementare il metodo astratto public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }.
AustrianDude

2
RecyclerView può essere progettato per essere estensibile, ma una cosa semplice come questa sembra semplicemente pigra a mancare. Buona risposta comunque! Grazie.
Michael

8
C'è un modo per lasciarlo scattare per iniziare, ma con un offset di X dp?
Mark Buikema

5
è possibile rallentarne la velocità?
Alireza Noorali

3
Mi chiedo anche se la velocità con cui si verifica lo scorrimento possa essere modificata (più lenta) @AlirezaNoorali
vikzilla

112

per questo devi creare un LayoutManager personalizzato

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

usalo per RecyclerView e chiama smoothScrollToPosition.

esempio :

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

questo scorrerà all'inizio dell'elemento RecyclerView della posizione specificata.

spero che questo ti aiuti.


Ho avuto problemi a implementare il LayoutManager suggerito. Questa risposta qui ha lavorato molto più facile per me: stackoverflow.com/questions/28025425/...
Arberg

3
Unica risposta utile dopo ore di dolore, funziona come previsto!
wblaschko

1
@droidev Ho usato il tuo esempio con uno scorrimento fluido e generalmente SNAP_TO_START è ciò di cui ho bisogno, ma funziona con alcuni problemi per me. A volte lo scorrimento si ferma 1, 2, 3 o 4 posizioni prima che io passi smoothScrollToPosition. Perché può verificarsi questo problema? Grazie.
Natasha

puoi in qualche modo raggiungerci il tuo codice? Sono abbastanza sicuro che sia il tuo problema con il codice.
droidev

1
Questa è la risposta corretta nonostante quella accettata
Alessio

26

Questa è una funzione di estensione che ho scritto in Kotlin da utilizzare con la RecyclerView(basata sulla risposta di @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)
}

Usalo in questo modo:

myRecyclerView.smoothSnapToPosition(itemPosition)

Funziona! Il problema con lo scorrimento fluido è quando si hanno elenchi di grandi dimensioni e quindi scorrere verso il basso o verso l'alto richiede molto, molto tempo
portfoliobuilder

@vovahost come posso centrare la posizione desiderata?
ysfcyln

@ysfcyln Controllare questo stackoverflow.com/a/39654328/1502079 risposta
vovahost

12

Possiamo provare in questo modo

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

5

Sostituisci la funzione CalcolaDyToMakeVisible / CalcolaDxToMakeVisible in LinearSmoothScroller per implementare la posizione Y / X di offset

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

Questo è quello che stavo cercando. Grazie per aver condiviso questo per l'implementazione della posizione offset di x / y :)
Shan Xeeshi

3

Il modo più semplice che ho trovato per scorrere a RecyclerViewè il seguente:

// 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
Questo non scorrerà fino a una posizione se quella posizione è già visibile. Scorre il minimo necessario per rendere visibile la posizione. Quindi perché abbiamo bisogno di quello con offset specificato.
TatiOverflow

3

ho usato in questo modo:

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

2

Grazie, @droidev per la soluzione. Se qualcuno cerca la soluzione Kotlin, fa riferimento a questo:

    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
Grazie @pankaj, stavo cercando una soluzione a Kotlin.
Akshay Kumar Both

1
  1. Estende la classe "LinearLayout" e sovrascrive le funzioni necessarie
  2. Crea un'istanza della classe precedente nel tuo frammento o attività
  3. Chiama "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)
    }
  }

Nota: L'esempio sopra è impostato sulla direzione ORIZZONTALE, è possibile passare VERTICALE / ORIZZONTALE durante l'inizializzazione.

Se imposti la direzione su VERTICALE , dovresti cambiare " calcolaDxToMakeVisible " in " CalcolaDyToMakeVisible " ( fai attenzione anche al valore di ritorno della chiamata del supertipo)

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

Probabilmente l'approccio @droidev è quello corretto, ma voglio solo pubblicare qualcosa di leggermente diverso, che fondamentalmente fa lo stesso lavoro e non richiede l'estensione del LayoutManager.

Una NOTA qui: funzionerà bene se il tuo elemento (quello che vuoi scorrere in cima all'elenco) è visibile sullo schermo e vuoi solo scorrerlo automaticamente verso l'alto. È utile quando l'ultimo elemento nell'elenco ha un'azione, che aggiunge nuovi elementi nello stesso elenco e si desidera focalizzare l'utente sui nuovi elementi aggiunti:

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

L'idea è semplice: 1. Dobbiamo ottenere la coordinata superiore dell'elemento recyclerview; 2. Dobbiamo ottenere la coordinata superiore dell'elemento di visualizzazione che vogliamo scorrere verso l'alto; 3. Alla fine con l'offset calcolato dobbiamo fare

recyclerView.scrollBy(0, offset);

200 è solo un esempio di valore intero hardcoded che puoi utilizzare se l'elemento viewholder non esiste, perché anche questo è possibile.


0

Voglio affrontare in modo più completo il problema della durata dello scorrimento , che, se dovessi scegliere una risposta precedente, in effetti varierà notevolmente (e in modo inaccettabile) in base alla quantità di scorrimento necessaria per raggiungere la posizione di destinazione dalla posizione corrente.

Per ottenere una durata di scorrimento uniforme, la velocità (pixel per millisecondo) deve tenere conto della dimensione di ogni singolo elemento - e quando gli elementi sono di dimensioni non standard, viene aggiunto un nuovo livello di complessità.

Questo potrebbe essere il motivo per cui gli sviluppatori di RecyclerView hanno implementato il cestino troppo rigido per questo aspetto vitale dello scorrimento fluido.

Supponendo che tu voglia una durata di scorrimento semi-uniforme e che il tuo elenco contenga elementi semi-uniformi , avrai bisogno di qualcosa di simile.

/** 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: maledico il giorno in cui ho iniziato a convertire indiscriminatamente ListView in RecyclerView .


-1

Puoi invertire la tua lista list.reverse()e infine chiamareRecylerView.scrollToPosition(0)

    list.reverse()
    layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true)  
    RecylerView.scrollToPosition(0)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.