android fragment- Cara menyimpan status tampilan dalam sebuah fragmen saat fragmen lain didorong ke atasnya

138

Di android, fragmen (katakanlah FragA) ditambahkan ke backstack dan fragmen lain (katakanlah FragB) muncul di atas. Sekarang memukul kembali FragAdatang ke atas dan onCreateView()dipanggil. Sekarang saya berada FragAdalam kondisi tertentu sebelum FragBdidorong ke atasnya.

Pertanyaan saya adalah bagaimana cara mengembalikan FragAke keadaan sebelumnya? Apakah ada cara untuk menyimpan status (seperti katakan dalam Bundle) dan jika demikian, metode mana yang harus saya ganti?

pankajagarwal
sumber

Jawaban:

98

Dalam panduan fragmen, contoh FragmentList Anda dapat menemukan:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Yang bisa Anda gunakan nanti seperti ini:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Saya seorang pemula dalam Fragmen tetapi sepertinya solusi dari masalah Anda;) OnActivityCreated dipanggil setelah fragmen kembali dari back-stack.

ania
sumber
19
Saya tidak bisa mendapatkan ini untuk bekerja, disimpanInstanceState selalu nol. Saya menambahkan fragmen melalui layout xml. Harus mengubah mCurCheckPosition menjadi statis kemudian berfungsi, tetapi terasa hacky.
scottyab
58
itu tidak memanggil onSaveInstanceState - mengapa demikian? Jadi, pendekatan ini tidak berhasil.
tengah malam
10
Akankah pendekatan ini benar-benar berfungsi jika kita ingin mempertahankan status fragmen saat kembali dari fragmen lain dalam Aktivitas yang sama? onSaveInstanceState () dipanggil hanya pada peristiwa Aktivitas onPause / onStop. Menurut dokumentasi: "Juga seperti aktivitas, Anda dapat mempertahankan status fragmen menggunakan Bundle, jika proses aktivitas dimatikan dan Anda perlu memulihkan status fragmen saat aktivitas dibuat ulang. Anda dapat menyimpan status selama callback onSaveInstanceState () fragmen dan memulihkannya selama onCreate (), onCreateView (), atau onActivityCreated (). "
Paramvir Singh
28
Sebagai catatan, pendekatan ini salah dan seharusnya tidak mendekati perolehan suara terbanyak. onSaveInstanceStatehanya dipanggil jika aktivitas yang sesuai juga dimatikan.
Martin Konecny
17
onSaveInstanceState () dipanggil onle ketika perubahan konfigurasi terjadi dan aktivitas dimusnahkan, jawaban ini salah
Tadas Valaitis
84

Fragmen onSaveInstanceState(Bundle outState)tidak akan pernah dipanggil kecuali aktivitas fragmen memanggilnya sendiri dan fragmen yang dilampirkan. Jadi metode ini tidak akan dipanggil hingga sesuatu (biasanya rotasi) memaksa aktivitas SaveInstanceStatedan memulihkannya nanti. Namun jika Anda hanya memiliki satu aktivitas dan sekumpulan besar fragmen di dalamnya (dengan penggunaan intensif replace) dan aplikasi hanya berjalan dalam satu orientasi, aktivitas onSaveInstanceState(Bundle outState)mungkin tidak akan dipanggil untuk waktu yang lama.

Saya tahu tiga solusi yang mungkin.

Pertama:

gunakan argumen fragmen untuk menyimpan data penting:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

Cara kedua tetapi kurang saksama - pegang variabel dalam bentuk tunggal

Yang ketiga - jangan replace()memecah tetapi add()/ show()/ hide()mereka sebagai gantinya.

Fyodor Volchyok
sumber
3
Solusi terbaik saat Fragment.onSaveInstanceState()tidak pernah dipanggil. Simpan saja data Anda sendiri ke argumen, termasuk item dalam tampilan daftar atau hanya ID mereka (jika Anda memiliki pengelola data terpusat lainnya). Tidak perlu menyimpan posisi tampilan daftar - yang disimpan dan dipulihkan secara otomatis.
John Pang
1
Saya mencoba menggunakan contoh Anda di aplikasi saya, tetapi ini: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);selalu null. Apa masalahnya?
fragon
Gunakan fragmen getArguments()PASTI cara yang harus dilakukan, termasuk fragmen bertingkat dalam ViewPager. Saya menggunakan 1 aktivitas dan menukar banyak fragmen masuk / keluar, dan ini bekerja dengan sempurna. Berikut adalah tes sederhana bagi Anda untuk memeriksa solusi yang diusulkan: 1) beralih dari Fragmen A ke Fragmen B; 2) ubah orientasi perangkat dua kali; 3) tekan tombol kembali pada perangkat.
Andy H.
1
Saya mencoba pendekatan pertama tetapi tidak berhasil untuk saya. getArguments () selalu mengembalikan null. Ini juga masuk akal karena fragmen diganti dan di onCreate () Anda menyetel Bundle baru sehingga Bundle lama hilang. Apa yang saya lewatkan dan apakah saya salah?
Zvi
@Zvi, saya menulis kode ini 1,5 tahun yang lalu dan tidak mengingat semua detailnya, tetapi seingat saya, replace tidak membuat ulang fragmen, fragmen dibuat ulang hanya jika Anda telah membuat instance baru dari kode Anda. Dalam hal ini, jelas, konstruktor memanggil dan setArguments(new Bundle());menimpa Bundel lama. Jadi pastikan Anda membuat fragmen hanya sekali, lalu gunakan instance ini daripada membuat yang baru setiap saat.
Fyodor Volchyok
20

Perhatikan bahwa jika Anda bekerja dengan Fragmen menggunakan ViewPager, itu cukup mudah. Anda hanya perlu memanggil metode ini: setOffscreenPageLimit().

Sesuai dengan dokumen:

Setel jumlah halaman yang harus dipertahankan di salah satu sisi halaman saat ini dalam hierarki tampilan dalam kondisi menganggur. Halaman di luar batas ini akan dibuat ulang dari adaptor jika diperlukan.

Masalah serupa di sini

malam
sumber
5
Ini berbeda. setOffScreenPageLimit bertindak seperti cache (artinya berapa banyak halaman yang harus ditangani ViewPager pada saat tertentu) tetapi tidak digunakan untuk menyimpan status Fragmen.
Renaud Mathieu
Dalam kasus saya, ini berfungsi dengan setOffscreenPageLimit () - meskipun fragmen dihancurkan, status tampilan disimpan dan dipulihkan.
Davincho
Terima kasih, bantu saya juga.
Elijah
Bertahun-tahun kemudian dan ini masih sangat relevan. Meskipun tidak benar-benar menjawab pertanyaannya, ini menyelesaikan masalah
Supreme Dolphin
19

Cukup kembangkan View Anda sekali.

Contoh berikut:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

Lym Zoy
sumber
juga harus menyimpan fragmen yang ada dalam array atau sesuatu
Amir
Saya pernah mendengar bahwa menyimpan referensi ke rootView sebuah fragmen adalah praktik yang buruk - dapat mengakibatkan kebocoran [rujukan?]?
giraffe.guru
1
@ giraffe.guru Fragmentmengacu pada tampilan akarnya tidak akan melakukan itu. Sementara referensi oleh beberapa elemen root GC akan, seperti properti statis global, variabel utas non-ui. Sebuah Fragmentinstance bukanlah GC-root, sehingga dapat dikumpulkan sampahnya. Begitu juga tampilan akarnya.
Lym Zoy
Anda sama hari saya.
Vijendra patidar
Manis dan sederhana.
Rupam Das
8

Saya bekerja dengan masalah yang sangat mirip dengan ini. Karena saya tahu saya akan sering kembali ke fragmen sebelumnya, saya memeriksa untuk melihat apakah fragmen .isAdded()itu benar, dan jika demikian, daripada melakukan transaction.replace()saya hanya melakukan a transaction.show(). Hal ini mencegah fragmen dibuat ulang jika sudah ada di stack - tidak perlu menyimpan status.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Hal lain yang perlu diingat adalah meskipun ini mempertahankan urutan alami untuk fragmen itu sendiri, Anda mungkin masih perlu menangani aktivitas itu sendiri yang dimusnahkan dan dibuat ulang saat orientasi (config) berubah. Untuk menyiasatinya di AndroidManifest.xml untuk node Anda:

android:configChanges="orientation|screenSize"

Di Android 3.0 dan lebih tinggi, screenSizetampaknya diperlukan.

Semoga berhasil

rmirabelle.dll
sumber
transaction.addToBackStack (button_id + "stack_item"); // apa yang dilakukan baris ini. Apa button_id di sini?
raghu_3
button_id hanyalah variabel yang dibuat. Argumen string yang diteruskan ke addToBackStack hanyalah nama opsional untuk status backstack - Anda dapat menyetelnya ke null jika Anda hanya mengelola satu backstack.
rmirabelle
, saya mengalami masalah yang mirip dengan ini dengan fragmen, dapatkah Anda memeriksanya- stackoverflow.com/questions/22468977/…
raghu_3
1
Jangan pernah menambahkan android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsdalam manifes Anda. Alih-alih pelajari cara menyimpan dan memulihkan status, misalnya dari sini: speakerdeck.com/cyrilmottier/…
Marcin Koziński
Dan kemudian setelah Anda mengetahui betapa sangat beratnya keadaan menabung dan bahwa solusi yang disajikan menyelesaikan masalah dengan paling bersih dalam situasi khusus Anda, lanjutkan dan gunakan, tanpa mempedulikan suara negatif dari mereka yang tidak setuju ;-)
rmirabelle
5

Solusi Terbaik yang saya temukan ada di bawah:

onSavedInstanceState (): selalu dipanggil di dalam fragmen saat aktivitas akan dimatikan (Pindahkan aktivitas dari satu ke yang lain atau konfigurasi berubah). Jadi jika kita memanggil banyak fragmen pada aktivitas yang sama maka kita harus menggunakan pendekatan berikut:

Gunakan OnDestroyView () dari fragmen dan simpan seluruh objek di dalam metode itu. Kemudian OnActivityCreated (): Periksa apakah objek null atau tidak (Karena metode ini memanggil setiap waktu). Sekarang pulihkan status objek di sini.

Ini selalu berhasil!

Aman Goel
sumber
4

jika Anda menangani perubahan konfigurasi dalam aktivitas fragmen Anda yang ditentukan dalam manifes android seperti ini

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

maka onSaveInstanceStatedari fragmen tidak akan dipanggil dan savedInstanceStateobjek akan selalu null.

Brook Oldre
sumber
1

Saya rasa onSaveInstanceStateitu bukan solusi yang baik. itu hanya digunakan untuk aktivitas yang telah dimusnahkan.

Dari android 3.0, Fragmen telah dikelola oleh FragmentManager, kondisinya adalah: satu fragmen manny pemetaan aktivitas, ketika fragmen ditambahkan (bukan menggantikan: itu akan dibuat ulang) di backStack, tampilan akan dihancurkan. ketika kembali ke yang terakhir, itu akan ditampilkan seperti sebelumnya.

Jadi menurut saya fragmentManger dan transaction cukup baik untuk menanganinya.

lemak kecil
sumber
0

Saya menggunakan pendekatan hybrid untuk fragmen yang berisi tampilan daftar. Tampaknya berhasil karena saya tidak mengganti fragmen saat ini, melainkan menambahkan fragmen baru dan menyembunyikan yang sekarang. Saya memiliki metode berikut dalam aktivitas yang menghosting fragmen saya:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

Saya menggunakan metode ini dalam fragmen saya (berisi tampilan daftar) setiap kali item daftar diklik / diketuk (dan karenanya saya perlu meluncurkan / menampilkan fragmen detail):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()mengembalikan larik string yang saya gunakan sebagai tag untuk fragmen berbeda ketika saya menambahkan fragmen baru (lihat transaction.addmetode dalam addFragmentmetode di atas).

Dalam fragmen yang berisi tampilan daftar, saya melakukan ini dalam metode onPause ():

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Kemudian di onCreateView dari fragmen (sebenarnya dalam metode yang dipanggil di onCreateView), saya memulihkan status:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Javad Sadeqzadeh
sumber
0

Pada akhirnya setelah mencoba banyak solusi rumit ini karena saya hanya perlu menyimpan / memulihkan satu nilai di Fragmen saya (konten EditText), dan meskipun itu mungkin bukan solusi paling elegan, membuat SharedPreference dan menyimpan status saya di sana bekerja untuk saya

VMMF
sumber
0

Cara sederhana untuk menyimpan nilai kolom dalam fragmen berbeda dalam suatu aktivitas

Buat Instance dari fragmen dan tambahkan alih-alih mengganti dan menghapus

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Kemudian cukup tampilkan dan sembunyikan fragmen alih-alih menambahkan dan menghapusnya lagi

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

Sreejesh K Nair
sumber
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Sathish Kumar
sumber
5
Harap pertimbangkan untuk memasukkan beberapa informasi tentang jawaban Anda, daripada hanya memposting kode. Kami mencoba memberikan tidak hanya 'perbaikan', tetapi membantu orang belajar. Anda harus menjelaskan apa yang salah dalam kode asli, apa yang Anda lakukan secara berbeda, dan mengapa perubahan Anda berhasil.
Andrew Barber