ViewPager e frammenti: qual è il modo giusto per memorizzare lo stato del frammento?


492

I frammenti sembrano essere molto belli per la separazione della logica dell'interfaccia utente in alcuni moduli. Ma insieme al ViewPagersuo ciclo di vita è ancora nebbioso per me. Quindi i pensieri di Guru sono estremamente necessari!

modificare

Vedi la soluzione stupida qui sotto ;-)

Scopo

L'attività principale ha a ViewPager con frammenti. Quei frammenti potrebbero implementare una logica leggermente diversa per altre attività (secondarie), quindi i dati dei frammenti vengono riempiti tramite un'interfaccia di callback all'interno dell'attività. E tutto funziona bene al primo lancio, ma! ...

Problema

Quando l'attività viene ricreata (ad es. Al cambio di orientamento), lo stesso ViewPagervale per i frammenti. Il codice (che troverai di seguito) dice che ogni volta che l'attività viene creata provo a crearne una nuovaViewPager adattatore per frammenti uguale ai frammenti (forse questo è il problema) ma FragmentManager ha già tutti questi frammenti memorizzati da qualche parte (dove?) E avvia il meccanismo ricreativo per quelli. Quindi il meccanismo di ricreazione chiama il "vecchio" frammento di onAttach, onCreateView, ecc. Con la mia chiamata dell'interfaccia di callback per l'avvio dei dati tramite il metodo implementato dell'Attività. Ma questo metodo punta al frammento appena creato che viene creato tramite il metodo onCreate dell'attività.

Problema

Forse sto usando schemi sbagliati ma anche il libro di Android 3 Pro non ha molto a riguardo. Quindi, per favore , dammi un pugno di due e sottolinea come farlo nel modo giusto. Grazie molto!

Codice

Attività principale

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);
}

BasePagerActivity aka helper

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Adattatore

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
}

}

Frammento

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... */
}

Soluzione

La soluzione stupida è quella di salvare i frammenti all'interno di onSaveInstanceState (dell'attività host) con putFragment e farli entrare inCreate tramite getFragment. Ma ho ancora la strana sensazione che le cose non dovrebbero funzionare così ... Vedi il codice qui sotto:

    @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
Ora mi chiedo: dovrei usare un approccio molto diverso o provare a salvare frammenti di attività principale (Dashboard) tramite onSavedInstancestate per usarli in onCreate (). Esiste un modo corretto per salvare quei frammenti e recuperarli dal bundle in onCreate? Non sembrano essere parcelabili ...
Oleksii Malovanyi,

2
Il secondo approccio funziona - vedi "Sulution". Ma sembra essere un brutto pezzo di codice, non è vero?
Oleksii Malovanyi,

1
Per lo sforzo di ripulire il tag Android (dettagli qui: meta.stackexchange.com/questions/100529/… ), ti dispiacerebbe pubblicare la soluzione come risposta e contrassegnarla come quella selezionata? In questo modo non si presenterà come una domanda senza risposta :)
Alexander Lucas,

1
sì, pensa che sia OK. Speravo in smth meglio del mio ...
Oleksii Malovanyi,

1
La soluzione stupida funziona anche? Mi dà un'eccezione puntatore nullo ..
Zhen Liu,

Risposte:


451

Quando FragmentPagerAdapteraggiunge un frammento a FragmentManager, utilizza un tag speciale basato sulla posizione particolare in cui verrà posizionato il frammento. FragmentPagerAdapter.getItem(int position)viene chiamato solo quando non esiste un frammento per quella posizione. Dopo aver ruotato, Android noterà che ha già creato / salvato un frammento per questa particolare posizione e quindi tenta semplicemente di riconnettersi con esso FragmentManager.findFragmentByTag(), invece di crearne uno nuovo. Tutto questo è gratuito quando si utilizza il FragmentPagerAdaptered è il motivo per cui è normale avere il codice di inizializzazione del frammento all'interno del getItem(int)metodo.

Anche se non stessimo usando a FragmentPagerAdapter, non è una buona idea creare un nuovo frammento ogni voltaActivity.onCreate(Bundle) . Come hai notato, quando un frammento viene aggiunto a FragmentManager, verrà ricreato per te dopo la rotazione e non è necessario aggiungerlo di nuovo. Ciò è una causa comune di errori quando si lavora con i frammenti.

Un approccio normale quando si lavora con i frammenti è questo:

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(); 
    }

    ...

}

Quando si utilizza a FragmentPagerAdapter, si rinuncia alla gestione dei frammenti all'adattatore e non è necessario eseguire i passaggi precedenti. Per impostazione predefinita, precaricherà solo un frammento davanti e dietro la posizione corrente (anche se non li distrugge a meno che non lo si stia usando FragmentStatePagerAdapter). Questo è controllato da ViewPager.setOffscreenPageLimit (int) . Per questo motivo, non è garantito che i metodi di chiamata diretta sui frammenti esterni all'adattatore siano validi, poiché potrebbero anche non essere vivi.

Per putFragmentfarla breve, la tua soluzione da utilizzare per poter ottenere un riferimento in seguito non è così folle, e non così diversa dal normale modo di usare i frammenti (sopra). È difficile ottenere un riferimento altrimenti perché il frammento viene aggiunto dall'adattatore e non da te personalmente. Assicurati solo che offscreenPageLimitsia abbastanza alto da caricare i frammenti desiderati in ogni momento, poiché fai affidamento sul fatto che sia presente. Ciò ignora le capacità di caricamento lento del ViewPager, ma sembra essere ciò che desideri per la tua applicazione.

Un altro approccio è quello di sostituire FragmentPageAdapter.instantiateItem(View, int)e salvare un riferimento al frammento restituito dalla super chiamata prima di restituirlo (ha la logica per trovare il frammento, se già presente).

Per un'immagine più completa, dai un'occhiata ad alcune delle fonti di FragmentPagerAdapter (breve) e ViewPager (lungo).


7
Adoro l'ultima parte. Aveva una cache per i frammenti e spostato il put nella logica della cache all'interno di FragmentPageAdapter.instantiateItem(View, int). Finalmente risolto un bug di lunga durata che appare solo nel cambio di rotazione / configurazione e mi faceva impazzire ...
Carlos Sobrinho

2
Questo risolto un problema che stavo avendo sul cambio di rotazione. Forse non riesco proprio a vedere per cercare, ma è documentato? cioè usando il tag per recuperare da uno stato precedente? Forse è una risposta ovvia, ma sono abbastanza nuovo nello sviluppo di Android.

1
@ Antidepressivo industriale È l'id del contenitore (ad esempio un FrameLayout) a cui aggiungere il frammento.
antonyt,

1
a proposito, per me è stato FragmentPageAdapter.instantiateItem(ViewGroup, int)piuttosto che FragmentPageAdapter.instantiateItem(View, int).
Visualizza nome

1
Btw sai quale (se presente) metodo di ciclo di vita del frammento viene chiamato quando il frammento viene rimosso dallo schermo? È onDetach()o qualcos'altro?
ygesher,

36

Voglio offrire una soluzione che si espande su antonyt's risposta meravigliosa e la menzione di rilevante FragmentPageAdapter.instantiateItem(View, int)per salvare i riferimenti a creato Fragmentsin modo da poter fare il lavoro su di loro in seguito. Questo dovrebbe funzionare anche con FragmentStatePagerAdapter; vedere le note per i dettagli.


Ecco un semplice esempio di come ottenere un riferimento al Fragmentsreso da FragmentPagerAdapterche non si basa sul tagsset interno sul Fragments. La chiave è quella di sovrascrivere instantiateItem()e salvare i riferimenti in lì anziché in 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();
        }
    }
}

o se preferisci lavorare con tagsle variabili / i riferimenti al membro della classe, Fragmentspuoi anche prendere l' tagsinsieme FragmentPagerAdapternello stesso modo: NOTA: questo non si applica FragmentStatePagerAdapterpoiché non si imposta tagsdurante la creazione Fragments.

@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;
}

Si noti che questo metodo NON si basa sull'imitazione tagdell'insieme interno FragmentPagerAdaptere utilizza invece le API appropriate per recuperarli. In questo modo anche se i tagcambiamenti nelle versioni future del SupportLibrarytuo saranno ancora al sicuro.


Non dimenticare che, a seconda del design del tuo Activity, su Fragmentscui stai cercando di lavorare potrebbe non esistere ancora, quindi devi tenerne conto facendo dei nullcontrolli prima di utilizzare i tuoi riferimenti.

Inoltre, se invece lavori con te FragmentStatePagerAdapter, allora non vuoi mantenere dei riferimenti duri ai tuoi Fragmentsperché potresti averne molti e riferimenti duri li manterrebbero inutilmente nella memoria. Salvare invece i Fragmentriferimenti in WeakReferencevariabili anziché in standard. Come questo:

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...
}

in mancanza di memoria Android potrebbe chiedere al FragmentManager di distruggere frammenti inutilizzati, giusto? Se ho ragione, la tua seconda versione funzionerà, la prima potrebbe fallire. (Non so se le attuali versioni di Android lo fanno, ma sembra che ci dobbiamo aspettare.)
Moritz Entrambi

1
che ne dite di creare un FragmetPagerAdapterin onCreate di attività su ogni rotazione dello schermo. È sbagliato perché può evitare il riutilizzo di frammenti già aggiunti inFragmentPagerAdapter
Bahman,

La sostituzione instantiateItem()è la strada da percorrere; questo mi ha aiutato a gestire le rotazioni dello schermo e a recuperare le mie istanze esistenti di frammenti una volta che l'attività e l'adattatore sono stati ripresi; Mi sono lasciato commenti nel codice come promemoria: dopo la rotazione, getItem()NON viene richiamato; instantiateItem()viene chiamato solo questo metodo . La super implementazione per instantiateItem()ricollegare effettivamente i frammenti dopo la rotazione (se necessario), invece di creare istanze di nuove istanze!
HelloImKevo,

Sto ottenendo il puntatore null Fragment createdFragment = (Fragment) super.instantiateItem..nella prima soluzione.
AlexS,

Dopo aver cercato tutte le varie iterazioni duplicate / varianti di questo problema, questa è stata la soluzione migliore. (Riferimenti incrociati da un'altra Q con un titolo più pertinente, quindi grazie per quello!)
MandisaW

18

Ho trovato un'altra soluzione relativamente semplice per la tua domanda.

Come puoi vedere dal codice sorgente FragmentPagerAdapter , i frammenti gestiti dall'archivio FragmentPagerAdapternel FragmentManagertag sotto generato generato usando:

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

The viewIdis the container.getId(), the containerè la tua ViewPageristanza. La indexè la posizione del frammento. Quindi è possibile salvare l'id oggetto in 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);

Se vuoi comunicare con questo frammento, puoi ottenere se da FragmentManager, come:

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

21
hmmm Non penso che questo sia un buon modo di procedere poiché la tua risposta su una convenzione interna di denominazione dei tag che non rimarrà affatto la stessa per sempre.
Dori,

1
Per coloro che cercano una soluzione che non si basa sull'interno tag, prova a provare la mia risposta .
Tony Chan,

16

Voglio offrire una soluzione alternativa per un caso forse leggermente diverso, poiché molte delle mie ricerche di risposte mi hanno portato a questo thread.

Il mio caso : sto creando / aggiungendo pagine dinamicamente e facendole scorrere in un ViewPager, ma quando ruotato (onConfigurationChange) finisco con una nuova pagina perché ovviamente OnCreate viene chiamato di nuovo. Ma voglio fare riferimento a tutte le pagine che sono state create prima della rotazione.

Problema : non ho identificatori univoci per ogni frammento che creo, quindi l'unico modo per fare riferimento era in qualche modo memorizzare i riferimenti in un array da ripristinare dopo la modifica di rotazione / configurazione.

Soluzione alternativa : il concetto chiave era che l'attività (che visualizza i frammenti) gestisse anche l'array di riferimenti ai frammenti esistenti, poiché questa attività può utilizzare i bundle in onSaveInstanceState

public class MainActivity extends FragmentActivity

Quindi, nell'ambito di questa attività, dichiaro un membro privato per tenere traccia delle pagine aperte

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

Questo viene aggiornato ogni volta che onSaveInstanceState viene chiamato e ripristinato in onCreate

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

... quindi una volta memorizzato, può essere recuperato ...

@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);
}

Queste erano le modifiche necessarie all'attività principale, e quindi avevo bisogno dei membri e dei metodi all'interno del mio FragmentPagerAdapter per farlo funzionare, quindi all'interno

public class ViewPagerAdapter extends FragmentPagerAdapter

un costrutto identico (come mostrato sopra in MainActivity)

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

e questa sincronizzazione (come usato sopra in onSaveInstanceState) è supportata specificamente dai metodi

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

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

E infine, nella classe dei frammenti

public class CustomFragment extends Fragment

affinché tutto ciò funzionasse, c'erano due cambiamenti, prima

public class CustomFragment extends Fragment implements Serializable

e poi aggiungendolo a onCreate in modo che i frammenti non vengano distrutti

setRetainInstance(true);

Sono ancora in procinto di avvolgere la mia testa attorno a Fragments e al ciclo di vita di Android, quindi un avvertimento qui potrebbe esserci ridondanze / inefficienze in questo metodo. Ma funziona per me e spero possa essere utile per gli altri con casi simili al mio.


2
+1 per setRetainInstance (true) - la magia nera che stavo cercando!
declinazione

È stato ancora più semplice usando FragmentStatePagerAdapter (v13). Che cosa ti riguarda con il ripristino e il rilascio dello stato.
The Butcher,

Descrizione chiara e soluzione molto bella. Grazie!
Bruno Bieri,

8

La mia soluzione è molto scortese ma funziona: essendo i miei frammenti creati dinamicamente dai dati conservati, rimuovo semplicemente tutti i frammenti dalla PageAdapterprima chiamata super.onSaveInstanceState()e quindi li ricrea sulla creazione di attività:

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

Non è possibile rimuoverli in onDestroy(), altrimenti si ottiene questa eccezione:

java.lang.IllegalStateException: Non è possibile eseguire questa azione dopo onSaveInstanceState

Ecco il codice nell'adattatore di pagina:

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

Salvo solo la pagina corrente e la ripristino onCreate()dopo che i frammenti sono stati creati.

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

Non so perché, ma notifyDataSetChanged (); provoca l'arresto anomalo dell'app
Malachiasz,

4

Che cos'è quello BasePagerAdapter? Dovresti usare uno degli adattatori per cercapersone standard - FragmentPagerAdaptero FragmentStatePagerAdapter, o , a seconda che tu voglia frammenti che non sono più necessari ViewPagerper essere tenuti in giro (il primo) o per salvare il loro stato (il secondo) e ricreare se necessario di nuovo.

Codice di esempio per l'utilizzo ViewPagerpuò essere trovato qui

È vero che la gestione dei frammenti in un pager della vista tra istanze di attività è un po 'complicata, poiché FragmentManagernel framework si occupa di salvare lo stato e ripristinare eventuali frammenti attivi che il pager ha creato. Tutto ciò significa davvero che l'adattatore durante l'inizializzazione deve assicurarsi di riconnettersi con qualsiasi frammento ripristinato ci sia. Puoi guardare il codice per FragmentPagerAdaptero FragmentStatePagerAdaptervedere come è fatto.


1
Il codice BasePagerAdapter è disponibile nella mia domanda. Come si può vedere, estende semplicemente FragmentPagerAdapter allo scopo di implementare TitleProvider . Quindi tutto ha già funzionato con l'approccio Android Dev suggerito.
Oleksii Malovanyi,

Non ero a conoscenza di "FragmentStatePagerAdapter". Mi hai salvato letteralmente ore. Grazie.
Cnosso,

2

Se qualcuno sta riscontrando problemi con FragmentStatePagerAdapter che non ripristina correttamente lo stato dei suoi frammenti ... cioè ... nuovi frammenti vengono creati da FragmentStatePagerAdapter anziché ripristinarli dallo stato ...

Assicurati di chiamare ViewPager.setOffscreenPageLimit()PRIMA di chiamareViewPager.setAdapter(fragmentStatePagerAdapter)

Al momento della chiamata ViewPager.setOffscreenPageLimit()... ViewPager cercherà immediatamente il suo adattatore e cercherà di ottenere i suoi frammenti. Ciò può accadere prima che ViewPager abbia la possibilità di ripristinare i frammenti da saveInstanceState (creando così nuovi frammenti che non possono essere reinizializzati da SavedInstanceState perché sono nuovi).


0

Ho trovato questa soluzione semplice ed elegante. Presuppone che l'attività sia responsabile della creazione dei frammenti e che l'adapter li serva.

Questo è il codice dell'adattatore (niente di strano qui, tranne per il fatto che mFragmentsè un elenco di frammenti gestiti dall'attività)

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();
    }
} 

L'intero problema di questo thread è ottenere un riferimento ai frammenti "vecchi", quindi uso questo codice in onCreate dell'attività.

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

Naturalmente è possibile ottimizzare ulteriormente questo codice, se necessario, ad esempio assicurandosi che i frammenti siano istanze di una particolare classe.


0

Per ottenere i frammenti dopo il cambio di orientamento devi usare .getTag ().

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

Per un po 'più di gestione ho scritto la mia ArrayList per il mio PageAdapter per ottenere il frammento di viewPagerId e FragmentClass in qualsiasi posizione:

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;
    }
}

Quindi basta creare una MyPageArrayList con i frammenti:

    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()));

e aggiungerli al viewPager:

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

dopo questo puoi ottenere dopo l'orientamento cambiare il frammento corretto usando la sua classe:

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

-30

Inserisci:

   @SuppressLint("ValidFragment")

prima della lezione.

non funziona fare qualcosa del genere:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.