RecyclerView - Як згладити прокрутку до верхньої частини елемента на певній позиції?


125

У RecyclerView я можу раптово прокрутити до початку обраного елемента, використовуючи:

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

Однак це різко переміщує елемент у верхнє положення. Я хочу плавно перейти до верхньої частини елемента .

Я також спробував:

recyclerView.smoothScrollToPosition(position);

але це працює не так добре, оскільки він не переміщує елемент у положення, вибране вгору. Він просто прокручує список, поки не з’явиться пункт на позиції.

Відповіді:


225

RecyclerViewпризначений для розширення, тому немає необхідності підкласифікувати LayoutManager(як запропонував droidev ) просто для того, щоб виконати прокрутку.

Замість цього просто створіть SmoothScrollerпараметр SNAP_TO_START:

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

Тепер ви встановлюєте позицію, куди ви хочете прокрутити:

smoothScroller.setTargetPosition(position);

і передати цей SmoothScroller в LayoutManager:

layoutManager.startSmoothScroll(smoothScroller);

9
Дякую за це коротше рішення. Ще дві речі, які мені довелося врахувати у своїй реалізації: Оскільки у мене є горизонтальний вид прокрутки, який я повинен був встановити protected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }. Крім того, мені довелося реалізувати абстрактний метод public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }.
AustrianDude

2
RecyclerView може бути розроблений як розширюваний, але така проста річ, як ця, просто лінується, що його не вистачає. Гарна відповідь Тхо! Дякую.
Майкл

8
Чи є якийсь спосіб дозволити йому запуститися, але зі зміщенням X dp?
Марк Буйкема

5
чи можна сповільнити його швидкість?
Аліреза Нооралі

3
Також цікаво, чи можна змінити швидкість, з якою відбувається прокручування (повільніше) @AlirezaNoorali
vikzilla

112

для цього вам потрібно створити власний 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 / ...
Аарберг

3
Тільки корисна відповідь після больових годин, це працює як задумано!
wblaschko

1
@droidev Я використовував ваш приклад з плавним прокручуванням і, як правило, SNAP_TO_START - це те, що мені потрібно, але це працює з деякими проблемами для мене. Іноді прокрутка зупиняє 1,2,3 або 4 позиції раніше, в які я переходжу smoothScrollToPosition. Чому ця проблема може виникнути? Дякую.
Наташа

Ви можете якось дістатися до свого коду до нас? Я майже впевнений, що це ваша проблема з кодом.
дройдев

1
Це правильна відповідь, незважаючи на прийняту
Алессіо

26

Це функція розширення, яку я написав у Котліні для використання з 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

@ysfcyln Перевірте stackoverflow.com/a/39654328/1502079 відповідь
vovahost

12

Ми можемо спробувати так

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

5

Перевизначте функцію CalcuDyToMakeVisible / izračunaDxToMakeVisible в LinearSmoothScroller, щоб реалізувати зміщення положення Y / X

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

Це те, що я шукав. Дякуємо, що поділилися цим для впровадження зміщення позиції x / y :)
Shan Xeeshi

3

Найпростіший спосіб, який я знайшов для прокрутки, 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);

2
Це не прокрутить до позиції, якщо ця позиція вже видно. Він прокручує найменшу кількість, необхідну для того, щоб позиція була видимою. Таким чином, для чого нам потрібен той із заданим зміщенням.
TatiOverflow

3

я використовував так:

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

2

Дякую, @droidev за рішення. Якщо хтось шукає рішення Котліна, зверніться до цього:

    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
Дякую @pankaj, шукав рішення в Котліні.
Акшай Кумар обидва

1
  1. Розширити клас "LinearLayout" та перекрити необхідні функції
  2. Створіть у своєму фрагменті чи діяльності екземпляр вищевказаного класу
  3. Виклик "recilerView.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)
    }
  }

Примітка. Наведений вище приклад встановлений у напрямку HORIZONTAL, ви можете пройти VERTICAL / HORIZONTAL під час ініціалізації.

Якщо ви встановите напрямок на VERTICAL, вам слід змінити " CalcuDxToMakeVisible " на " CalcuDyToMakeVisible " (також врахуйте значення повернення виклику супертипу)

Діяльність / Fragment.kt :

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

0

Ймовірно, підхід @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 - лише приклад цілочислового цілого числа, яке ви можете використовувати, якщо елемент перегляду не існує, тому що це також можливо.


0

Я хочу більш повно розглянути питання про тривалість прокрутки , яке, якщо ви виберете будь-яку попередню відповідь, насправді буде різко (і неприйнятно) залежно від кількості прокрутки, необхідної для досягнення цільової позиції з поточної позиції.

Для отримання рівномірної тривалості прокрутки швидкість (пікселі на мілісекунд) повинна враховувати розмір кожного окремого елемента - а коли елементи мають нестандартний розмір, тоді додається цілий новий рівень складності.

Ось чому розробники RecyclerView розгорнули занадто жорсткий кошик для цього життєво важливого аспекту плавного прокручування.

Припускаючи , що ви хочете статі-Uniform скролінгу тривалості, і що список містить підлогу неоднорідних елементів , то вам потрібно що - щось на зразок цього.

/** 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 .


-1

Ви можете змінити свій список до list.reverse()і, нарешті, зателефонуватиRecylerView.scrollToPosition(0)

    list.reverse()
    layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true)  
    RecylerView.scrollToPosition(0)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.