web-dev-qa-db-ja.com

RecyclerViewに特定のアイテムの位置から開始するように指示する方法

アダプターが更新された後、LinearLayoutManagerを備えたRecyclerViewに、特定のアイテムのスクロール位置を表示します。 (最初/最後の位置ではありません)最初の(再)レイアウトを意味します。この特定の位置は可視領域にある必要があります。位置0を上にしてレイアウトし、後で目的の位置までスクロールしないでください。

私のアダプターはitemCount = 0で始まり、そのデータをスレッドにロードして、後で実際のカウントを通知します。ただし、カウントがまだ0の間に開始位置を設定しておく必要があります。

現在のところ、ある種の_post Runnable_ contains scrollToPositionを使用しましたが、これには副作用があり(最初のposから始まり、すぐにターゲット位置(0-> target)にジャンプします)、DiffUtil( 0->ターゲット-> 0))

編集:明確にするために:layoutManager.setStackFromEnd(true);のようなsetStackFrom(position)の代わりが必要です。 ScrollToPositionは、itemCountがまだ0のときに呼び出すと機能しないため、無視されます。 itemCountが> 0になったことを通知したときにそれを呼び出すと、0からレイアウトされ、ターゲット位置にジャンプします。そして、DiffUtil.DiffResult.dispatchUpdatesTo(adapter) `を使用すると、完全に失敗します。 (0から表示され、次にターゲット位置までスクロールしてから、再び位置0に戻ります)

6
allofmex

私は自分で解決策を見つけました:

LayoutManagerを拡張しました。

_  class MyLayoutManager extends LinearLayoutManager {

    private int mPendingTargetPos = -1;
    private int mPendingPosOffset = -1;

    @Override
    public void onLayoutChildren(Recycler recycler, State state) {
        if (mPendingTargetPos != -1 && state.getItemCount() > 0) {
            /*
            Data is present now, we can set the real scroll position
            */
            scrollToPositionWithOffset(mPendingTargetPos, mPendingPosOffset);
            mPendingTargetPos = -1;
            mPendingPosOffset = -1;
        }
        super.onLayoutChildren(recycler, state);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        /*
        May be needed depending on your implementation.

        Ignore target start position if InstanceState is available (page existed before already, keep position that user scrolled to)
         */
        mPendingTargetPos = -1;
        mPendingPosOffset = -1;
        super.onRestoreInstanceState(state);
    }

    /**
     * Sets a start position that will be used <b>as soon as data is available</b>.
     * May be used if your Adapter starts with itemCount=0 (async data loading) but you need to
     * set the start position already at this time. As soon as itemCount > 0,
     * it will set the scrollPosition, so that given itemPosition is visible.
     * @param position
     * @param offset
     */
    public void setTargetStartPos(int position, int offset) {
        mPendingTargetPos = position;
        mPendingPosOffset = offset;
    }
}
_

目標位置を格納する場合があります。 onLayoutChildrenがRecyclerViewによって呼び出された場合、itemCountがすでに0より大きいかどうかを確認します。trueの場合、scrollToPositionWithOffset()を呼び出します。

そのため、どの位置を表示する必要があるかすぐにわかりますが、Adapterに位置が存在する前にLayoutManagerに通知されません。

5
allofmex

あなたはこれを試すことができます、それはあなたが望む位置にスクロールします:

rv.getLayoutManager().scrollToPosition(positionInTheAdapter).
1
Nigel Brown

上記の方法はどれも私にとってはうまくいきませんでした。子が追加/表示が変更されたときにトリガーされるViewTreeObserverを使用して、次のことを行いました。

recyclerView.apply {
   adapter = ...
   layoutManager = ...

   val itemCount = adapter?.itemCount ?: 0
   if(itemCount > 1) {
      viewTreeObserver.addOnGlobalLayoutListener(object: ViewTreeObserver.OnGlobalLayoutListener {
      override fun onGlobalLayout() {
         viewTreeObserver.removeOnGlobalLayoutListener(this)
         (layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
      }
   }
}

次に、#PositionToStartAtを特定の値に設定します。また、特定の数の子が配置されたら、RecyclerViewの初期位置設定が確実にトリガーされ、正しく設定されていることを確認できます。

if(recyclerView.childCount() > #PositionCheck) {
   viewTreeObserver.removeOnGlobalLayoutListener(this)
   (layoutManager as? LinearLayoutManager)?.scrollToPosition(#PositionToStartAt)
}
0
Daniel Kim

特定の位置にスクロールする必要があり、その位置がアダプターの位置である場合は、StaggeredGridLayoutManagerscrollToPositionを使用できます。

StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
       staggeredGridLayoutManager.scrollToPosition(10);
       recyclerView.setLayoutManager(staggeredGridLayoutManager);

私が質問を理解している場合、特定の位置までスクロールしたいのですが、その位置はアダプターの位置であり、RecyclerViewのアイテムの位置ではありません。

これは、LayoutManagerを通じてのみ達成できます。

rv.getLayoutManager().scrollToPosition(youPositionInTheAdapter);
0
Prince