support FragmentPagerAdapter contiene riferimenti a vecchi frammenti


109

ULTIME INFORMAZIONI:

ho ridotto il mio problema a un problema con il fragmentManager che conserva istanze di vecchi frammenti e il mio viewpager non è sincronizzato con il mio FragmentManager. Visualizza questo problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Non ho ancora idea di come risolverlo. Eventuali suggerimenti...

Ho provato a eseguire il debug di questo per molto tempo e qualsiasi aiuto sarebbe molto apprezzato. Sto usando un FragmentPagerAdapter che accetta un elenco di frammenti in questo modo:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

L'implementazione è standard. Sto usando ActionBarSherlock e la libreria di computabilità v4 per i frammenti.

Il mio problema è che dopo aver lasciato l'app e aver aperto diverse altre applicazioni e tornato indietro, i frammenti perdono il riferimento a FragmentActivity (cioè getActivity() == null). Non riesco a capire perché sta accadendo. Ho provato a impostare manualmente setRetainInstance(true);ma questo non aiuta. Ho pensato che ciò accada quando la mia FragmentActivity viene distrutta, tuttavia ciò accade ancora se apro l'app prima di ricevere il messaggio di registro. Ci sono idee?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

L'adattatore:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Uno dei miei frammenti è stato spogliato, ma ho comunicato tutto ciò che è stato spogliato e ancora non funziona ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


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

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

Il metodo update viene chiamato quando un utente interagisce con un frammento diverso e getActivity restituisce null. Ecco il metodo che l'altro frammento sta chiamando ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Credo che quando l'app viene distrutta, quindi creata di nuovo invece di avviare l'app fino al frammento predefinito, l'app si avvia e quindi viewpager si sposta all'ultima pagina nota. Sembra strano, l'app non dovrebbe semplicemente caricarsi nel frammento predefinito?


27
Il frammento di Android fa schifo!
Hamidreza Sadegh

13
Dio, amo i tuoi messaggi di registro: D
oli.G

Risposte:


120

Stai riscontrando un problema perché stai creando un'istanza e conservando i riferimenti ai tuoi frammenti al di fuori di PagerAdapter.getIteme stai tentando di utilizzare quei riferimenti indipendentemente dal ViewPager. Come dice Seraph, hai la garanzia che un frammento è stato istanziato / aggiunto in un ViewPager in un determinato momento - questo dovrebbe essere considerato un dettaglio di implementazione. Un ViewPager esegue il caricamento lento delle sue pagine; per impostazione predefinita carica solo la pagina corrente e quella a sinistra ea destra.

Se metti la tua app in background, i frammenti che sono stati aggiunti al gestore dei frammenti vengono salvati automaticamente. Anche se la tua app viene interrotta, queste informazioni vengono ripristinate quando riavvii l'app.

Considera ora di aver visualizzato alcune pagine, i frammenti A, B e C. Sai che questi sono stati aggiunti al gestore dei frammenti. Poiché stai utilizzando FragmentPagerAdaptere non FragmentStatePagerAdapter, questi frammenti verranno comunque aggiunti (ma potenzialmente scollegati) quando scorri verso altre pagine.

Considera che poi fai in background la tua applicazione, e poi viene uccisa. Quando torni, Android ricorderà che avevi i frammenti A, B e C nel gestore dei frammenti e quindi li ricrea per te e poi li aggiunge. Tuttavia, quelli che vengono aggiunti al gestore dei frammenti ora NON sono quelli presenti nell'elenco dei frammenti nella tua attività.

Il FragmentPagerAdapter non tenterà di chiamare getPositionse è già stato aggiunto un frammento per quella particolare posizione della pagina. Infatti, poiché il frammento ricreato da Android non verrà mai rimosso, non hai speranza di sostituirlo con una chiamata a getPosition. È anche piuttosto difficile ottenere un riferimento ad esso perché è stato aggiunto con un tag a te sconosciuto. Questo è di progettazione; sei scoraggiato dal fare confusione con i frammenti che il cercapersone sta gestendo. Dovresti eseguire tutte le tue azioni all'interno di un frammento, comunicare con l'attività e richiedere di passare a una determinata pagina, se necessario.

Ora, torniamo al tuo problema con l'attività mancante. Chiamare pagerAdapter.getItem(1)).update(id, name)dopo che tutto ciò è accaduto restituisce il frammento nell'elenco, che deve ancora essere aggiunto al gestore dei frammenti , e quindi non avrà un riferimento all'attività. Suggerirei che il tuo metodo di aggiornamento dovrebbe modificare una struttura dati condivisa (possibilmente gestita dall'attività), e quindi quando ti sposti su una pagina particolare può disegnare se stesso in base a questi dati aggiornati.


1
Mi piace la tua soluzione poiché è molto elegante e probabilmente eseguirà il refactoring del mio codice, tuttavia come hai detto volevo mantenere il 100% della logica e dei dati all'interno del frammento. La tua soluzione richiederebbe praticamente la conservazione di tutti i dati in FragmentActivity e quindi utilizzare ogni frammento semplicemente per gestire la logica di visualizzazione. Come ho detto lo preferisco ma l'interazione tra i frammenti è super pesante e potrebbe diventare fastidiosa da gestire. Comunque grazie per la tua spiegazione dettagliata. L'hai spiegato molto meglio di quanto potessi.
Maurycy

28
Breve: non tenere mai un riferimento a un frammento al di fuori dell'adattatore
passa il

2
In che modo un'istanza di Activity accede all'istanza di FragmentPagerAdapter se non ha creato un'istanza di FragmentPagerAdapter? Le istanze future non ripristineranno semplicemente l'istanza di FragmentPagerAdapter e tutte le sue istanze di frammento? Il FragmentPagerAdapter dovrebbe implementare tutte le interfacce dei frammenti per gestire la comunicazione tra i frammenti?
Eric H.

l'affermazione "È anche piuttosto difficile ottenere un riferimento ad esso perché è stato aggiunto con un tag che ti è sconosciuto. Questo è per impostazione predefinita; sei scoraggiato dal fare confusione con i frammenti che il cercapersone sta gestendo ". è falso. è molto facile ottenere una referenza telefonando instantiateIteme in realtà dovresti farlo nella onCreatetua attività. vedi i dettagli qui: stackoverflow.com/questions/14035090/...
morgwai

in generale è rischioso istanziare i frammenti da soli senza caricarli perché in uno scenario "distruggi e ricrea" potresti finire per gestire un frammento separato da quello effettivamente ricreato e mostrato
hmac

108

Ho trovato una soluzione semplice che ha funzionato per me.

Fai in modo che l'adattatore per i frammenti estenda FragmentStatePagerAdapter invece di FragmentPagerAdapter e sovrascrivi il metodo onSave per restituire null

@Override
public Parcelable saveState()
{
    return null;
}

Ciò impedisce ad Android di ricreare il frammento


Un giorno dopo ho trovato un'altra e migliore soluzione.

Chiama setRetainInstance(true)per tutti i tuoi frammenti e salva i riferimenti ad essi da qualche parte. L'ho fatto in variabile statica nella mia attività, perché è dichiarato come singleTask e frammenti possono rimanere gli stessi tutto il tempo.

In questo modo Android non ricrea i frammenti ma usa le stesse istanze.


4
Grazie, grazie, grazie mille, davvero non ho parole per ringraziarti Mik, stavo inseguendo questo problema negli ultimi 10 giorni e ho provato tanti metodi, ma queste quattro linee magiche mi hanno salvato la vita :)

7
avere un riferimento statico a frammenti e / o attività è una cosa molto rischiosa, poiché può causare facilmente perdite di memoria. Ovviamente, se stai attento, puoi gestirlo abbastanza facilmente impostandoli su null quando non sono più necessari.
sviluppatore Android

Questo ha funzionato per me. I frammenti e le rispettive visualizzazioni mantengono i collegamenti dopo l'arresto anomalo e il riavvio dell'app. Grazie!
Swebal

Sto usando setRetainInstance(true)con FragmentPagerAdapter. Tutto funziona bene. Ma quando ruoto il dispositivo, l'adattatore ha ancora i frammenti ma i frammenti non vengono visualizzati. Anche i metodi del ciclo di vita dei frammenti non vengono chiamati. Qualcuno può aiutare?
Jonas

1
mi hai salvato la giornata !!! Finora ho cercato questo bug per 3 giorni. Avevo un Viewpager con 2 frammenti all'interno di un'attività SingleTask e con il flag "Non mantenere le attività" abilitato. Molte grazie!!!!
matrice

29

Ho risolto questo problema accedendo ai miei frammenti direttamente tramite FragmentManager invece che tramite FragmentPagerAdapter in questo modo. Per prima cosa devo capire il tag del frammento generato automaticamente dal FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Quindi ottengo semplicemente un riferimento a quel frammento e faccio quello che mi serve così ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

All'interno dei miei frammenti ho impostato in setRetainInstance(false);modo da poter aggiungere manualmente valori al bundle savedInstanceState.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

e poi in OnCreate prendo quella chiave e ripristino lo stato del frammento se necessario. Una soluzione facile che era difficile (almeno per me) da capire.


Ho un problema simile al tuo, ma non ho capito bene la tua spiegazione, puoi fornire maggiori dettagli? Ho anche un adattatore che memorizza i frammenti nell'elenco, ma quando riprendo la mia app dalle app recenti le app si bloccano perché c'è un frammento staccato. Il problema è qui stackoverflow.com/questions/11631408/...
Georgy Gobozov

3
Qual è il tuo "mio" nel riferimento al frammento?
Josh

Sappiamo tutti che questa soluzione non è un buon approccio, ma è il modo più semplice da utilizzare FragmentByTagin ViewPager.
Youngjae,

ecco un modo come frammenti di accesso senza fare affidamento sulla compatibilità con il modo interno di assegnare tag: stackoverflow.com/questions/14035090/...
morgwai

26

Soluzione globale testata.

getSupportFragmentManager()mantiene il riferimento nullo alcune volte e View pager non ne crea di nuovo poiché trova il riferimento allo stesso frammento. Quindi, questo uso getChildFragmentManager()risolve il problema in modo semplice.

Non farlo:

new PagerAdapter(getSupportFragmentManager(), fragments);

Fai questo:

new PagerAdapter(getChildFragmentManager() , fragments);


6
questo sarebbe possibile solo se stai ospitando (e istanziando) il pagerAdapter all'interno di un Fragment e non all'interno della tua attività. In questo caso hai ragione, dovresti usare il childFragmentManager per impostazione predefinita. L'uso del SupportFragmentManager sarebbe sbagliato per impostazione predefinita
Klitos G.

Molto preciso. Ho risolto il mio problema senza usare gli hack. Grazie! La mia versione di Kotlin è passata da FragmentStatePagerAdapter(activity!!.supportFragmentManager)una più facile da guardare FragmentStatePagerAdapter(childFragmentManager):)
ecth

7

Non tentare di interagire tra i frammenti in ViewPager. Non puoi garantire che un altro frammento sia allegato o addirittura esista. Invece di cambiare il titolo della barra delle azioni da frammento, puoi farlo dalla tua attività. Usa il modello di interfaccia standard per questo:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}

Sicuramente includerà il codice ora ... Sto già registrando quei metodi. OnDetach viene chiamato quando onStop viene chiamato in fragmentActivity. onAttach viene chiamato appena prima di onCreate di FragmentActivity.
Maurycy

Hmm grazie ma il problema persiste ancora. Ho invece impostato il nome come richiamata. Non posso mettere la logica nel frammento? Ho i miei frammenti che attivano servizi e altri elementi che richiedono un riferimento all'attività. Dovrei spostare tutta la logica delle mie applicazioni nell'attività principale per evitare il problema che sto avendo. Questo sembra un po 'inutile.
Maurycy

Ok, quindi l'ho testato completamente e ho commentato tutto il mio codice ... tranne la richiamata per cambiare il titolo. Quando l'app viene avviata per la prima volta e mi sposto su quella schermata, il nome del titolo viene cambiato np. Dopo aver caricato diverse applicazioni, il titolo non cambia più. Sembra che la richiamata sia stata ricevuta su una vecchia istanza di attività. Non riesco a pensare a un'altra spiegazione
Maurycy

ho ridotto questo a un problema con il fragmentManager e questo problema ... code.google.com/p/android/issues/detail?id=19211 . Non ho ancora idea di come risolvere questo problema
Maurycy

5

Puoi rimuovere i frammenti quando distruggi il viewpager, nel mio caso, li ho rimossi dal onDestroyView()mio frammento:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}

Grazie, la tua soluzione funziona. È necessario che ViewPagersia basato anche su childFragmentManageradattatore (non fragmentManager). Anche un'altra variante funziona: non utilizzare onDestroyView, ma rimuovere i frammenti figlio prima della ViewPagercreazione dell'adattatore.
CoolMind

4

Dopo alcune ore di ricerca di un problema simile, penso di avere un'altra soluzione. Questo almeno ha funzionato per me e devo solo cambiare un paio di righe.

Questo è il problema che ho avuto, ho un'attività con un cercapersone di visualizzazione che utilizza un FragmentStatePagerAdapter con due frammenti. Tutto funziona bene finché non forzo la distruzione dell'attività (opzioni sviluppatore) o ruoto lo schermo. Mantengo un riferimento ai due frammenti dopo che sono stati creati all'interno del metodo getItem.

A quel punto l'attività verrà creata di nuovo e tutto funziona correttamente a questo punto, ma ho perso il riferimento ai miei frammenti poiché getItem non viene richiamato di nuovo.

Ecco come ho risolto il problema, all'interno di FragmentStatePagerAdapter:

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object aux = super.instantiateItem(container, position);

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

Non riceverai più una chiamata su getItem se l'adattatore ha già un riferimento ad esso internamente e non dovresti cambiarlo. Invece puoi ottenere il frammento che viene utilizzato guardando questo altro metodo instantiateItem () che verrà chiamato per ciascuno dei tuoi frammenti.

Spero che aiuti chiunque.


E se avessi molti frammenti? preferiresti davvero salvare un riferimento per ciascuno di essi? Non fa male alla memoria?
sviluppatore Android

Tutto dipende da cosa stai cercando di ottenere esattamente. Normalmente con questi cercapersone di visualizzazione non si conservano più di 3 frammenti alla volta. Quello al centro e quello su ogni lato. Per quello che voglio ho davvero bisogno di avere un riferimento ai frammenti, ma so che devo occuparmene solo di due.
Lancelot

0

Poiché FragmentManager si occuperà di ripristinare i tuoi frammenti per te non appena viene chiamato il metodo onResume (), ho il frammento chiamato all'attività e si aggiunge a un elenco. Nel mio caso sto memorizzando tutto questo nella mia implementazione PagerAdapter. Ogni frammento conosce la sua posizione perché viene aggiunto agli argomenti del frammento durante la creazione. Ora, ogni volta che ho bisogno di manipolare un frammento in un indice specifico, tutto ciò che devo fare è utilizzare l'elenco dal mio adattatore.

Di seguito è riportato un esempio di un adattatore per un ViewPager personalizzato che aumenterà il frammento man mano che si sposta nello stato attivo e lo ridimensiona quando si sposta fuori fuoco. Oltre alle classi Adapter e Fragment ho qui tutto ciò di cui hai bisogno è che l'attività genitore sia in grado di fare riferimento alla variabile dell'adattatore e sei a posto.

Adattatore

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

@Override
public int getCount() {
    return COUNT;
}

@Override
public Fragment getItem(int position) {
    return SummaryTabletFragment.newInstance(position);
}

@Override
public void onPageScrollStateChanged(int state) {}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Frammento

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

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

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}

0

La mia soluzione: ho impostato quasi tutte le visualizzazioni come static . Ora la mia app interagisce perfettamente. Essere in grado di chiamare i metodi statici da qualsiasi luogo forse non è un buon stile, ma perché giocare con codice che non funziona? Ho letto molte domande e le loro risposte qui su SO e nessuna soluzione ha portato al successo (per me).

So che può perdere la memoria e sprecare un mucchio di rifiuti, e il mio codice non sarà adatto ad altri progetti, ma non ho paura di questo: ho testato l'app su diversi dispositivi e condizioni, nessun problema, Android La piattaforma sembra essere in grado di gestirlo. L'interfaccia utente viene aggiornata ogni secondo e anche su un dispositivo S2 ICS (4.0.3) l'app è in grado di gestire migliaia di geo-marker.


0

Ho affrontato lo stesso problema ma il mio ViewPager era all'interno di un TopFragment che ha creato e impostato un adattatore utilizzando setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

Ho risolto questo problema onAttachFragment(Fragment childFragment)eseguendo l' override in TopFragment in questo modo:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Come già noto (vedi risposte sopra), quando il childFragmentManager si ricrea, crea anche i frammenti che erano all'interno del viewPager.
La parte importante è che, dopo di ciò, chiama onAttachFragment e ora abbiamo un riferimento al nuovo frammento ricreato!

Spero che questo aiuterà chiunque a ottenere questo vecchio Q come me :)


0

Ho risolto il problema salvando i frammenti in SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}

0

Solo così sai ...

In aggiunta alla litania dei guai con queste classi, c'è un bug piuttosto interessante che vale la pena condividere.

Sto usando un ViewPager per navigare in un albero di elementi (seleziona un elemento e il cercapersone della vista si anima scorrendo verso destra e viene visualizzato il ramo successivo, torna indietro e il ViewPager scorre nella direzione opposta per tornare al nodo precedente) .

Il problema sorge quando spingo e inserisco i frammenti fuori dalla fine del FragmentStatePagerAdapter. È abbastanza intelligente da notare che gli elementi cambiano e abbastanza intelligente da creare e sostituire un frammento quando l'elemento è cambiato. Ma non abbastanza intelligente da scartare lo stato del frammento o abbastanza intelligente da tagliare gli stati del frammento salvati internamente quando la dimensione dell'adattatore cambia. Quindi, quando si apre un oggetto e si inserisce uno nuovo alla fine, il frammento del nuovo oggetto ottiene lo stato salvato del frammento per il vecchio oggetto, il che ha causato il caos assoluto nel mio codice. I miei frammenti trasportano dati che potrebbero richiedere molto lavoro per essere recuperati da Internet, quindi non salvare lo stato non era davvero un'opzione.

Non ho una soluzione pulita. Ho usato qualcosa del genere:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Una soluzione imperfetta perché la nuova istanza di frammento riceve ancora un pacchetto savedState, ma almeno non trasporta dati obsoleti.


0

Poiché le persone non tendono a leggere i commenti, ecco una risposta che per lo più duplica ciò che ho scritto qui :

la causa principale del problema è il fatto che il sistema Android non chiama getItemper ottenere frammenti effettivamente visualizzati, ma instantiateItem. Questo metodo prova prima a cercare e riutilizzare un'istanza di frammento per una determinata scheda in FragmentManager. Solo se questa ricerca fallisce (cosa che accade solo la prima volta quando FragmentManagerviene creata di recente), getItemviene chiamata. È per ovvi motivi non ricreare frammenti (che possono essere pesanti) ad esempio ogni volta che un utente ruota il proprio dispositivo.
Per risolvere questo problema, invece di creare frammenti Fragment.instantiatenella tua attività, dovresti farlo con pagerAdapter.instantiateIteme tutte queste chiamate dovrebbero essere circondate da startUpdate/finishUpdatechiamate di metodo che rispettivamente avviano / eseguono la transazione di frammento.getItem dovrebbe essere il luogo in cui i frammenti vengono realmente creati utilizzando i rispettivi costruttori.

List<Fragment> fragments = new Vector<Fragment>();

@Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        adapter.finishUpdate(viewPager);
}

class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

        @Override public int getCount() {return 2;}

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
}
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.