RecyclerView-特定の位置でアイテムの上部にスムーズにスクロールする方法は?


125

RecyclerViewでは、次のコマンドを使用して、選択したアイテムの一番上に突然スクロールできます。

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

ただし、これにより、アイテムが突然上位移動します。スムーズにアイテムの先頭に移動したい。

私も試しました:

recyclerView.smoothScrollToPosition(position);

ただし、アイテムを一番上に選択した位置に移動しないため、うまく機能しません。位置にあるアイテムが表示されるまでリストをスクロールするだけです。

回答:


224

RecyclerViewは拡張可能に設計されているため、スクロールを実行するためだけにLayoutManagerdroidevが示唆するように)をサブクラス化する必要はありません。

代わりに、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
この短い解決策をありがとう。実装で考慮しなければならないことが2つだけありprotected int getHorizontalSnapPreference() { return LinearSmoothScroller.SNAP_TO_START; }ます。水平スクロールビューがあるため、設定する必要がありました。さらに、抽象メソッドを実装する必要がありましたpublic PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); }
AustrianDude

2
RecyclerViewは拡張できるように設計されているかもしれませんが、このような単純なものは、欠けているのが面倒だと感じているだけです。いい答えですね!ありがとうございました。
マイケル

8
スナップして開始する方法はありますが、オフセットはX dpですか?
マークブイケマ2018年

2
@Alessio正確には、これはRecyclerViewのデフォルトのsmoothScrollToPosition機能を壊します
droidev

4
その速度を遅くすることは可能ですか?
Alireza Noorali 2018

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ます。なぜこの問題が発生するのですか?ありがとう。
Natasha

どういうわけか私たちにあなたのコードに到達できますか?私はそれがあなたのコードの問題だと確信しています。
ドロイデフ2016年

1
正解ですが、これは正解です
アレッシオ2018

25

これは、(@ Paul Woitaschekの回答に基づいて)Kotlinで使用するために記述した拡張関数です。RecyclerView

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 Check this stackoverflow.com/a/39654328/1502079 answer
vovahost

12

こうやってみます

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

5

LinearSmoothScrollerのcalculateDyToMakeVisible / calculateDxToMakeVisible関数をオーバーライドして、オフセット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。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
        }


    }

}

1
@pankajに感謝し、Kotlinで解決策を探していました。
Akshay Kumar両方

1
  1. 「LinearLayout」クラスを拡張し、必要な関数をオーバーライドします
  2. フラグメントまたはアクティビティに上記のクラスのインスタンスを作成します
  3. 「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)
    }
  }

注:上記の例はHORIZONTAL方向に設定されています。初期化中にVERTICAL / HORIZONTALを渡すことができます。

方向をVERTICALに設定する場合は、「calculateDxToMakeVisible」を「calculateDyToMakeVisible」に変更する必要があります(スーパータイプコールの戻り値にも注意してください)。

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

おそらく@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.一番上までスクロールするビューアイテムの一番上の座標を取得する必要があります。3.最後に、計算したオフセットを使用して、実行する必要があります

recyclerView.scrollBy(0, offset);

200は、viewholderアイテムが存在しない場合に使用できるハードコードされた整数値の単なる例です。


0

スクロール継続時間の問題をより完全に解決したいと思います。以前の回答を選択した場合、実際の位置から目標位置に到達するために必要なスクロールの量によって、実際には劇的に(そして許容できないほど)変化します。

均一なスクロール期間を取得するには、速度(ミリ秒あたりのピクセル数)で個々のアイテムのサイズを考慮する必要があります。アイテムのサイズが標準以外の場合は、まったく新しいレベルの複雑さが追加されます。

これが、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:ListViewRecyclerViewに無差別に変換し始めた日を呪いました。


0

list.reverse()最後に電話でリストを逆にすることができますRecylerView.scrollToPosition(0)

    list.reverse()
    layout = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,true)  
    RecylerView.scrollToPosition(0)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.