web-dev-qa-db-ja.com

RecyclerView-毎回機能しない位置までスクロール

水平スクロール可能なRecyclerViewを実装しました。私のRecyclerViewLinearLayoutManagerを使用しており、私が直面している問題は、scrollToPosition(position)またはsmoothScrollToPosition(position)を使用しようとしたとき、またはLinearLayoutManagerscrollToPositionWithOffset(position)。どちらも機能しません。スクロール呼び出しが目的の場所にスクロールしないか、OnScrollListenerを呼び出しません。

これまでのところ、コードのさまざまな組み合わせを試してみたので、ここにすべてを掲載することはできません。以下は私のために働くものです(しかし、部分的にのみ):

_public void smoothUserScrollTo(final int position) {

    if (position < 0 || position > getAdapter().getItemCount()) {
        Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
        return;
    }

    if (getLayoutManager() == null) {
        Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
                "Call setLayoutManager with a non-null layout.");
        return;
    }

    if (getChildAdapterPosition(getCenterView()) == position) {
        return;
    }

    stopScroll();

    scrollToPosition(position);

    if (lastScrollPosition == position) {

        addOnLayoutChangeListener(new OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {

                if (left == oldLeft && right == oldRight && top == oldTop && bottom == oldBottom) {
                    removeOnLayoutChangeListener(this);

                    updateViews();

                    // removing the following line causes a position - 3 effect.
                    scrollToView(getChildAt(0));
                }
            }
        });
    }

    lastScrollPosition = position;
}

@Override
public void scrollToPosition(int position) {
    if (position < 0 || position > getAdapter().getItemCount()) {
        Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
        return;
    }

    if (getLayoutManager() == null) {
        Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
                "Call setLayoutManager with a non-null layout.");
        return;
    }

//      stopScroll();

        ((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, 0);
//        getLayoutManager().scrollToPosition(position);
    }
_

this のためscrollToPositionWithOffset()を選択しましたが、GridLayoutManagerの代わりにLinearLayoutManagerを使用するため、ケースが異なる可能性があります。しかし、このソリューションは私にとっても機能しますが、先ほど言ったように部分的にしかありません。

  • Scrollの呼び出しが0番目の位置からtotalSizeまでの場合、7スクロールは魅力のように機能します。
  • スクロールがtotalSize-7からtotalSize-3の場合、最初はリストの最後の7番目のアイテムまでスクロールします。 2回目はしかし、私はうまくスクロールすることができます
  • TotalSize-3からtotalSizeにスクロールすると、予期しない動作が発生し始めます。

誰かが回避策を見つけたら、感謝します。カスタムReyclerViewのコードには Gist があります。

23
Abbas

私は数週間前に同じ問題を抱えていましたが、それを解決するための本当に悪い解決策しか見つかりませんでした。 200〜300ミリ秒のpostDelayedを使用する必要がありました。

new Handler().postDelayed(new Runnable() {
    @Override
    public void run() {
        yourList.scrollToPosition(position);
    }
}, 200);

より良い解決策を見つけた場合は、お知らせください!がんばろう!

39
Robert Banyai

私が利用するまで私は同様の問題を抱えていたことが判明

myRecyclerview.scrollToPosition(objectlist.size()-1)

Objectlistサイズのみを入れる場合、常に最上位に留まります。これは、変数と同じサイズを設定することにしました。繰り返しますが、それはうまくいきませんでした。それから私はおそらく私に言わずに境界外の性別を処理していると思いました。だから私は1を引いた。それからそれは働いた。

15

どの方法も私にとってはうまく機能していないようです。以下の1行のコードのみが機能しました

((LinearLayoutManager)mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(adapter.currentPosition(),200);

2番目のパラメーターはオフセットを参照します。これは、実際にはアイテムビューの開始エッジとRecyclerViewの開始エッジの間の距離(ピクセル単位)です。上位の項目も表示されるように、定数値を指定しました。

こちら の詳細を参照してください

4

まあ、私は回避策を見つけました。

scrollToPositionWithOffset(position, offset)が最高のコールだったので、使用しています。さまざまなデータセットで出力をテストしましたが、これまでのところ矛盾は見つかりませんでした。うまく行かないことを願っています(でも、もし誰かが見つけたらコメントしてください)。

しかし、その後_last 7 Items_の問題があり、スクローラーはどういうわけかスクロールできません。それらの不幸なアイテムには、smoothScrollBy(dx, dy)を使用しています。ただし、scrollto-itemの位置がわかっている場合にのみ使用できるため、少し注意が必要です。しかし、ここに解決策があります(上記の Gist では、次の関数定義のみを変更しました)。

_public void smoothUserScrollTo(final int position) {

    if (position < 0 || position > getAdapter().getItemCount()) {
        Log.e(TAG, "An attempt to scroll out of adapter size has been stopped.");
        return;
    }

    if (getLayoutManager() == null) {
        Log.e(TAG, "Cannot scroll to position a LayoutManager is not set. " +
                "Call setLayoutManager with a non-null layout.");
        return;
    }

    if (getChildAdapterPosition(getCenterView()) == position) {
        scrollToView(getCenterView());
        return;
    }

    stopScroll();

    scrollToPosition(position);

    if (lastScrollPosition == position || position >= getAdapter().getItemCount() - 7) {

        addOnLayoutChangeListener(new OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {

                if (left == oldLeft && right == oldRight && top == oldTop && bottom == oldBottom) {

                    if (getChildAdapterPosition(getCenterView()) == position) {
                        //  Only remove the listener if/when the centered items is the item we want to scroll to.
                        removeOnLayoutChangeListener(this);
                        scrollToView(getCenterView());
                        return;
                    }

                    if (position >= 0 && position < getAdapter().getItemCount() - 7) {

                        removeOnLayoutChangeListener(this);
                        updateViews();
                        scrollToView(getChildAt(0));
                    }
                    else if (position >= getAdapter().getItemCount() - 7 && position <= getAdapter().getItemCount()){

                        //  Search in the attached items of the RecyclerView for our required item.
                        //  You can remove the loop and optimize your code further if you want to,
                        //  but I am gonna leave it here as is. It is simple enough :-).
                        int childPosition = 0;
                        for (int i = 0; i < getChildCount(); i++) {
                            childPosition = getChildAdapterPosition(getChildAt(i));
                            if (childPosition == position) {
                                updateViews();
                                scrollToView(getChildAt(i));
                                break;
                            }

                            //  Since we couldn't find the item in attached items.
                            //  Scroll to the last attached item (It'll dettach and attach
                            //  necessary items). So we can scroll once the view is stable again.
                            if (i == getChildCount() - 2) {
                                scrollToView(getChildAt(i));
                            }
                        }
                    }
                }
            }
        });
    }

    lastScrollPosition = position;
}
_

注:最後の7項目に対してのみsmoothScrollBy(dx, dy)を使用しています。また、どんな変更も歓迎します。

2
Abbas

私にとっての問題は、RecyclerViewNestedScrollView 。これが問題であることがわかりました。これに対する解決策は(Kotlin):

val childY = recycler_view.y + recycler_view.getChildAt(position).y
nested_scrollview.smoothScrollTo(0, childY.toInt())

Java(Himagiのクレジット https://stackoverflow.com/a/50367883/2917564

float y = recyclerView.getY() + recyclerView.getChildAt(selectedPosition).getY();    
scrollView.smoothScrollTo(0, (int) y);

コツは、ネストされたスクロールビューをRecyclerViewではなくYにスクロールすることです。これは、Android 5.0 Samsung J5およびHuawei P30 pro with Android 9。

2
jobbert

サイクリック/サーキュラーアダプターを作成するときに同じ問題が発生しました。位置を初期化すると0。私は最初にロバートのアプローチを使用することを検討しましたが、ハンドラーが1回しか起動しなかったため、信頼性が低すぎました。

これを解決するために、XXXの時間ごとに初期化が成功し、その後破棄されるかどうかを確認する間隔Observableを作成します。私のユースケースでは、このアプローチは非常に確実に機能しました。

private fun initialisePositionToAllowBidirectionalScrolling(layoutManager: LinearLayoutManager, realItemCount: Int) {
        val compositeDisposable = CompositeDisposable() // Added here for clarity, make this into a private global variable and clear in onDetach()/onPause() in case auto-disposal wouldn't ever occur here
        val initPosition = realItemCount * 1000

        Observable.interval(INIT_DELAY_MS, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe ({
                    if (layoutManager.findFirstVisibleItemPosition() == 0) {
                        layoutManager.scrollToPositionWithOffset(initPosition, 0)

                        if (layoutManager.findFirstCompletelyVisibleItemPosition() == initPosition) {
                            Timber.d("Adapter initialised, setting position to $initPosition and disposing interval subscription!")
                            compositeDisposable.clear()
                        }
                    }
                }, {
                    Timber.e("Failed to initialise position!\n$it")
                    compositeDisposable.clear()
                }).let { compositeDisposable.add(it) }
    }
1
ItWillDo

私の場合、これは毎回機能したLinearSmoothScrollerを使用できます:

  1. 最初にLinearSmoothScrollerのインスタンスを作成します。
  LinearSmoothScroller smoothScroller=new LinearSmoothScroller(activity){
            @Override
            protected int getVerticalSnapPreference() {
                return LinearSmoothScroller.SNAP_TO_START;
            }
        };
  1. そして、リサイクラビューを任意の位置にスクロールするには、次の操作を行います。
smoothScroller.setTargetPosition(pos);  // pos on which item you want to scroll recycler view
recyclerView.getLayoutManager().startSmoothScroll(smoothScroller);

できた.

1
Suraj Vaishnav

同じ問題がありました。私の問題は、スクロールしようとした後、非同期タスクのデータでビューを埋め直すことでした。 onPostExecute ofcからこの問題は修正されました。スクロールが実行されたとき、リストはすでに補充されていたため、遅延によりこの問題も修正されました。

0
Jujinko

受け入れられた答えは機能しますが、壊れることもあります。この問題の主な理由は、スクロールするように依頼するまでにリサイクラビューが準備できていない可能性があることです。同じための最善の解決策は、リサイクラビューが準備できるのを待ってからスクロールすることです。幸いなことにAndroidはそのようなオプションの1つを提供しています。以下のソリューションはKotlinのためのもので、同じものの代わりにJava.

newsRecyclerView.post {
    layoutManager?.scrollToPosition(viewModel.selectedItemPosition)
}

Post runnableメソッドはすべてのView要素で使用でき、ビューの準備ができたら実行されるため、必要なときにコードが正確に実行されるようにします。

0
Harikrishnan

以下のソリューションを使用して、リサイクラービューが再ロードされた後(方向の変更など)、リサイクラービューで選択されたアイテムを表示します。 LinearLayoutManagerをオーバーライドし、onSaveInstanceStateを使用して現在のリサイクラの位置を保存します。次に、onRestoreInstanceStateで、保存された位置が復元されます。最後に、onLayoutCompletedscrollToPosition(mRecyclerPosition)を使用して、以前に選択したリサイクラ位置を再び表示しますが、Robert Banyaiが述べたように、確実に動作するには一定の遅延が必要です挿入されます。 scrollToPositionが呼び出される前に、アダプターがデータをロードするのに十分な時間を提供する必要があると思います。

private class MyLayoutManager extends LinearLayoutManager{
    private boolean isRestored;

    public MyLayoutManager(Context context) {
        super(context);
    }

    public MyLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public MyLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void onLayoutCompleted(RecyclerView.State state) {
        super.onLayoutCompleted(state);
        if(isRestored && mRecyclerPosition >-1) {
            Handler handler=new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    MyLayoutManager.this.scrollToPosition(mRecyclerPosition);
                }
            },200);

        }
        isRestored=false;
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable savedInstanceState = super.onSaveInstanceState();
        Bundle bundle=new Bundle();
        bundle.putParcelable("saved_state",savedInstanceState);
        bundle.putInt("position", mRecyclerPosition);
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        Parcelable savedState = ((Bundle)state).getParcelable("saved_state");
        mRecyclerPosition = ((Bundle)state).getInt("position",-1);
        isRestored=true;
        super.onRestoreInstanceState(savedState);
    }
}
0
colens