Parçaların örnek durumunu doğru yığın halinde nasıl kaydedebilirim?


489

SO hakkında benzer bir sorunun birçok örneğini buldum, ancak hiçbir cevap maalesef gereksinimlerimi karşılamıyor.

Portre ve manzara için farklı mizanpajlarım var ve arka plan yığınını setRetainState()kullanıyorum.

Varsayılan işleyiciye kaydedilmemiş olan TextViews'ta kullanıcıya belirli bilgileri gösteririm. Başvurumu yalnızca Etkinlikler kullanarak yazarken aşağıdakiler iyi çalıştı:

TextView vstup;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.whatever);
    vstup = (TextView)findViewById(R.id.whatever);
    /* (...) */
}

@Override
public void onSaveInstanceState(Bundle state) {
    super.onSaveInstanceState(state);
    state.putCharSequence(App.VSTUP, vstup.getText());
}

@Override
public void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);
    vstup.setText(state.getCharSequence(App.VSTUP));
}

İle Fragments, bu sadece çok özel durumlarda çalışır. Özellikle, korkunç bir şekilde kırılan bir parçayı değiştirmek, arka yığını içine koymak ve daha sonra yeni parça gösterilirken ekranı döndürmektir. Anladığım kadarıyla, eski parça onSaveInstanceState()değiştirilirken bir çağrı almaz, ancak bir şekilde bağlantılı kalır Activityve Viewartık mevcut olmadığında bu yöntem daha sonra çağrılır , bu yüzden TextViews sonuçlarından herhangi birini arıyorum NullPointerException.

Ayrıca, benim referans referans tutmak 's ile Tamam olsa bile TextViews, Fragments ile iyi bir fikir olmadığını buldum Activity. Bu durumda, onSaveInstanceState()aslında durumu kaydeder, ancak parça gizlendiğinde ekranı iki kez döndürürsem onCreateView()yeni örnekte çağrılmadığı için sorun yeniden ortaya çıkar .

Ben koruma durumudur düşünce onDestroyView()bazı içine Bundle(aslında daha fazla veri değil, sadece biri tipi sınıf üyesi elemanı TextViewve tasarrufu) o içinde onSaveInstanceState()ancak diğer dezavantajları vardır. Öncelikle, parça şu anda gösteriliyorsa, iki işlevi çağırmanın sırası tersine çevrilir, bu yüzden iki farklı durumu hesaba katmam gerekir. Daha temiz ve doğru bir çözüm olmalı!


1
İşte detay açıklamalarıyla birlikte çok iyi bir örnek. emuneee.com/blog/2013/01/07/saving-fragment-states
Hesam

1
Emunee.com bağlantısını ikinci olarak kullandım. Benim için bir sopa UI sorunu çözdü!
Reenactor Rob

Yanıtlar:


541

Örnek durumunu doğru bir şekilde kaydetmek Fragmentiçin aşağıdakileri yapmanız gerekir:

1. Parçada, geçersiz kılınarak örnek durumunu kaydedin onSaveInstanceState()ve şuraya geri yükleyin onActivityCreated():

class MyFragment extends Fragment {

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's state here
        }
    }
    ...
    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        //Save the fragment's state here
    }

}

2. Ve önemli nokta , aktivitede, parçanın örneğini kaydetmeniz onSaveInstanceState()ve geri yüklemeniz gerekir onCreate().

class MyActivity extends Activity {

    private MyFragment 

    public void onCreate(Bundle savedInstanceState) {
        ...
        if (savedInstanceState != null) {
            //Restore the fragment's instance
            mMyFragment = getSupportFragmentManager().getFragment(savedInstanceState, "myFragmentName");
            ...
        }
        ...
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        //Save the fragment's instance
        getSupportFragmentManager().putFragment(outState, "myFragmentName", mMyFragment);
    }

}

Bu yardımcı olur umarım.


7
Bu benim için mükemmel çalıştı! Geçici çözüm yok, hack yok, bu şekilde mantıklı geliyor. Bunun için teşekkürler, saatler süren arama başarılı oldu. SaveInstanceState () değerini parça olarak kaydedin, ardından parçayı tutarak Etkinlik'e parçayı kaydedin, sonra geri yükleyin :)
MattMatt

77
MContent nedir?
wizurd

14
@wizurd mContent bir Parçadır, etkinlikteki geçerli parçanın örneğine başvurur.
ThanhHH

13
Bunun, bir parçanın örnek durumunu arka yığını nasıl kaydedeceğini açıklayabilir misiniz? OP sordu.
hitmaneidos

51
Bu soru ile ilgili değildir, fragman backstack'e konulduğunda onSaveInstance çağrılmaz
Tadas Valaitis

87

Şu anda bu şekilde kullanıyorum ... çok karmaşık ama en azından olası tüm durumları hallediyor. Herkes ilgileniyorsa.

public final class MyFragment extends Fragment {
    private TextView vstup;
    private Bundle savedState = null;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.whatever, null);
        vstup = (TextView)v.findViewById(R.id.whatever);

        /* (...) */

        /* If the Fragment was destroyed inbetween (screen rotation), we need to recover the savedState first */
        /* However, if it was not, it stays in the instance from the last onDestroyView() and we don't want to overwrite it */
        if(savedInstanceState != null && savedState == null) {
            savedState = savedInstanceState.getBundle(App.STAV);
        }
        if(savedState != null) {
            vstup.setText(savedState.getCharSequence(App.VSTUP));
        }
        savedState = null;

        return v;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        savedState = saveState(); /* vstup defined here for sure */
        vstup = null;
    }

    private Bundle saveState() { /* called either from onDestroyView() or onSaveInstanceState() */
        Bundle state = new Bundle();
        state.putCharSequence(App.VSTUP, vstup.getText());
        return state;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /* If onDestroyView() is called first, we can use the previously savedState but we can't call saveState() anymore */
        /* If onSaveInstanceState() is called first, we don't have savedState, so we need to call saveState() */
        /* => (?:) operator inevitable! */
        outState.putBundle(App.STAV, (savedState != null) ? savedState : saveState());
    }

    /* (...) */

}

Alternatif olarak , her zaman pasif Views'de görüntülenen verilerin değişkenlerde Viewtutulması ve s'nin sadece görüntülenmesi için kullanılması, iki şeyin senkronize tutulması da mümkündür. Yine de son kısmı çok temiz saymıyorum.


68
Eğer iki fragman varsa, Bu şimdiye kadar bulduğum en iyi çözümdür ancak kalan (biraz egzotik) Sorun hala bir tane var Ave Bnerede Abackstack şu anda ve Bo zaman durumu görünür kaybetmek A(görünmez bir) ekranı iki kez döndürürseniz . Sorun onCreateView()sadece bu senaryoda çağrılmamasıdır onCreate(). Böylece daha sonra, onSaveInstanceState()devleti kurtaracak görüş yok. Kişinin, geçtiği devleti depolaması ve kaydetmesi gerekir onCreate().
devconsole

7
@devconsole Keşke bu yorum için size 5 oy verebilir! Bu rotasyonun iki katı beni günlerdir öldürüyor.
DroidT

Harika cevap için teşekkürler! Yine de bir sorum var. Bu parçada model nesnesini (POJO) somutlaştırmak için en iyi yer neresi?
15:15, Renjith

7
Diğerleri için zaman kazanmanıza yardımcı olmak App.VSTUPve App.STAVher ikisi de elde etmeye çalıştıkları nesneleri temsil eden dize etiketleridir. Örnek: savedState = savedInstanceState.getBundle(savedGamePlayString);veyasavedState.getDouble("averageTime")
Tanner Hallman

1
Bu bir güzellik meselesi.
Ivan

61

En son destek kütüphanesinde, burada tartışılan çözümlerin hiçbiri artık gerekli değildir. Activity'Nin parçalarını istediğiniz gibi kullanarak oynayabilirsiniz FragmentTransaction. Parçalarınızın bir kimlik veya etiketle tanımlanabildiğinden emin olun.

Her çağrıda onları yeniden oluşturmaya çalışmadığınız sürece parçalar otomatik olarak geri yüklenecektir onCreate(). Bunun yerine, savedInstanceStatenull olup olmadığını kontrol etmeli ve bu durumda oluşturulan parçalara eski referansları bulmalısınız.

İşte bir örnek:

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

    if (savedInstanceState == null) {
        myFragment = MyFragment.newInstance();
        getSupportFragmentManager()
                .beginTransaction()
                .add(R.id.my_container, myFragment, MY_FRAGMENT_TAG)
                .commit();
    } else {
        myFragment = (MyFragment) getSupportFragmentManager()
                .findFragmentByTag(MY_FRAGMENT_TAG);
    }
...
}

Ancak , bir parçanın gizli durumunu geri yüklerken şu anda bir hata olduğunu unutmayın . Etkinliğinizdeki fragmanları saklıyorsanız, bu durumda bu durumu manuel olarak geri yüklemeniz gerekir.


2
Bu düzeltme, destek kitaplığını kullanırken fark ettiğiniz bir şey mi yoksa bir yerde okudunuz mu? Bu konuda verebileceğiniz daha fazla bilgi var mı? Teşekkürler!
Piovezan

1
@Piovezan, dokümanlardan örtük bir şekilde çıkarılabilir. Örneğin, beginTransaction () dokümanı şu şekildedir: "Bunun nedeni, çerçevenin geçerli parçalarınızı eyalette (...) kaydetmeye özen göstermesidir". Ayrıca uygulamalarımı uzun süredir bu beklenen davranışla kodluyorum.
Ricardo

1
@ ViewPager kullanılıyorsa bu geçerli mi?
Derek Beattie

1
Normalde evet, FragmentPagerAdapterveya uygulamasında varsayılan davranışı değiştirmedikçe FragmentStatePagerAdapter. FragmentStatePagerAdapterÖrneğin, koduna bakarsanız , bağdaştırıcıyı oluştururken restoreState()yöntemin FragmentManager, parametre olarak geçirdiğiniz parçaları geri yüklediğini göreceksiniz .
Ricardo

4
Bence bu katkı asıl sorunun en iyi cevabı. Aynı zamanda - bence - en iyi Android platformunun nasıl çalıştığı ile uyumludur. Gelecekteki okuyuculara daha iyi yardımcı olmak için bu cevabı "Kabul Edildi" olarak işaretlemenizi tavsiye ederim .
dbm

17

Vasek ve devconsole'dan türettiğim bu yazıda sunulan tüm vakaları ele alan bir çözüm vermek istiyorum. Bu çözüm, parçalar görünmezken telefon birden fazla döndürüldüğünde de özel durumu ele alır.

OnCreate ve onSaveInstanceState parçasını görünür olmadığında yapılan tek çağrılar olduğu için paketi daha sonra kullanmak üzere sakladım

MyObject myObject;
private Bundle savedState = null;
private boolean createdStateInDestroyView;
private static final String SAVED_BUNDLE_TAG = "saved_bundle";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        savedState = savedInstanceState.getBundle(SAVED_BUNDLE_TAG);
    }
}

DestroyView özel döndürme durumunda çağrılmadığından, durumu oluşturuyorsa kullanmamız gerektiğinden emin olabiliriz.

@Override
public void onDestroyView() {
    super.onDestroyView();
    savedState = saveState();
    createdStateInDestroyView = true;
    myObject = null;
}

Bu kısım aynı olurdu.

private Bundle saveState() { 
    Bundle state = new Bundle();
    state.putSerializable(SAVED_BUNDLE_TAG, myObject);
    return state;
}

Şimdi burada zor kısmıdır. Benim onActivityCreated yönteminde "myObject" değişkenini somutlaştırıyorum ancak döndürme onActivity ve onCreateView çağrılmıyor. Bu nedenle, yönlendirme bir kereden fazla döndüğünde myObject boş olacaktır. Ben dışarı giden paket olarak onCreate kaydedilen aynı paketi yeniden kullanarak bu sorunu çözmek.

    @Override
public void onSaveInstanceState(Bundle outState) {

    if (myObject == null) {
        outState.putBundle(SAVED_BUNDLE_TAG, savedState);
    } else {
        outState.putBundle(SAVED_BUNDLE_TAG, createdStateInDestroyView ? savedState : saveState());
    }
    createdStateInDestroyView = false;
    super.onSaveInstanceState(outState);
}

Şimdi durumu geri yüklemek istediğiniz her yerde sadece savedState paketini kullanın

  @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    if(savedState != null) {
        myObject = (MyObject) savedState.getSerializable(SAVED_BUNDLE_TAG);
    }
    ...
}

Bana söyleyebilir misiniz ... Burada "MyObject" nedir?
kavie

2
Olmasını istediğiniz her şey. Bu sadece pakete kaydedilecek bir şeyi temsil eden bir örnektir.
DroidT

3

DroidT sayesinde , bunu yaptı:

Fragment onCreateView () yürütmezse, görünümünün somut olmadığını anlıyorum. Bu nedenle, arka yığındaki parça görüşlerini oluşturmadıysa, son saklanan durumu kaydederim, aksi takdirde kaydetmek / geri yüklemek istediğim verilerle kendi paketimi oluştururum.

1) Bu sınıfı genişlet:

import android.os.Bundle;
import android.support.v4.app.Fragment;

public abstract class StatefulFragment extends Fragment {

    private Bundle savedState;
    private boolean saved;
    private static final String _FRAGMENT_STATE = "FRAGMENT_STATE";

    @Override
    public void onSaveInstanceState(Bundle state) {
        if (getView() == null) {
            state.putBundle(_FRAGMENT_STATE, savedState);
        } else {
            Bundle bundle = saved ? savedState : getStateToSave();

            state.putBundle(_FRAGMENT_STATE, bundle);
        }

        saved = false;

        super.onSaveInstanceState(state);
    }

    @Override
    public void onCreate(Bundle state) {
        super.onCreate(state);

        if (state != null) {
            savedState = state.getBundle(_FRAGMENT_STATE);
        }
    }

    @Override
    public void onDestroyView() {
        savedState = getStateToSave();
        saved = true;

        super.onDestroyView();
    }

    protected Bundle getSavedState() {
        return savedState;
    }

    protected abstract boolean hasSavedState();

    protected abstract Bundle getStateToSave();

}

2) Parçanızda şunlara sahip olmalısınız:

@Override
protected boolean hasSavedState() {
    Bundle state = getSavedState();

    if (state == null) {
        return false;
    }

    //restore your data here

    return true;
}

3) Örneğin, onActivityCreated'da hasSavedState öğesini çağırabilirsiniz:

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

    if (hasSavedState()) {
        return;
    }

    //your code here
}

-6
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.hide(currentFragment);
ft.add(R.id.content_frame, newFragment.newInstance(context), "Profile");
ft.addToBackStack(null);
ft.commit();

1
Orijinal soru ile ilgili değil.
Jorge E. Hernández
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.