จะบันทึกตำแหน่งการเลื่อนของ RecyclerView โดยใช้ RecyclerView.State ได้อย่างไร


108

ฉันมีคำถามเกี่ยวกับ Android ของRecyclerView.State

ฉันใช้ RecyclerView ฉันจะใช้และเชื่อมโยงกับ RecyclerView.State ได้อย่างไร

จุดประสงค์ของฉันคือการบันทึกเลื่อนตำแหน่ง RecyclerView ของ

คำตอบ:


37

คุณวางแผนที่จะบันทึกตำแหน่งที่บันทึกไว้ล่าสุดด้วยวิธีRecyclerView.Stateใด?

คุณสามารถพึ่งพาสถานะการบันทึกที่ดีได้ตลอดเวลา ขยายRecyclerViewและแทนที่ onSaveInstanceState () และonRestoreInstanceState():

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        LayoutManager layoutManager = getLayoutManager();
        if(layoutManager != null && layoutManager instanceof LinearLayoutManager){
            mScrollPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
        }
        SavedState newState = new SavedState(superState);
        newState.mScrollPosition = mScrollPosition;
        return newState;
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        super.onRestoreInstanceState(state);
        if(state != null && state instanceof SavedState){
            mScrollPosition = ((SavedState) state).mScrollPosition;
            LayoutManager layoutManager = getLayoutManager();
            if(layoutManager != null){
              int count = layoutManager.getItemCount();
              if(mScrollPosition != RecyclerView.NO_POSITION && mScrollPosition < count){
                  layoutManager.scrollToPosition(mScrollPosition);
              }
            }
        }
    }

    static class SavedState extends android.view.View.BaseSavedState {
        public int mScrollPosition;
        SavedState(Parcel in) {
            super(in);
            mScrollPosition = in.readInt();
        }
        SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);
            dest.writeInt(mScrollPosition);
        }
        public static final Parcelable.Creator<SavedState> CREATOR
                = new Parcelable.Creator<SavedState>() {
            @Override
            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };
    }

1
ตอนนี้ฉันมีคำถามอื่นว่าไม่ RecyclerView.State คืออะไร?
陈文涛

4
มันได้ผลจริงหรือ? ฉันได้ลองแล้ว แต่ฉันได้รับข้อยกเว้นรันไทม์เช่นนี้เรื่อย ๆ : MyRecyclerView $ SavedState ไม่สามารถส่งไปยัง android.support.v7.widget.RecyclerView $ SavedState
Daivid

2
ในสถานะคืนค่าอินสแตนซ์เราจำเป็นต้องส่ง superState ไปยัง super class (มุมมอง recytcler) เช่นนี้: super.onRestoreInstanceState (((ScrollPositionSavingRecyclerView.SavedState) state) .getSuperState ());
micnoy

5
ไม่ได้ผลถ้าคุณขยาย RecyclerView คุณจะได้รับ BadParcelException
EpicPandaForce

1
สิ่งนี้ไม่จำเป็น: หากคุณใช้บางอย่างเช่น LinearLayoutManager / GridLayoutManager "ตำแหน่งการเลื่อน" จะถูกบันทึกไว้โดยอัตโนมัติสำหรับคุณแล้ว (มันคือ mAnchorPosition ใน LinearLayoutManager.SavedState) หากคุณไม่เห็นสิ่งนั้นแสดงว่ามีบางอย่างผิดปกติกับวิธีการนำข้อมูลไปใช้ซ้ำ
Brian Yencho

230

อัปเดต

เริ่มตั้งแต่การrecyclerview:1.2.0-alpha02เปิดตัวStateRestorationPolicyได้รับการแนะนำ อาจเป็นแนวทางที่ดีกว่าในการแก้ไขปัญหา

หัวข้อนี้ได้รับการคุ้มครองในหุ่นยนต์นักพัฒนาบทความขนาดกลาง

นอกจากนี้ @ rubén-viguera ยังแบ่งปันรายละเอียดเพิ่มเติมในคำตอบด้านล่าง https://stackoverflow.com/a/61609823/892500

คำตอบเก่า

หากคุณใช้ LinearLayoutManager จะมาพร้อมกับบันทึก api linearLayoutManagerInstance.onSaveInstanceState ()และกู้คืน api ที่สร้างไว้ล่วงหน้า linearLayoutManagerInstance.onRestoreInstanceState (... )

ด้วยวิธีนี้คุณสามารถบันทึกพัสดุที่ส่งคืนไปยังสถานะภายนอกของคุณได้ เช่น,

outState.putParcelable("KeyForLayoutManagerState", linearLayoutManagerInstance.onSaveInstanceState());

และเรียกคืนตำแหน่งคืนค่าด้วยสถานะที่คุณบันทึกไว้ เช่น,

Parcelable state = savedInstanceState.getParcelable("KeyForLayoutManagerState");
linearLayoutManagerInstance.onRestoreInstanceState(state);

ในการสรุปโค้ดสุดท้ายของคุณจะมีลักษณะดังนี้

private static final String BUNDLE_RECYCLER_LAYOUT = "classname.recycler.layout";

/**
 * This is a method for Fragment. 
 * You can do the same in onCreate or onRestoreInstanceState
 */
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);

    if(savedInstanceState != null)
    {
        Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
        recyclerView.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, recyclerView.getLayoutManager().onSaveInstanceState());
}

แก้ไข:คุณยังสามารถใช้ apis เดียวกันกับ GridLayoutManager ได้เนื่องจากเป็นคลาสย่อยของ LinearLayoutManager ขอบคุณ @wegsehen สำหรับข้อเสนอแนะ

แก้ไข:โปรดจำไว้ว่าหากคุณกำลังโหลดข้อมูลในเธรดพื้นหลังด้วยคุณจะต้องเรียกใช้ onRestoreInstanceState ภายในเมธอด onPostExecute / onLoadFinished เพื่อให้ตำแหน่งถูกเรียกคืนเมื่อมีการเปลี่ยนแปลงการวางแนวเช่น

@Override
protected void onPostExecute(ArrayList<Movie> movies) {
    mLoadingIndicator.setVisibility(View.INVISIBLE);
    if (movies != null) {
        showMoviePosterDataView();
        mDataAdapter.setMovies(movies);
      mRecyclerView.getLayoutManager().onRestoreInstanceState(mSavedRecyclerLayoutState);
        } else {
            showErrorMessage();
        }
    }

21
นี่เป็นคำตอบที่ดีที่สุดอย่างชัดเจนเหตุใดจึงไม่ยอมรับ โปรดทราบว่า LayoutManager ใด ๆ มีสิ่งนั้นไม่เพียง แต่ LinearLayoutManager ยังมี GridLayoutManager และหากไม่ใช่การนำไปใช้งานที่ผิดพลาด
Paul Woitaschek

linearLayoutManager.onInstanceState () คืนค่า null เสมอ -> ตรวจสอบซอร์สโค้ด น่าเสียดายที่ไม่ทำงาน
luckyhandler

1
RecyclerView ไม่บันทึกสถานะ LayoutManager ของตัวเองใน onSaveInstanceState หรือไม่ กำลังดู v24 ของการสนับสนุน lib ...
androidguy

3
สิ่งนี้ใช้ได้กับหน้าเดียวRecyclerViewแต่ไม่ใช่ถ้าคุณมีViewPagerด้วยRecycerViewในแต่ละหน้า
Kris B

1
@ Ivan ฉันคิดว่ามันใช้งานได้ในส่วน (เพิ่งตรวจสอบ) แต่ไม่ใช่ในonCreateViewแต่หลังจากโหลดข้อมูล ดูคำตอบของ @Farmaker ที่นี่
CoolMind

81

เก็บ

lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();

คืนค่า

((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);

และหากไม่ได้ผลให้ลอง

((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)

ใส่ร้านค้าonPause() และเรียกคืนonResume()


นี้ทำงาน แต่คุณอาจจะต้องตั้งค่าlastFirstVisiblePositionการ0หลังจากคืนนั้น กรณีนี้มีประโยชน์เมื่อคุณมีแฟรกเมนต์ / กิจกรรมที่โหลดข้อมูลที่แตกต่างกันRecyclerViewตัวอย่างเช่นแอพ file explorer
blueware

ยอดเยี่ยม! มันตอบสนองทั้งการกลับมาจากกิจกรรมรายละเอียดไปยังรายการและสถานะการบันทึกในการหมุนหน้าจอ
Javi

จะทำอย่างไรถ้า RecyclerView ของฉันอยู่ในSwipeRefreshLayout?
Morozov

2
เหตุใดตัวเลือกการกู้คืนครั้งแรกจึงไม่ทำงาน แต่ตัวเลือกที่สองจะเป็นอย่างไร
lucidbrot

1
@MehranJalili ดูเหมือนจะเป็นRecyclerView
Vlad

22

ฉันต้องการบันทึกตำแหน่งการเลื่อนของ Recycler View เมื่อออกจากกิจกรรมในรายการของฉันแล้วคลิกปุ่มย้อนกลับเพื่อย้อนกลับ วิธีแก้ปัญหาหลายอย่างที่มีให้สำหรับปัญหานี้มีความซับซ้อนมากกว่าที่จำเป็นหรือใช้ไม่ได้กับการกำหนดค่าของฉันฉันจึงคิดว่าจะแบ่งปันวิธีแก้ปัญหาของฉัน

ก่อนอื่นให้บันทึกสถานะอินสแตนซ์ของคุณใน onPause ตามที่หลายคนแสดง ฉันคิดว่าควรเน้นที่นี่ว่า onSaveInstanceState เวอร์ชันนี้เป็นวิธีการจากคลาส RecyclerView.LayoutManager

private LinearLayoutManager mLayoutManager;
Parcelable state;

        @Override
        public void onPause() {
            super.onPause();
            state = mLayoutManager.onSaveInstanceState();
        }

กุญแจสำคัญในการทำให้สิ่งนี้ทำงานได้อย่างถูกต้องคือตรวจสอบให้แน่ใจว่าคุณเรียกใช้ onRestoreInstanceState หลังจากที่คุณต่ออะแด็ปเตอร์ของคุณตามที่บางคนระบุไว้ในเธรดอื่น อย่างไรก็ตามการเรียกเมธอดจริงนั้นง่ายกว่าที่หลาย ๆ คนระบุไว้

private void someMethod() {
    mVenueRecyclerView.setAdapter(mVenueAdapter);
    mLayoutManager.onRestoreInstanceState(state);
}

1
นั่นเป็นวิธีที่ถูกต้อง : D
Afjalur Rahman Rana

1
สำหรับฉัน onSaveInstanceState ไม่ได้ถูกเรียกดังนั้นการบันทึกสถานะใน onPause จึงดูเหมือนเป็นตัวเลือกที่ดีกว่า ฉันยังเปิดแบ็คสแต็คเพื่อกลับไปที่แฟรกเมนต์
filthy_wizard

สำหรับการโหลดในสถานะ ซึ่งฉันทำใน onViewCreated ดูเหมือนว่าจะใช้ได้เฉพาะเมื่อฉันอยู่ในโหมดดีบั๊กและใช้จุดพัก? หรือถ้าฉันเพิ่มการหน่วงเวลาและวางการดำเนินการในโครูทีน
filthy_wizard

21

ฉันตั้งค่าตัวแปรใน onCreate () บันทึกตำแหน่งเลื่อนใน onPause () และตั้งค่าตำแหน่งเลื่อนใน onResume ()

public static int index = -1;
public static int top = -1;
LinearLayoutManager mLayoutManager;

@Override
public void onCreate(Bundle savedInstanceState)
{
    //Set Variables
    super.onCreate(savedInstanceState);
    cRecyclerView = ( RecyclerView )findViewById(R.id.conv_recycler);
    mLayoutManager = new LinearLayoutManager(this);
    cRecyclerView.setHasFixedSize(true);
    cRecyclerView.setLayoutManager(mLayoutManager);

}

@Override
public void onPause()
{
    super.onPause();
    //read current recyclerview position
    index = mLayoutManager.findFirstVisibleItemPosition();
    View v = cRecyclerView.getChildAt(0);
    top = (v == null) ? 0 : (v.getTop() - cRecyclerView.getPaddingTop());
}

@Override
public void onResume()
{
    super.onResume();
    //set recyclerview position
    if(index != -1)
    {
        mLayoutManager.scrollToPositionWithOffset( index, top);
    }
}

ให้ชดเชยรายการด้วย ดี. ขอบคุณ!
t0m

14

คุณไม่จำเป็นต้องบันทึกและฟื้นฟูสภาพด้วยตัวเองอีกต่อไป หากคุณตั้งค่า ID ที่ไม่ซ้ำกันใน xml และระบบ RecyclerView.setSaveEnabled (true) (true ตามค่าเริ่มต้น) จะดำเนินการโดยอัตโนมัติ ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้: http://trickyandroid.com/saving-android-view-state-correctly/


14

ตัวจัดการโครงร่างทั้งหมดที่รวมอยู่ในไลบรารีการสนับสนุนรู้วิธีบันทึกและกู้คืนตำแหน่งการเลื่อนแล้ว

ตำแหน่งการเลื่อนจะถูกเรียกคืนในเค้าโครงแรกซึ่งจะเกิดขึ้นเมื่อตรงตามเงื่อนไขต่อไปนี้:

  • ตัวจัดการเค้าโครงแนบมากับไฟล์ RecyclerView
  • อะแดปเตอร์ติดอยู่กับไฟล์ RecyclerView

โดยปกติคุณจะตั้งค่าตัวจัดการโครงร่างใน XML ดังนั้นสิ่งที่คุณต้องทำตอนนี้คือ

  1. โหลดอะแด็ปเตอร์พร้อมข้อมูล
  2. จากนั้นต่ออะแดปเตอร์เข้ากับไฟล์RecyclerView

คุณสามารถทำได้ทุกเมื่อไม่ จำกัดFragment.onCreateViewหรือActivity.onCreateหรือ

ตัวอย่าง: ตรวจสอบให้แน่ใจว่าเชื่อมต่ออะแด็ปเตอร์ทุกครั้งที่มีการอัพเดตข้อมูล

viewModel.liveData.observe(this) {
    // Load adapter.
    adapter.data = it

    if (list.adapter != adapter) {
        // Only set the adapter if we didn't do it already.
        list.adapter = adapter
    }
}

ตำแหน่งการเลื่อนที่บันทึกไว้คำนวณจากข้อมูลต่อไปนี้:

  • ตำแหน่งอะแดปเตอร์ของรายการแรกบนหน้าจอ
  • พิกเซลออฟเซ็ตด้านบนของรายการจากด้านบนสุดของรายการ (สำหรับเค้าโครงแนวตั้ง)

ดังนั้นคุณอาจไม่ตรงกันเล็กน้อยเช่นหากสินค้าของคุณมีขนาดที่แตกต่างกันในแนวตั้งและแนวนอน


RecyclerView/ ScrollView/ สิ่งต้องมีandroid:idสำหรับสถานะของมันจะถูกบันทึกไว้


นี่คือสำหรับฉัน ฉันกำลังตั้งค่าอะแดปเตอร์ในตอนแรกจากนั้นอัปเดตข้อมูลเมื่อมี ตามที่อธิบายไว้ข้างต้นปล่อยทิ้งไว้จนกว่าจะมีข้อมูลทำให้เลื่อนกลับไปที่เดิมโดยอัตโนมัติ
NeilS

ขอบคุณ @Eugen มีเอกสารเกี่ยวกับการบันทึกและการกู้คืนตำแหน่งเลื่อนของ LayoutManager หรือไม่
Adam Hurwitz

@AdamHurwitz คุณคาดหวังเอกสารประเภทใด สิ่งที่ฉันอธิบายคือรายละเอียดการใช้งาน LinearLayoutManager ดังนั้นโปรดอ่านซอร์สโค้ด
Eugen Pechanec

1
นี่คือลิงค์ไปที่LinearLayoutManager.SavedState: cs.android.com/androidx/platform/frameworks/support/+/…
Eugen Pechanec

ขอบคุณ @EugenPechanec สิ่งนี้ใช้ได้ผลสำหรับฉันสำหรับการหมุนอุปกรณ์ ฉันยังใช้มุมมองการนำทางด้านล่างและเมื่อฉันเปลี่ยนไปใช้แท็บด้านล่างอื่นและกลับมารายการจะเริ่มต้นจากจุดเริ่มต้นอีกครั้ง มีความคิดว่าทำไมถึงเป็นเช่นนี้?
schv09

4

นี่คือวิธีการของฉันในการบันทึกและรักษาสถานะของการรีไซเคิลในส่วนย่อย ขั้นแรกให้เพิ่มการเปลี่ยนแปลงการกำหนดค่าในกิจกรรมหลักของคุณตามโค้ดด้านล่าง

android:configChanges="orientation|screenSize"

จากนั้นใน Fragment ของคุณให้ประกาศวิธีการอินสแตนซ์เหล่านี้

private  Bundle mBundleRecyclerViewState;
private Parcelable mListState = null;
private LinearLayoutManager mLinearLayoutManager;

เพิ่มโค้ดด้านล่างในเมธอด @onPause ของคุณ

@Override
public void onPause() {
    super.onPause();
    //This used to store the state of recycler view
    mBundleRecyclerViewState = new Bundle();
    mListState =mTrailersRecyclerView.getLayoutManager().onSaveInstanceState();
    mBundleRecyclerViewState.putParcelable(getResources().getString(R.string.recycler_scroll_position_key), mListState);
}

เพิ่ม onConfigartionChanged Method ตามด้านล่าง

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    //When orientation is changed then grid column count is also changed so get every time
    Log.e(TAG, "onConfigurationChanged: " );
    if (mBundleRecyclerViewState != null) {
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                mListState = mBundleRecyclerViewState.getParcelable(getResources().getString(R.string.recycler_scroll_position_key));
                mTrailersRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);

            }
        }, 50);
    }
    mTrailersRecyclerView.setLayoutManager(mLinearLayoutManager);
}

วิธีนี้จะช่วยแก้ไขปัญหาของคุณ หากคุณมีตัวจัดการเลย์เอาต์ที่แตกต่างกันให้เปลี่ยนผู้จัดการเค้าโครงด้านบน


คุณอาจไม่จำเป็นต้องมีตัวจัดการสำหรับข้อมูลที่มีอยู่ วิธีการตลอดอายุการใช้งานจะเพียงพอสำหรับการบันทึก / โหลด
Mahi

@sayaMahi คุณช่วยอธิบายอย่างเหมาะสมได้ไหม ฉันยังได้เห็นว่า onConfigurationChanged ป้องกันการเรียกใช้เมธอดทำลาย ดังนั้นจึงไม่ใช่วิธีแก้ปัญหาที่เหมาะสม
Pawan kumar sharma

3

นี่คือวิธีที่ฉันกู้คืนตำแหน่ง RecyclerView ด้วย GridLayoutManager หลังจากการหมุนเมื่อคุณต้องการโหลดข้อมูลจากอินเทอร์เน็ตด้วย AsyncTaskLoader

สร้างตัวแปรส่วนกลางของ Parcelable และ GridLayoutManager และสตริงสุดท้ายแบบคงที่:

private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";

บันทึกสถานะของ gridLayoutManager ใน onSaveInstance ()

 @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putParcelable(BUNDLE_RECYCLER_LAYOUT, 
            mGridLayoutManager.onSaveInstanceState());
        }

คืนค่าใน onRestoreInstanceState

@Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        //restore recycler view at same position
        if (savedInstanceState != null) {
            savedRecyclerLayoutState = savedInstanceState.getParcelable(BUNDLE_RECYCLER_LAYOUT);
        }
    }

จากนั้นเมื่อตัวโหลดดึงข้อมูลจากอินเทอร์เน็ตคุณจะเรียกคืนตำแหน่ง Recyclerview ใน onLoadFinished ()

if(savedRecyclerLayoutState!=null){
                mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
            }

.. แน่นอนว่าคุณต้องสร้างอินสแตนซ์ gridLayoutManager ภายใน onCreate ไชโย


3

ฉันมีข้อกำหนดเหมือนกัน แต่วิธีแก้ปัญหาที่นี่ไม่ได้ทำให้ฉันข้ามสายเนื่องจากแหล่งข้อมูลสำหรับรีไซเคิล View

ฉันกำลังแยกสถานะ LinearLayoutManager ของ RecyclerViews ใน onPause ()

private Parcelable state;

Public void onPause() {
    state = mLinearLayoutManager.onSaveInstanceState();
}

Parcelable stateจะถูกบันทึกonSaveInstanceState(Bundle outState)และดึงข้อมูลอีกครั้งในonCreate(Bundle savedInstanceState)เมื่อsavedInstanceState != nullเมื่อ

อย่างไรก็ตามอะแด็ปเตอร์รีไซเคิล View ถูกเติมข้อมูลและอัพเดตโดยอ็อบเจ็กต์ ViewModel LiveData ที่ส่งคืนโดยการเรียก DAO ไปยังฐานข้อมูล Room

mViewModel.getAllDataToDisplay(mSelectedData).observe(this, new Observer<List<String>>() {
    @Override
    public void onChanged(@Nullable final List<String> displayStrings) {
        mAdapter.swapData(displayStrings);
        mLinearLayoutManager.onRestoreInstanceState(state);
    }
});

ในที่สุดฉันก็พบว่าการกู้คืนสถานะอินสแตนซ์โดยตรงหลังจากตั้งค่าข้อมูลในอะแด็ปเตอร์จะทำให้สถานะเลื่อนตลอดการหมุน

ฉันสงสัยว่านี่เป็นเพราะไฟล์ LinearLayoutManagerสถานะที่ฉันเรียกคืนถูกเขียนทับเมื่อข้อมูลถูกส่งคืนโดยการเรียกฐานข้อมูลหรือสถานะที่กู้คืนไม่มีความหมายเทียบกับ LinearLayoutManager ที่ 'ว่างเปล่า'

หากข้อมูลอะแด็ปเตอร์พร้อมใช้งานโดยตรง (เช่นไม่เกิดขึ้นกับการเรียกฐานข้อมูล) การกู้คืนสถานะอินสแตนซ์บน LinearLayoutManager สามารถทำได้หลังจากตั้งค่าอะแด็ปเตอร์บนรีไซเคิล

ความแตกต่างระหว่างสถานการณ์ทั้งสองทำให้ฉันต้องทนมานาน


3

ใน Android API ระดับ 28 ฉันแค่มั่นใจว่าได้ตั้งค่าLinearLayoutManagerและRecyclerView.AdapterในFragment#onCreateViewวิธีการของฉันและทุกอย่าง Just Worked ™️ ฉันไม่จำเป็นต้องทำอะไรonSaveInstanceStateหรือonRestoreInstanceStateทำงาน

คำตอบของ Eugen Pechanecอธิบายว่าเหตุใดจึงได้ผล


2

เริ่มตั้งแต่เวอร์ชัน 1.2.0-alpha02 ของไลบรารีรีไซเคิล androidx ตอนนี้ได้รับการจัดการโดยอัตโนมัติแล้ว เพียงเพิ่มด้วย:

implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"

และใช้:

adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY

StateRestorationPolicy enum มี 3 ตัวเลือก:

  • ALLOW - สถานะเริ่มต้นที่เรียกคืนสถานะ RecyclerView ทันทีในเลย์เอาต์ถัดไป
  • PREVENT_WHEN_EMPTY - กู้คืนสถานะ RecyclerView เฉพาะเมื่ออะแด็ปเตอร์ไม่ว่างเปล่า (adapter.getItemCount ()> 0) หากข้อมูลของคุณถูกโหลดแบบ async RecyclerView จะรอจนกว่าข้อมูลจะถูกโหลดและจากนั้นสถานะจะถูกเรียกคืนเท่านั้น หากคุณมีรายการเริ่มต้นเช่นส่วนหัวหรือตัวบ่งชี้ความคืบหน้าในการโหลดเป็นส่วนหนึ่งของอะแดปเตอร์ของคุณคุณควรใช้ตัวเลือกป้องกันเว้นแต่จะเพิ่มรายการเริ่มต้นโดยใช้MergeAdapter MergeAdapterMergeAdapter รอให้อะแด็ปเตอร์ทั้งหมดพร้อมใช้งานจากนั้นจึงกู้คืนสถานะ
  • ป้องกัน - การกู้คืนสถานะทั้งหมดจะถูกเลื่อนออกไปจนกว่าคุณจะตั้งค่า ALLOW หรือ PREVENT_WHEN_EMPTY

ทราบว่าในช่วงเวลาของคำตอบนี้ห้องสมุด recyclerView ยังคงอยู่ใน alpha03 แต่เฟสอัลฟาไม่เหมาะสำหรับวัตถุประสงค์ในการผลิต


ประหยัดงานหนักมาก อย่างอื่นเราต้องบันทึกดัชนีการคลิกรายการเรียกคืนองค์ประกอบป้องกันการวาดใหม่ใน onCreateView ใช้ scrollToPosition (หรือทำตามคำตอบอื่น ๆ ) แต่ผู้ใช้สามารถสังเกตเห็นความแตกต่างได้หลังจากกลับมา
Rahul Kahale

นอกจากนี้หากฉันต้องการบันทึกตำแหน่ง RecyclerView หลังจากนำทางผ่านแอพ ... มีเครื่องมือสำหรับมันหรือไม่?
StayCool

@RubenViguera ทำไมอัลฟ่าไม่ได้มีไว้เพื่อการผลิต? ฉันหมายความว่ามันแย่มากเหรอ?
StayCool

1

สำหรับฉันปัญหาคือฉันตั้งค่า layoutmanager ใหม่ทุกครั้งที่ฉันเปลี่ยนอะแดปเตอร์ทำให้สูญเสียตำแหน่งการเลื่อน que บนรีไซเคิลlerView


1

ฉันต้องการแบ่งปันสถานการณ์ล่าสุดที่ฉันพบกับ RecyclerView ฉันหวังว่าทุกคนที่ประสบปัญหาเดียวกันจะได้รับประโยชน์

ความต้องการโครงการของฉัน: ดังนั้นฉันจึงมี RecyclerView ที่แสดงรายการที่คลิกได้ในกิจกรรมหลักของฉัน (กิจกรรม -A) เมื่อคลิกไอเทมกิจกรรมใหม่จะแสดงพร้อมรายละเอียดไอเทม (กิจกรรม -B)

ฉันใช้ในไฟล์Manifestโดยที่ Activity-A เป็นพาเรนต์ของ Activity-B ด้วยวิธีนี้ฉันมีปุ่มย้อนกลับหรือปุ่มโฮมบน ActionBar ของ Activity-B

ปัญหา: ทุกครั้งที่ฉันกดปุ่มย้อนกลับหรือปุ่มโฮมใน Activity-B ActionBar Activity-A จะเข้าสู่วงจรชีวิตของกิจกรรมทั้งหมดโดยเริ่มจาก onCreate ()

แม้ว่าฉันจะใช้ onSaveInstanceState () บันทึกรายการของอะแดปเตอร์ของ RecyclerView เมื่อ Activity-A เริ่มวงจรชีวิต แต่ saveInstanceState จะเป็นโมฆะเสมอในเมธอด onCreate ()

ขุดเพิ่มเติมในอินเทอร์เน็ตฉันพบปัญหาเดียวกัน แต่บุคคลนั้นสังเกตเห็นว่าปุ่มย้อนกลับหรือโฮมด้านล่างอุปกรณ์ Anroid (ปุ่มย้อนกลับ / โฮมเริ่มต้น) กิจกรรม -A ไม่เข้าสู่วงจรชีวิตของกิจกรรม

สารละลาย:

  1. ฉันลบแท็กสำหรับกิจกรรมหลักในรายการสำหรับกิจกรรม -B
  2. ฉันเปิดใช้งานปุ่มโฮมหรือปุ่มย้อนกลับบน Activity-B

    ภายใต้ onCreate () วิธีการเพิ่มบรรทัดนี้supportActionBar? .setDisplayHomeAsUpEnabled (จริง)

  3. ในเมธอด onOptionsItemSelected () ที่สนุกซ้อนทับฉันได้ตรวจสอบไอเท็ม ItemId ที่ไอเท็มถูกคลิกตาม id รหัสสำหรับปุ่มย้อนกลับคือ

    android.R.id.home

    จากนั้นใช้การเรียกฟังก์ชัน finish () ใน android.R.id.home

    สิ่งนี้จะสิ้นสุดกิจกรรม -B และนำ Acitivy-A โดยไม่ต้องผ่านวงจรชีวิตทั้งหมด

สำหรับความต้องการของฉันนี่เป็นทางออกที่ดีที่สุด

     override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_show_project)

    supportActionBar?.title = projectName
    supportActionBar?.setDisplayHomeAsUpEnabled(true)

}


 override fun onOptionsItemSelected(item: MenuItem?): Boolean {

    when(item?.itemId){

        android.R.id.home -> {
            finish()
        }
    }

    return super.onOptionsItemSelected(item)
}

1

ฉันมีปัญหาเดียวกันกับความแตกต่างเล็กน้อยฉันใช้Databindingและฉันกำลังตั้งค่าอะแดปเตอร์รีไซเคิลและ layoutManager ในวิธีการผูก

ปัญหาคือการผูกข้อมูลเกิดขึ้นหลังจากonResumeดังนั้นมันจึงรีเซ็ต layoutManager ของฉันหลังจาก onResume และนั่นหมายความว่ามันกำลังเลื่อนกลับไปที่เดิมทุกครั้ง ดังนั้นเมื่อฉันหยุดการตั้งค่า layoutManager ในวิธีการอะแดปเตอร์ Databinding มันก็ทำงานได้ดีอีกครั้ง


คุณประสบความสำเร็จได้อย่างไร
Kedar Sukerkar

0

ในกรณีของฉันฉันกำลังตั้งค่า RecyclerView layoutManagerทั้งใน XML และในonViewCreated. การลบการมอบหมายในการonViewCreatedแก้ไข

with(_binding.list) {
//  layoutManager =  LinearLayoutManager(context)
    adapter = MyAdapter().apply {
        listViewModel.data.observe(viewLifecycleOwner,
            Observer {
                it?.let { setItems(it) }
            })
    }
}

-1

Activity.java:

public RecyclerView.LayoutManager mLayoutManager;

Parcelable state;

    mLayoutManager = new LinearLayoutManager(this);


// Inside `onCreate()` lifecycle method, put the below code :

    if(state != null) {
    mLayoutManager.onRestoreInstanceState(state);

    } 

    @Override
    protected void onResume() {
        super.onResume();

        if (state != null) {
            mLayoutManager.onRestoreInstanceState(state);
        }
    }   

   @Override
    protected void onPause() {
        super.onPause();

        state = mLayoutManager.onSaveInstanceState();

    }   

ทำไมฉันใช้OnSaveInstanceState()ในonPause()วิธีการในขณะที่สวิทช์เพื่อกิจกรรมอื่น onPause จะ called.It จะประหยัดเลื่อนตำแหน่งและเรียกคืนตำแหน่งเมื่อเรากลับมาจากกิจกรรมอื่น


2
ฟิลด์สาธารณะคงไม่ดี ข้อมูลทั่วโลกและเสื้อกล้ามไม่ดี
weston

@weston ตอนนี้ฉันเข้าใจแล้วฉันเปลี่ยน savingstate เป็น onpause เพื่อลบ global
สตีฟ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.