คำตอบ:
คุณวางแผนที่จะบันทึกตำแหน่งที่บันทึกไว้ล่าสุดด้วยวิธี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];
}
};
}
เริ่มตั้งแต่การ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();
}
}
RecyclerView
แต่ไม่ใช่ถ้าคุณมีViewPager
ด้วยRecycerView
ในแต่ละหน้า
onCreateView
แต่หลังจากโหลดข้อมูล ดูคำตอบของ @Farmaker ที่นี่
เก็บ
lastFirstVisiblePosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
คืนค่า
((LinearLayoutManager) rv.getLayoutManager()).scrollToPosition(lastFirstVisiblePosition);
และหากไม่ได้ผลให้ลอง
((LinearLayoutManager) rv.getLayoutManager()).scrollToPositionWithOffset(lastFirstVisiblePosition,0)
ใส่ร้านค้าonPause()
และเรียกคืนonResume()
lastFirstVisiblePosition
การ0
หลังจากคืนนั้น กรณีนี้มีประโยชน์เมื่อคุณมีแฟรกเมนต์ / กิจกรรมที่โหลดข้อมูลที่แตกต่างกันRecyclerView
ตัวอย่างเช่นแอพ file explorer
SwipeRefreshLayout
?
RecyclerView
ฉันต้องการบันทึกตำแหน่งการเลื่อนของ 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);
}
ฉันตั้งค่าตัวแปรใน 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);
}
}
คุณไม่จำเป็นต้องบันทึกและฟื้นฟูสภาพด้วยตัวเองอีกต่อไป หากคุณตั้งค่า ID ที่ไม่ซ้ำกันใน xml และระบบ RecyclerView.setSaveEnabled (true) (true ตามค่าเริ่มต้น) จะดำเนินการโดยอัตโนมัติ ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้: http://trickyandroid.com/saving-android-view-state-correctly/
ตัวจัดการโครงร่างทั้งหมดที่รวมอยู่ในไลบรารีการสนับสนุนรู้วิธีบันทึกและกู้คืนตำแหน่งการเลื่อนแล้ว
ตำแหน่งการเลื่อนจะถูกเรียกคืนในเค้าโครงแรกซึ่งจะเกิดขึ้นเมื่อตรงตามเงื่อนไขต่อไปนี้:
RecyclerView
RecyclerView
โดยปกติคุณจะตั้งค่าตัวจัดการโครงร่างใน XML ดังนั้นสิ่งที่คุณต้องทำตอนนี้คือ
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
สำหรับสถานะของมันจะถูกบันทึกไว้
LinearLayoutManager.SavedState
: cs.android.com/androidx/platform/frameworks/support/+/…
นี่คือวิธีการของฉันในการบันทึกและรักษาสถานะของการรีไซเคิลในส่วนย่อย ขั้นแรกให้เพิ่มการเปลี่ยนแปลงการกำหนดค่าในกิจกรรมหลักของคุณตามโค้ดด้านล่าง
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);
}
วิธีนี้จะช่วยแก้ไขปัญหาของคุณ หากคุณมีตัวจัดการเลย์เอาต์ที่แตกต่างกันให้เปลี่ยนผู้จัดการเค้าโครงด้านบน
นี่คือวิธีที่ฉันกู้คืนตำแหน่ง 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 ไชโย
ฉันมีข้อกำหนดเหมือนกัน แต่วิธีแก้ปัญหาที่นี่ไม่ได้ทำให้ฉันข้ามสายเนื่องจากแหล่งข้อมูลสำหรับรีไซเคิล 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 สามารถทำได้หลังจากตั้งค่าอะแด็ปเตอร์บนรีไซเคิล
ความแตกต่างระหว่างสถานการณ์ทั้งสองทำให้ฉันต้องทนมานาน
ใน Android API ระดับ 28 ฉันแค่มั่นใจว่าได้ตั้งค่าLinearLayoutManager
และRecyclerView.Adapter
ในFragment#onCreateView
วิธีการของฉันและทุกอย่าง Just Worked ™️ ฉันไม่จำเป็นต้องทำอะไรonSaveInstanceState
หรือonRestoreInstanceState
ทำงาน
คำตอบของ Eugen Pechanecอธิบายว่าเหตุใดจึงได้ผล
เริ่มตั้งแต่เวอร์ชัน 1.2.0-alpha02 ของไลบรารีรีไซเคิล androidx ตอนนี้ได้รับการจัดการโดยอัตโนมัติแล้ว เพียงเพิ่มด้วย:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
และใช้:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
StateRestorationPolicy enum มี 3 ตัวเลือก:
ทราบว่าในช่วงเวลาของคำตอบนี้ห้องสมุด recyclerView ยังคงอยู่ใน alpha03 แต่เฟสอัลฟาไม่เหมาะสำหรับวัตถุประสงค์ในการผลิต
สำหรับฉันปัญหาคือฉันตั้งค่า layoutmanager ใหม่ทุกครั้งที่ฉันเปลี่ยนอะแดปเตอร์ทำให้สูญเสียตำแหน่งการเลื่อน que บนรีไซเคิลlerView
ฉันต้องการแบ่งปันสถานการณ์ล่าสุดที่ฉันพบกับ 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 ไม่เข้าสู่วงจรชีวิตของกิจกรรม
สารละลาย:
ฉันเปิดใช้งานปุ่มโฮมหรือปุ่มย้อนกลับบน Activity-B
ภายใต้ onCreate () วิธีการเพิ่มบรรทัดนี้supportActionBar? .setDisplayHomeAsUpEnabled (จริง)
ในเมธอด 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)
}
ฉันมีปัญหาเดียวกันกับความแตกต่างเล็กน้อยฉันใช้Databindingและฉันกำลังตั้งค่าอะแดปเตอร์รีไซเคิลและ layoutManager ในวิธีการผูก
ปัญหาคือการผูกข้อมูลเกิดขึ้นหลังจากonResumeดังนั้นมันจึงรีเซ็ต layoutManager ของฉันหลังจาก onResume และนั่นหมายความว่ามันกำลังเลื่อนกลับไปที่เดิมทุกครั้ง ดังนั้นเมื่อฉันหยุดการตั้งค่า layoutManager ในวิธีการอะแดปเตอร์ Databinding มันก็ทำงานได้ดีอีกครั้ง
ในกรณีของฉันฉันกำลังตั้งค่า RecyclerView layoutManager
ทั้งใน XML และในonViewCreated
. การลบการมอบหมายในการonViewCreated
แก้ไข
with(_binding.list) {
// layoutManager = LinearLayoutManager(context)
adapter = MyAdapter().apply {
listViewModel.data.observe(viewLifecycleOwner,
Observer {
it?.let { setItems(it) }
})
}
}
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 จะประหยัดเลื่อนตำแหน่งและเรียกคืนตำแหน่งเมื่อเรากลับมาจากกิจกรรมอื่น