मेरे पास Android के RecyclerView.State के बारे में एक प्रश्न है ।
मैं एक RecyclerView का उपयोग कर रहा हूं, मैं इसे कैसे उपयोग कर सकता हूं और इसे RecyclerView.State के साथ बांध सकता हूं?
मेरा उद्देश्य RecyclerView के स्क्रॉल स्थिति को सहेजना है ।
मेरे पास Android के RecyclerView.State के बारे में एक प्रश्न है ।
मैं एक RecyclerView का उपयोग कर रहा हूं, मैं इसे कैसे उपयोग कर सकता हूं और इसे RecyclerView.State के साथ बांध सकता हूं?
मेरा उद्देश्य RecyclerView के स्क्रॉल स्थिति को सहेजना है ।
जवाबों:
आप के साथ अंतिम बचाया स्थिति को बचाने के लिए कैसे योजना है 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
किया गया है। यह दी गई समस्या का एक बेहतर तरीका हो सकता है।
यह विषय Android डेवलपर्स मध्यम लेख पर कवर किया गया है ।
इसके अलावा, @ rubén-viguera ने नीचे दिए गए उत्तर में अधिक जानकारी साझा की। https://stackoverflow.com/a/61609823/892500
यदि आप LinearLayoutManager का उपयोग कर रहे हैं, तो यह प्री-बिल्ट सेव एपी रेखीयलैयूटमैनेजरइन्स्टेंस.ऑनसैव इनस्टांसटेस्ट () के साथ आता है और एपी रेखीय लयआउट मैनजमेंट इनस्टेंस.ऑनस्टॉर इनस्टोरेंस (...) को पुनर्स्थापित करता है।
उस के साथ, आप लौटे पार्सल को अपने आउटस्टेट में सहेज सकते हैं। जैसे,
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());
}
संपादित करें: आप GridLayoutManager के साथ उसी एप का उपयोग कर सकते हैं, क्योंकि यह LinearLayoutManager का उपवर्ग है। सुझाव के लिए धन्यवाद @wegsehen
संपादित करें: याद रखें, यदि आप एक बैकग्राउंड थ्रेड में डेटा लोड कर रहे हैं, तो आपको ओरिएंटेशन चेंज पर बहाल किए जाने की स्थिति के लिए अपने onPostExecute / onLoadFinished विधि के भीतर onRestoreInstanceState पर कॉल करने की आवश्यकता होगी।
@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
, उदाहरण के लिए, एक फ़ाइल एक्सप्लोरर ऐप।
SwipeRefreshLayout
?
RecyclerView
मैं अपनी सूची गतिविधि से दूर नेविगेट करने और फिर वापस नेविगेट करने के लिए वापस बटन पर क्लिक करने पर रिसाइक्लर व्यू की स्क्रॉल स्थिति को बचाना चाहता था। इस समस्या के लिए प्रदान किए गए कई समाधान या तो ज़रूरत से ज़्यादा जटिल थे या मेरे कॉन्फ़िगरेशन के लिए काम नहीं करते थे, इसलिए मैंने सोचा कि मैं अपना समाधान साझा करूँगा।
सबसे पहले अपने इंस्टेंस स्टेट को ऑनपॉज में सेव करें जैसा कि कई ने दिखाया है। मुझे लगता है कि यह यहाँ जोर देने लायक है कि 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 () में चर सेट करता हूं, ऑनपॉज़ में स्क्रॉल स्थिति बचा सकता हूं () और ऑनस्क्यूम में स्क्रॉल स्थिति सेट करता हूं।)
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);
}
}
आपको अब अपने आप से राज्य को बचाने और पुनर्स्थापित करने की आवश्यकता नहीं है। यदि आप xml और recyclerView.setSaveEnabled (ट्रू) (डिफॉल्ट बाय ट्रू) सिस्टम में यूनिक आईडी सेट करते हैं तो यह अपने आप आ जाएगा। यहाँ इस बारे में अधिक है: 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/+/…
यहाँ उन्हें बचाने और एक टुकड़े में recyclerview की स्थिति बनाए रखने के लिए मेरा दृष्टिकोण है। सबसे पहले, कोड के नीचे अपनी मूल गतिविधि में कॉन्फ़िगर परिवर्तन जोड़ें।
android:configChanges="orientation|screenSize"
फिर अपने फ्रैगमेंट में इन इंस्टेंस मेथड को घोषित करें
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);
}
यह दृष्टिकोण आपकी समस्या का समाधान करेगा। यदि आपके पास अलग लेआउट प्रबंधक है तो बस ऊपरी लेआउट प्रबंधक को बदलें।
जब आप AsyncTaskLoader के साथ इंटरनेट से डेटा पुनः लोड करने की आवश्यकता होती है, तो मैं रोटेशन के बाद GridLayoutManager के साथ RecyclerView की स्थिति को पुनर्स्थापित करता हूं।
Parcelable और GridLayoutManager का वैश्विक चर और एक स्थिर अंतिम स्ट्रिंग बनाएं:
private Parcelable savedRecyclerLayoutState;
private GridLayoutManager mGridLayoutManager;
private static final String BUNDLE_RECYCLER_LAYOUT = "recycler_layout";
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);
}
}
फिर जब लोडर इंटरनेट से डेटा प्राप्त करता है, तो आप onLoadFinished में recyclerview स्थिति को पुनर्स्थापित करते हैं ()
if(savedRecyclerLayoutState!=null){
mGridLayoutManager.onRestoreInstanceState(savedRecyclerLayoutState);
}
.. बेशक आप onCreate के अंदर gridLayoutManager को पलटना है। चियर्स
मेरे पास समान आवश्यकता है, लेकिन यहाँ समाधान मुझे रिसाइकलर के लिए डेटा के स्रोत के कारण लाइन के पार नहीं मिला।
मैं ऑनपॉज़ () में RecyclerViews 'LinearLayoutManager राज्य निकाल रहा था
private Parcelable state;
Public void onPause() {
state = mLinearLayoutManager.onSaveInstanceState();
}
Parcelable state
में सहेजा जाता है onSaveInstanceState(Bundle outState)
, और फिर से निकाला जाता है onCreate(Bundle savedInstanceState)
, जब savedInstanceState != null
।
हालाँकि, recyclerView एडॉप्टर पॉपुलेटेड है और एक ViewModel LiveData ऑब्जेक्ट द्वारा अद्यतन किया गया है जो DAO कॉल द्वारा एक कक्ष डेटाबेस में लौटाया गया है।
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 पर किया जा सकता है।
दो परिदृश्यों के बीच अंतर ने मुझे युगों तक संभाला।
एंड्रॉइड एपीआई लेवल 28 पर, मैं बस यह सुनिश्चित करता हूं कि मैंने अपना LinearLayoutManager
और RecyclerView.Adapter
अपने Fragment#onCreateView
तरीके का, और सब कुछ बस काम किया ️। मुझे कोई काम करने onSaveInstanceState
या onRestoreInstanceState
काम करने की ज़रूरत नहीं थी ।
यूजेन पिंचेक का उत्तर बताता है कि यह क्यों काम करता है।
Androidx recyclerView लाइब्रेरी के संस्करण 1.2.0-Alpha02 से शुरू होकर अब यह स्वचालित रूप से प्रबंधित हो गया है। बस इसके साथ जोड़ें:
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
और उपयोग करें:
adapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
StateRestorationPolicy Enum में 3 विकल्प हैं:
ध्यान दें कि इस उत्तर के समय में, recyclerView पुस्तकालय अभी भी अल्फा 03 में है, लेकिन उत्पादन के उद्देश्य के लिए अल्फा चरण उपयुक्त नहीं है ।
मेरे लिए, समस्या यह थी कि मैंने अपने एडेप्टर को बदलने के लिए हर बार एक नया लेआउटमैन स्थापित किया, जो कि रीसायकलर पर क्यू स्क्रॉल स्थिति खो रहा था।
मैं हाल ही में रीसायकलर व्यू के साथ मेरे द्वारा किए गए पूर्वानुमान को साझा करना चाहूंगा। मुझे उम्मीद है कि किसी को भी इस समस्या का सामना करने से लाभ होगा।
मेरी परियोजना की आवश्यकता: इसलिए मेरे पास एक पुनर्नवीनीकरण दृश्य है जो मेरी मुख्य गतिविधि (गतिविधि-ए) में कुछ क्लिक करने योग्य वस्तुओं को सूचीबद्ध करता है। जब आइटम पर क्लिक किया जाता है तो आइटम विवरण (गतिविधि-बी) के साथ एक नई गतिविधि दिखाई जाती है।
मैंने मेनिफेस्ट में लागू किया फ़ाइल कि एक्टिविटी-ए, एक्टिविटी-बी का जनक है, इस तरह, एक्टिविटी-बी के एक्शनबार पर मेरा बैक या होम बटन है
समस्या: हर बार जब मैंने एक्टिविटी-बी एक्शनबार में बैक या होम बटन दबाया, तो एक्टिविटी-ए पूरी गतिविधि लाइफ साइकल से शुरू होती है जो ऑनक्रिएट () से शुरू होती है।
भले ही मैं एक onSaveInstanceState () को RecyclerView के एडॉप्टर की सूची को सहेजते हुए कार्यान्वित करता हूं, जब गतिविधि-ए शुरू होता है यह जीवनचक्र होता है, फिर भी onInreatanceState onCreate () विधि में शून्य है।
इंटरनेट में आगे की खुदाई, मैं एक ही समस्या में आया था, लेकिन उस व्यक्ति ने देखा कि एनोइड डिवाइस (डिफ़ॉल्ट बैक / होम बटन) के नीचे बैक या होम बटन, एक्टिविटी-ए, एक्टिविटी लाइफ-साइकल में नहीं जाता है।
उपाय:
मैंने गतिविधि-बी पर घर या बैक बटन को सक्षम किया
OnCreate () विधि के तहत इस लाइन को जोड़ें ।ActionBar? ?SetDisplayHomeAsUpEnn की विश्वसनीय ()
ओवरऑल फन ऑनऑन्यूज इटसेलेक्टेड () मेथड में, मैंने आइटम के लिए जांच की। इटिम आईड पर आइडी के आधार पर किस आइटम पर क्लिक किया गया है। बैक बटन के लिए Id है
android.R.id.home
फिर android.R.id.home के अंदर एक फिनिश () फ़ंक्शन कॉल लागू करें
यह गतिविधि-बी को समाप्त कर देगा और पूरे जीवन-चक्र से गुजरे बिना एसिटिवी-ए लाएगा।
मेरी आवश्यकता के लिए यह अब तक का सबसे अच्छा समाधान है।
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)
}
मुझे मामूली अंतर के साथ एक ही समस्या थी, मैं डेटाबाइंडिंग का उपयोग कर रहा था , और मैं बाइंडिंग विधियों में recyclerView एडेप्टर और लेआउट मैनेजर को सेट कर रहा था।
समस्या यह थी कि डेटा-बाइंडिंग onResume के बाद हो रही थी, इसलिए यह onResume के बाद मेरे लेआउट मैनेजर को रीसेट कर रहा था और इसका मतलब है कि यह हर बार मूल स्थान पर वापस स्क्रॉल कर रहा था। इसलिए जब मैंने डेटाबाइंडिंग एडॉप्टर विधियों में लेआउटमैनेजर को सेट करना बंद कर दिया, तो यह फिर से ठीक काम करता है।
मेरे मामले में मैं layoutManager
XML में और दोनों में RecyclerView की स्थापना कर रहा था 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()
, जबकि ऑनपॉज़ में किसी अन्य गतिविधि पर स्विच किया जाएगा। यह उस स्क्रॉल स्थिति को बचाएगा और उस स्थिति को पुनर्स्थापित करेगा जब हम किसी अन्य गतिविधि से वापस आ रहे हैं।