ViewPager और टुकड़े - टुकड़े के राज्य को स्टोर करने का सही तरीका क्या है?


492

कुछ मॉड्यूल में UI तर्क को अलग करने के लिए फ्रेगमेंट बहुत अच्छे लगते हैं। लेकिन इसके साथ-साथ ViewPagerइसका जीवनचक्र अभी भी मेरे लिए गलत है। तो गुरु विचारों की बुरी तरह से जरूरत है!

संपादित करें

नीचे गूंगा घोल देखें ;-)

क्षेत्र

मुख्य गतिविधि में ViewPagerटुकड़ों के साथ है। वे टुकड़े अन्य (सबमैन) गतिविधियों के लिए थोड़ा अलग तर्क लागू कर सकते हैं, इसलिए टुकड़े के डेटा को गतिविधि के अंदर कॉलबैक इंटरफ़ेस के माध्यम से भरा जाता है। और सब कुछ पहले लॉन्च पर ठीक काम करता है, लेकिन! ...

मुसीबत

जब गतिविधि फिर से हो जाती है (जैसे अभिविन्यास परिवर्तन पर) तो ViewPagerखंडों को करें। कोड (आप नीचे पाएंगे) का कहना है कि हर बार गतिविधि बनने पर मैं ViewPagerटुकड़ों के रूप में एक नए टुकड़े एडाप्टर बनाने की कोशिश करता हूं (शायद यह समस्या है) लेकिन FragmentManager के पास पहले से ही ये सभी टुकड़े कहीं संग्रहीत हैं (जहां?)? उन लोगों के लिए मनोरंजन तंत्र शुरू करता है। तो मनोरंजन तंत्र "पुराने" टुकड़े के onAttach, onCreateView, आदि को कॉलबैक इंटरफ़ेस कॉल के साथ गतिविधि की कार्यान्वित विधि के माध्यम से डेटा आरंभ करने के लिए कहता है। लेकिन यह विधि नव निर्मित खंड की ओर इशारा करती है जो गतिविधि की ऑनक्रिट विधि के माध्यम से बनाई गई है।

मुद्दा

शायद मैं गलत पैटर्न का उपयोग कर रहा हूं, लेकिन यहां तक ​​कि एंड्रॉइड 3 प्रो बुक में इसके बारे में बहुत कुछ नहीं है। तो, कृपया , मुझे एक-दो पंच दें और इंगित करें कि इसे सही तरीके से कैसे किया जाए। बहुत धन्यवाद!

कोड

मुख्य गतिविधि

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

बेसपेजरएक्टिविटी उर्फ ​​हेल्पर

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

अनुकूलक

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

टुकड़ा

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

समाधान

गूंगा समाधान putFragment के साथ onSaveInstanceState (मेजबान गतिविधि के अंदर) के टुकड़े को बचाने के लिए है और getFragment के माध्यम से onCreate के अंदर उन्हें प्राप्त करें। लेकिन मुझे अभी भी एक अजीब सा अहसास है कि चीजें इस तरह काम नहीं करनी चाहिए ... नीचे कोड देखें:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}

1
मुझे अब आश्चर्य है: क्या मुझे onCreate () में उपयोग करने के लिए onSavedInstancestate के माध्यम से एक बहुत अलग दृष्टिकोण का उपयोग करना चाहिए या मुख्य गतिविधि (डैशबोर्ड) के टुकड़े को बचाने की कोशिश करनी चाहिए। क्या उन टुकड़ों को बचाने और उन्हें ऑनक्रिएट में बंडल से प्राप्त करने का एक उचित तरीका है? वे पार्सल योग्य नहीं लगते ...
ओलेक्सी मालोवैनी

2
दूसरा दृष्टिकोण काम करता है - "निवारक" देखें। लेकिन यह कोड का एक बदसूरत टुकड़ा है, है ना?
ओलेक्सी मालोवैनी

1
Android टैग को साफ़ करने के प्रयास के लिए (यहाँ विवरण: meta.stackexchange.com/questions/100529/… ) क्या आप अपने समाधान को उत्तर के रूप में पोस्ट करने और इसे चयनित के रूप में चिह्नित करना चाहेंगे? इस तरह यह एक अनुत्तरित प्रश्न के रूप में नहीं दिखाई देगा :)
अलेक्जेंडर लुकास

1
हाँ, लगता है कि यह ठीक है। खदान से बेहतर होने की उम्मीद ...
ओलेक्सी मालोवैनी

1
गूंगा समाधान भी काम करता है? यह मुझे एक शून्य सूचक अपवाद देता है ..
जेन लियू

जवाबों:


451

जब FragmentPagerAdapterFragmentManager में एक खंड जोड़ता है, तो यह उस विशेष स्थिति के आधार पर एक विशेष टैग का उपयोग करता है जिसे खंड रखा जाएगा। FragmentPagerAdapter.getItem(int position)केवल तब कहा जाता है जब उस स्थिति के लिए एक टुकड़ा मौजूद नहीं होता है। घूमने के बाद, एंड्रॉइड यह नोटिस करेगा कि उसने इस विशेष स्थिति के लिए पहले से ही एक टुकड़ा बनाया / सहेजा है और इसलिए यह केवल FragmentManager.findFragmentByTag()एक नया बनाने के बजाय इसके साथ पुन: कनेक्ट करने का प्रयास करता है । इस का उपयोग करते समय यह सब मुक्त हो जाता है FragmentPagerAdapterऔर यही कारण है कि getItem(int)विधि के अंदर आपके टुकड़े के प्रारंभिककरण कोड का होना सामान्य है ।

यहां तक ​​कि अगर हम एक का उपयोग नहीं कर रहे थे FragmentPagerAdapter, तो हर बार एक नया टुकड़ा बनाना एक अच्छा विचार नहीं है Activity.onCreate(Bundle)। जैसा कि आपने देखा है, जब एक टुकड़ा FragmentManager में जोड़ा जाता है, तो इसे घुमाए जाने के बाद आपके लिए फिर से बनाया जाएगा और इसे फिर से जोड़ने की कोई आवश्यकता नहीं है। टुकड़ों के साथ काम करते समय ऐसा करना त्रुटियों का एक सामान्य कारण है।

टुकड़ों के साथ काम करते समय एक सामान्य दृष्टिकोण यह है:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

ए का उपयोग करते समय FragmentPagerAdapter, हम एडाप्टर को टुकड़े प्रबंधन को त्याग देते हैं, और उपरोक्त चरणों को करने की आवश्यकता नहीं है। डिफ़ॉल्ट रूप से, यह केवल एक फ़्रैगमेंट को सामने और वर्तमान स्थिति के पीछे लोड करेगा (हालांकि यह तब तक नष्ट नहीं होता है जब तक आप उपयोग नहीं कर रहे हैं FragmentStatePagerAdapter)। यह ViewPager.setOffscreenPageLimit (int) द्वारा नियंत्रित किया जाता है । इस वजह से, एडॉप्टर के बाहर के टुकड़ों पर सीधे कॉल करने के तरीके वैध होने की गारंटी नहीं है, क्योंकि वे जीवित भी नहीं हो सकते हैं।

एक लंबी कहानी को छोटा putFragmentकरने के लिए, बाद में संदर्भ प्राप्त करने में सक्षम होने के लिए उपयोग करने के लिए आपका समाधान इतना पागल नहीं है, और वैसे भी टुकड़ों के उपयोग करने के सामान्य तरीके के विपरीत नहीं है। अन्यथा एक संदर्भ प्राप्त करना मुश्किल है क्योंकि टुकड़े टुकड़े को एडेप्टर द्वारा जोड़ा जाता है, और आप व्यक्तिगत रूप से नहीं। बस यह सुनिश्चित करें कि offscreenPageLimitआपके वांछित अंशों को हर समय लोड करने के लिए पर्याप्त है, क्योंकि आप इस पर भरोसा करते हैं। यह ViewPager की आलसी लोडिंग क्षमताओं को दरकिनार करता है, लेकिन लगता है कि आप अपने आवेदन के लिए क्या चाहते हैं।

एक और तरीका FragmentPageAdapter.instantiateItem(View, int)यह है कि इसे वापस करने से पहले सुपर कॉल से लौटाए गए टुकड़े के संदर्भ को ओवरराइड और सेव करें (यह पहले से मौजूद है, तो टुकड़े को खोजने का तर्क है)।

एक फुलर तस्वीर के लिए, FragmentPagerAdapter (लघु) और ViewPager (लंबे) के कुछ स्रोत पर एक नज़र डालें ।


7
पिछला भाग पसंद आया। अंशों के लिए एक कैश था और अंदर कैश तर्क में पुट को स्थानांतरित कर दिया FragmentPageAdapter.instantiateItem(View, int)। अंत में एक लंबे समय तक चलने वाला बग तय किया जो केवल रोटेशन / कॉन्फ़िगरेशन परिवर्तन में प्रकट होता है और मुझे पागल कर रहा था ...
कार्लोस सोबिरहो

2
यह एक मुद्दा है जो मैं रोटेशन परिवर्तन पर चल रहा था। हो सकता है कि मैं सिर्फ देखने के लिए नहीं देख सकता, लेकिन क्या यह प्रलेखित है? पिछले टैग से पुनर्प्राप्त करने के लिए टैग का उपयोग करते हुए? यह शायद एक स्पष्ट जवाब है, लेकिन मैं Android विकास के लिए काफी नया हूं।

1
@ इंडस्ट्रियल-एंटीडिप्रेसेंट यह कंटेनर की आईडी है (जैसे कि फ्रेमलेयआउट) जिसमें फ्रैगमेंट जोड़ा जाना है।
एंटनी

1
btw, मेरे लिए यह FragmentPageAdapter.instantiateItem(ViewGroup, int)बजाय था FragmentPageAdapter.instantiateItem(View, int)
प्रदर्शन नाम

1
Btw क्या आप जानते हैं कि (यदि कोई हो) टुकड़ा जीवनचक्र विधि को कहा जाता है जब टुकड़ा स्क्रीन से स्वाइप हो जाता है? यह onDetach(), या कुछ और है?
यगेशर

36

मैं एक समाधान की पेशकश करना चाहते हैं उस पर फैलता antonytहै अद्भुत जवाब और अधिभावी का उल्लेख FragmentPageAdapter.instantiateItem(View, int)बनाई गई के लिए संदर्भ को बचाने के लिए Fragmentsताकि आप बाद में उन पर काम कर सकते हैं। इसके साथ भी काम करना चाहिए FragmentStatePagerAdapter; विवरण के लिए नोट्स देखें।


यहाँ एक सरल उदाहरण Fragmentsदिया गया है FragmentPagerAdapterकि किस तरह से रिटर्न का संदर्भ प्राप्त किया जा सकता है जो कि आंतरिक tagsसेट पर निर्भर नहीं करता है Fragments। कुंजी इसके बजायinstantiateItem() संदर्भ को ओवरराइड करना और सहेजना है ।getItem()

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

या यदि आप tagsवर्ग सदस्य चर / संदर्भों के बजाय साथ काम करना पसंद करते हैं तो आप सेट को उसी तरीके से Fragmentsपकड़ भी सकते हैं : नोट: यह लागू नहीं होता है क्योंकि इसे बनाते समय यह सेट नहीं होता है ।tagsFragmentPagerAdapterFragmentStatePagerAdaptertagsFragments

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

ध्यान दें कि यह विधि आंतरिक tagसेट की नकल करने पर भरोसा नहीं करती है FragmentPagerAdapterऔर इसके बजाय उन्हें पुनर्प्राप्त करने के लिए उचित एपीआई का उपयोग करती है। इस तरह से भले ही tagआप के भविष्य के संस्करणों में परिवर्तन SupportLibraryअभी भी सुरक्षित रहें।


यह मत भूलो कि आपके डिजाइन के आधार पर Activity, Fragmentsआप काम करने की कोशिश कर रहे हैं या अभी तक मौजूद नहीं हो सकते हैं, इसलिए आपको nullअपने संदर्भों का उपयोग करने से पहले चेक करके उस पर ध्यान देना होगा।

इसके अलावा, यदि आप इसके बजाय काम कर रहे हैं FragmentStatePagerAdapter, तो आप अपने लिए कठिन संदर्भ नहीं रखना चाहते हैं Fragmentsक्योंकि आपके पास उनमें से कई हो सकते हैं और कठिन संदर्भ अनावश्यक रूप से उन्हें स्मृति में रखेंगे। इसके बजाय मानक के बजाय चर Fragmentमें संदर्भ को बचाएं WeakReference। ऐशे ही:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}

स्मृति की कमी पर एंड्रॉइड FragmentManager को अप्रयुक्त टुकड़ों को नष्ट करने के लिए कह सकता है, है ना? यदि मैं सही हूं, तो आपका दूसरा संस्करण काम करेगा, पहला विफल हो सकता है। (पता नहीं कि वर्तमान एंड्रॉइड वर्जन ऐसा करते हैं, लेकिन ऐसा लगता है कि हमें यह उम्मीद करनी होगी।)
मोरिट्ज़ दोनों

1
क्या FragmetPagerAdapterहर स्क्रीन रोटेशन पर गतिविधि के onCreate बनाने के बारे में। क्या यह गलत है क्योंकि यह पहले से ही जोड़े गए टुकड़ों का पुन: उपयोग कर सकता हैFragmentPagerAdapter
बहमन

ओवरराइडिंग instantiateItem()जाने का रास्ता है; गतिविधि और अडैप्टर को फिर से शुरू करने के बाद इसने मुझे स्क्रीन रोटेशन को संभालने में मदद की, और मेरे मौजूदा फ्रैगमेंट उदाहरणों को पुनः प्राप्त किया; मैंने अपने आप को कोड में एक अनुस्मारक के रूप में टिप्पणी छोड़ दी: रोटेशन के बाद, getItem()आमंत्रित नहीं किया गया है; केवल इस विधि instantiateItem()को बुलाया जाता है। instantiateItem()वास्तव में सुपर कार्यान्वयन , नए उदाहरणों को तात्कालिक करने के बजाय, रोटेशन (आवश्यक होने के बाद) को पुनः संलग्न करता है!
HelloImKevo

मैं Fragment createdFragment = (Fragment) super.instantiateItem..पहले समाधान में अशक्त सूचक मिल रहा हूँ ।
एलेक्सा

इस समस्या के सभी विभिन्न डुप्लिकेट / वैरिएंट पुनरावृत्तियों के माध्यम से शिकार करने के बाद, यह सबसे अच्छा समाधान था। (एक और क्यू से अधिक प्रासंगिक शीर्षक के साथ क्रॉस-संदर्भित, इसलिए इसके लिए धन्यवाद!)
मंडिस्साॅव

18

मुझे आपके प्रश्न के लिए एक और अपेक्षाकृत आसान समाधान मिला।

जैसा कि आप FragmentPagerAdapter स्रोत कोड से देख सकते हैं , उपयोग किए गए टैग FragmentPagerAdapterके FragmentManagerतहत स्टोर द्वारा प्रबंधित टुकड़े :

String tag="android:switcher:" + viewId + ":" + index;

viewIdहै container.getId(), containerअपने है ViewPagerउदाहरण। indexटुकड़ा की स्थिति है। इसलिए आप ऑब्जेक्ट आईडी को यहां सेव कर सकते हैं outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

यदि आप इस टुकड़े के साथ संवाद करना चाहते हैं, तो आप ऐसा कर सकते हैं FragmentManager, जैसे:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")

21
हममम मुझे नहीं लगता कि यह एक आंतरिक टैग नामकरण सम्मेलन पर आपके उत्तर के रूप में जाने का एक अच्छा तरीका है जो कि किसी भी तरह से हमेशा के लिए रहने वाला नहीं है।
डोरी

1
ऐसे समाधान की तलाश में जो आंतरिक पर भरोसा नहीं करता है tag, मेरे जवाब की कोशिश करने पर विचार करें ।
टोनी चैन

16

मैं शायद थोड़ा अलग मामले के लिए एक वैकल्पिक समाधान की पेशकश करना चाहता हूं, क्योंकि उत्तर के लिए मेरी कई खोज मुझे इस धागे तक ले जाती रहीं।

मेरा मामला - मैं पृष्ठों को गतिशील रूप से बना / जोड़ रहा हूं और उन्हें एक ViewPager में स्लाइड कर रहा हूं, लेकिन जब घुमाया जाता है (onConfigurationChange) मैं एक नए पृष्ठ के साथ समाप्त होता हूं क्योंकि बेशक ऑनक्रीट को फिर से कहा जाता है। लेकिन मैं उन सभी पृष्ठों का संदर्भ रखना चाहता हूं जो रोटेशन से पहले बनाए गए थे।

समस्या - मेरे द्वारा बनाए गए प्रत्येक टुकड़े के लिए मेरे पास अद्वितीय पहचानकर्ता नहीं हैं, इसलिए संदर्भ का एकमात्र तरीका किसी भी तरह से संदर्भों को स्टोर करना था ताकि रोटेशन / कॉन्फ़िगरेशन परिवर्तन के बाद पुनर्स्थापना की जा सके।

वर्कअराउंड - मुख्य अवधारणा के लिए गतिविधि थी (जो फ्रैगमेंट प्रदर्शित करती है) मौजूदा फ़्रैगमेंट के संदर्भों की सरणी को भी प्रबंधित करती है, क्योंकि यह गतिविधि ऑनस्लेवइनस्टैंसस्टेट में बंडल का उपयोग कर सकती है

public class MainActivity extends FragmentActivity

इसलिए इस गतिविधि के भीतर, मैं खुले पृष्ठों को ट्रैक करने के लिए एक निजी सदस्य घोषित करता हूं

private List<Fragment> retainedPages = new ArrayList<Fragment>();

यह अद्यतन किया जाता है हर बार onSaveInstanceState कहा जाता है और onCreate में पुनर्स्थापित किया जाता है

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... तो एक बार संग्रहीत होने के बाद, इसे पुनः प्राप्त किया जा सकता है ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

ये मुख्य गतिविधि के लिए आवश्यक परिवर्तन थे, और इसलिए मुझे काम करने के लिए अपने FragmentPagerAdapter के भीतर सदस्यों और विधियों की आवश्यकता थी, इसलिए भीतर

public class ViewPagerAdapter extends FragmentPagerAdapter

एक समान निर्माण (जैसा कि मेनएक्टिविटी में ऊपर दिखाया गया है)

private List<Fragment> _pages = new ArrayList<Fragment>();

और यह सिंकिंग (जैसा कि onSaveInstanceState में ऊपर प्रयोग किया गया है) विशेष रूप से विधियों द्वारा समर्थित है

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

और फिर अंत में, टुकड़ा वर्ग में

public class CustomFragment extends Fragment

इस सब के लिए काम करने के लिए, पहले दो बदलाव हुए थे

public class CustomFragment extends Fragment implements Serializable

और फिर इसे जोड़ने के लिए ताकि फ्रैगमेंट नष्ट न हों

setRetainInstance(true);

मैं अभी भी अपने सिर को फ्रैगमेंट्स और एंड्रॉइड जीवन चक्र के आसपास लपेटने की प्रक्रिया में हूं, इसलिए यहां पर चेतावनी इस पद्धति में अतिरेक / अक्षमता हो सकती है। लेकिन यह मेरे लिए काम करता है और मुझे आशा है कि मेरे जैसे मामलों के साथ दूसरों के लिए भी मददगार हो सकता है।


2
+1 setRetainInstance (सत्य) के लिए - वह काला जादू जिसकी मुझे तलाश थी!
अपकर्ष

यह FragmentStatePagerAdapter (v13) का उपयोग करके और भी आसान था। जो आपके लिए राज्य को बहाल करने और जारी करने का सौदा करते हैं।
बुचर

स्पष्ट विवरण और बहुत अच्छा समाधान। धन्यवाद!
ब्रूनो बीरी

8

मेरा समाधान बहुत अशिष्ट है, लेकिन काम करता है: गतिशील रूप से बनाए रखा डेटा से गतिशील रूप से बनाए गए मेरे टुकड़े होने के नाते, मैं बस PageAdapterकॉल करने से पहले सभी टुकड़े निकालता हूं super.onSaveInstanceState()और फिर उन्हें गतिविधि निर्माण पर फिर से बनाता हूं:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

आप उन्हें हटा नहीं सकते onDestroy(), अन्यथा आपको यह अपवाद मिलता है:

java.lang.IllegalStateException: इसके बाद यह कार्रवाई नहीं कर सकते onSaveInstanceState

यहाँ पृष्ठ एडॉप्टर में कोड:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

onCreate()टुकड़े होने के बाद मैं केवल वर्तमान पृष्ठ सहेजता हूं और उसे पुनर्स्थापित करता हूं ।

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  

मुझे पता नहीं क्यों, लेकिन InformDataSetChanged (); दुर्घटना के लिए एप्लिकेशन का कारण बनता है
मैलाकियास

4

वह क्या है BasePagerAdapter? आपको मानक पेजर एडेप्टर में से किसी एक का उपयोग करना चाहिए - या तो FragmentPagerAdapterया FragmentStatePagerAdapter, इस बात पर निर्भर करता है कि क्या आप चाहते हैं कि फ्रेगमेंट जो अब जरूरत नहीं हैं, ViewPagerउन्हें या तो अपने आसपास (पूर्व) रखा जाए या उनका राज्य बचा (बाद वाला) और फिर से बनाया जाए फिर से जरूरत है।

उपयोग करने के लिए नमूना कोड यहांViewPager पाया जा सकता है

यह सच है कि गतिविधि के उदाहरणों में एक दृश्य पेजर में अंशों का प्रबंधन थोड़ा जटिल है, क्योंकि FragmentManagerरूपरेखा में राज्य को बचाने और पेजर द्वारा किए गए किसी भी सक्रिय टुकड़े को पुनर्स्थापित करने का ध्यान रखा गया है। यह सब वास्तव में इसका मतलब यह है कि एडाप्ट करने वाले को यह सुनिश्चित करने की आवश्यकता होती है कि जो भी बहाल हुए टुकड़े हैं उनके साथ वह फिर से जुड़ जाए। आप इसके लिए कोड को देख सकते हैं FragmentPagerAdapterया FragmentStatePagerAdapterयह देख सकते हैं कि यह कैसे किया जाता है।


1
BasePagerAdapter कोड मेरे प्रश्न में उपलब्ध है। जैसा कि कोई देख सकता है कि यह केवल TitleProvider को लागू करने के उद्देश्य से FragmentPagerAdapter बढ़ाता है । तो सब कुछ पहले से ही एंड्रॉइड देव सुझाए गए दृष्टिकोण के साथ काम कर रहा है।
ओलेक्सी मालोवैनी

मुझे "FragmentStatePagerAdapter" की जानकारी नहीं थी। आपने मुझे सचमुच घंटे बचाए। धन्यवाद।
नॉटोस

2

अगर किसी के पास अपने FragmentStatePagerAdapter के साथ समस्याएँ हैं, तो इसके टुकड़ों की स्थिति को ठीक से बहाल नहीं कर रहा है ... अर्थात ... नए Fragments FragmentStatePagerAdapter द्वारा बनाए जा रहे हैं, बजाय इसके कि उन्हें राज्य से पुनर्स्थापित किया जा रहा है ...

सुनिश्चित करें कि आप कॉल करने ViewPager.setOffscreenPageLimit()से पहले कॉल करते हैंViewPager.setAdapter(fragmentStatePagerAdapter)

कॉल करने पर ViewPager.setOffscreenPageLimit()... ViewPager अपने एडाप्टर को तुरंत देखेगा और उसके टुकड़े प्राप्त करने का प्रयास करेगा। ऐसा हो सकता है इससे पहले कि ViewPager के पास Fragments को SaveInstanceState से पुनर्स्थापित करने का मौका हो (इस प्रकार नए फ़्रैगमेंट बनाए जो SavedInstanceState से फिर से आरंभिक नहीं हो सकते क्योंकि वे नए हैं)।


0

मैं इस सरल और सुरुचिपूर्ण समाधान के साथ आया था। यह मानता है कि गतिविधि फ्रैगमेंट बनाने के लिए जिम्मेदार है, और एडेप्टर सिर्फ उनकी सेवा करता है।

यह एडेप्टर कोड है (यहां कुछ भी अजीब नहीं है, इस तथ्य के अलावा कि mFragmentsगतिविधि द्वारा बनाए गए टुकड़ों की एक सूची है)

class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {

    public MyFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getCount() {
        return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        TabFragment fragment = (TabFragment)mFragments.get(position);
        return fragment.getTitle();
    }
} 

इस धागे की पूरी समस्या को "पुराने" अंशों का संदर्भ मिल रहा है, इसलिए मैं इस कोड का उपयोग गतिविधि के ऑनक्रिएट में करता हूं।

    if (savedInstanceState!=null) {
        if (getSupportFragmentManager().getFragments()!=null) {
            for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                mFragments.add(fragment);
            }
        }
    }

यदि आवश्यक हो तो निश्चित रूप से आप इस कोड को और बेहतर बना सकते हैं, उदाहरण के लिए यह सुनिश्चित करना कि टुकड़े किसी विशेष वर्ग के उदाहरण हैं।


0

अभिविन्यास परिवर्तन के बाद टुकड़े प्राप्त करने के लिए आपको .getTag () का उपयोग करना होगा।

    getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)

थोड़ा और अधिक हैंडलिंग के लिए मैंने अपने पेज एडेप्टर के लिए अपने खुद के ArrayList को किसी भी स्थिति में viewPagerId और FragmentClass द्वारा टुकड़ा प्राप्त करने के लिए लिखा था:

public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
private final String logTAG = MyPageAdapter.class.getName() + ".";

private ArrayList<MyPageBuilder> fragmentPages;

public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
    super(fm);
    fragmentPages = fragments;
}

@Override
public Fragment getItem(int position) {
    return this.fragmentPages.get(position).getFragment();
}

@Override
public CharSequence getPageTitle(int position) {
    return this.fragmentPages.get(position).getPageTitle();
}

@Override
public int getCount() {
    return this.fragmentPages.size();
}


public int getItemPosition(Object object) {
    //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden

    Log.d(logTAG, object.getClass().getName());
    return POSITION_NONE;
}

public Fragment getFragment(int position) {
    return getItem(position);
}

public String getTag(int position, int viewPagerId) {
    //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())

    return "android:switcher:" + viewPagerId + ":" + position;
}

public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
    return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
}


public static class MyPageBuilder {

    private Fragment fragment;

    public Fragment getFragment() {
        return fragment;
    }

    public void setFragment(Fragment fragment) {
        this.fragment = fragment;
    }

    private String pageTitle;

    public String getPageTitle() {
        return pageTitle;
    }

    public void setPageTitle(String pageTitle) {
        this.pageTitle = pageTitle;
    }

    private int icon;

    public int getIconUnselected() {
        return icon;
    }

    public void setIconUnselected(int iconUnselected) {
        this.icon = iconUnselected;
    }

    private int iconSelected;

    public int getIconSelected() {
        return iconSelected;
    }

    public void setIconSelected(int iconSelected) {
        this.iconSelected = iconSelected;
    }

    public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        this.pageTitle = pageTitle;
        this.icon = icon;
        this.iconSelected = selectedIcon;
        this.fragment = frag;
    }
}

public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
    private final String logTAG = MyPageArrayList.class.getName() + ".";

    public MyPageBuilder get(Class cls) {
        // Fragment über FragmentClass holen
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return super.get(indexOf(item));
            }
        }
        return null;
    }

    public String getTag(int viewPagerId, Class cls) {
        // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
        for (MyPageBuilder item : this) {
            if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                return "android:switcher:" + viewPagerId + ":" + indexOf(item);
            }
        }
        return null;
    }
}

तो बस टुकड़ों के साथ एक MyPageArrayList बनाएँ:

    myFragPages = new MyPageAdapter.MyPageArrayList();

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_data_frag),
            R.drawable.ic_sd_storage_24dp,
            R.drawable.ic_sd_storage_selected_24dp,
            new WidgetDataFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_color_frag),
            R.drawable.ic_color_24dp,
            R.drawable.ic_color_selected_24dp,
            new WidgetColorFrag()));

    myFragPages.add(new MyPageAdapter.MyPageBuilder(
            getString(R.string.widget_config_textsize_frag),
            R.drawable.ic_settings_widget_24dp,
            R.drawable.ic_settings_selected_24dp,
            new WidgetTextSizeFrag()));

और उन्हें व्यूगर में जोड़ें:

    mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
    myViewPager.setAdapter(mAdapter);

इसके बाद आप अभिविन्यास के बाद इसकी कक्षा का उपयोग करके सही टुकड़े को बदल सकते हैं:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));

-30

जोड़ें:

   @SuppressLint("ValidFragment")

अपनी कक्षा से पहले।

यह ऐसा कुछ नहीं करता है:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.