ViewPager dan fragmen - apa cara yang tepat untuk menyimpan status fragmen?

492

Fragmen tampaknya sangat bagus untuk pemisahan logika UI menjadi beberapa modul. Namun seiring dengan ViewPagersiklus hidupnya masih berkabut bagi saya. Jadi pikiran Guru sangat dibutuhkan!

Edit

Lihat solusi bodoh di bawah ;-)

Cakupan

Aktivitas utama memiliki ViewPagerdengan fragmen. Fragmen-fragmen itu dapat menerapkan logika yang sedikit berbeda untuk aktivitas (kirim) lainnya, sehingga data fragmen diisi melalui antarmuka panggilan balik di dalam aktivitas. Dan semuanya bekerja dengan baik pada peluncuran pertama, tetapi! ...

Masalah

Ketika aktivitas dibuat ulang (misalnya tentang perubahan orientasi), maka lakukan juga ViewPagerfragmennya. Kode (Anda akan temukan di bawah) mengatakan bahwa setiap kali aktivitas dibuat, saya mencoba membuat ViewPageradaptor fragmen baru sama dengan fragmen (mungkin ini masalahnya) tetapi FragmentManager sudah menyimpan semua fragmen ini di suatu tempat (di mana?) Dan memulai mekanisme rekreasi bagi mereka. Jadi mekanisme rekreasi memanggil fragmen "lama" diAttach, onCreateView, dll. Dengan panggilan antarmuka panggilan balik untuk menginisiasi data melalui metode yang diterapkan Aktivitas. Tetapi metode ini menunjuk ke fragmen yang baru dibuat yang dibuat melalui metode onCreate Activity.

Isu

Mungkin saya menggunakan pola yang salah tetapi bahkan buku Android 3 Pro tidak memiliki banyak tentang hal itu. Jadi, tolong , beri saya satu-dua pukulan dan tunjukkan bagaimana melakukannya dengan cara yang benar. Terimakasih banyak!

Kode

Aktifitas utama

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 alias pembantu

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

Adaptor

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
}

}

Pecahan

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

Larutan

Solusi bodoh adalah menyimpan fragmen di dalam onSaveInstanceState (dari aktivitas host) dengan putFragment dan memasukkannya ke dalam onCreate via getFragment. Tapi saya masih punya perasaan aneh bahwa hal-hal yang seharusnya tidak berfungsi seperti itu ... Lihat kode di bawah ini:

    @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();
    ...
}
Oleksii Malovanyi
sumber
1
Saya ingin tahu sekarang: apakah saya harus menggunakan pendekatan yang sangat berbeda atau mencoba menyimpan fragmen aktivitas utama (Dashboard) melalui onSavedInstancestate untuk menggunakannya di onCreate (). Apakah ada cara yang tepat untuk menyimpan fragmen-fragmen itu dan mendapatkannya dari bundel di onCreate? Mereka sepertinya tidak terpisahkan ...
Oleksii Malovanyi
2
Pendekatan ke-2 berhasil - lihat "Sulution". Tapi sepertinya itu kode yang jelek, bukan?
Oleksii Malovanyi
1
Demi upaya membersihkan tag Android (detail di sini: meta.stackexchange.com/questions/100529/… ), maukah Anda memposting solusi Anda sebagai jawaban dan menandainya sebagai yang dipilih? Dengan begitu, pertanyaan itu tidak akan muncul sebagai pertanyaan yang belum terjawab :)
Alexander Lucas
1
ya, anggap tidak apa-apa. Berharap lebih baik daripada milikku ...
Oleksii Malovanyi
1
Apakah solusi bodoh itu berhasil? Ini memberi saya pengecualian null pointer ..
Zhen Liu

Jawaban:

451

Ketika FragmentPagerAdaptermenambahkan fragmen ke FragmentManager, ia menggunakan tag khusus berdasarkan posisi tertentu bahwa fragmen akan ditempatkan. FragmentPagerAdapter.getItem(int position)hanya dipanggil ketika sebuah fragmen untuk posisi itu tidak ada. Setelah berputar, Android akan melihat bahwa ia telah membuat / menyimpan sebuah fragmen untuk posisi tertentu ini dan karena itu ia hanya mencoba untuk terhubung kembali dengannya FragmentManager.findFragmentByTag(), alih-alih membuat yang baru. Semua ini datang gratis ketika menggunakan FragmentPagerAdapterdan itulah sebabnya biasanya memiliki kode inisialisasi fragmen Anda di dalam getItem(int)metode.

Bahkan jika kami tidak menggunakan FragmentPagerAdapter, itu bukan ide yang baik untuk membuat fragmen baru setiap saat di Activity.onCreate(Bundle). Seperti yang Anda perhatikan, ketika sebuah fragmen ditambahkan ke FragmentManager, itu akan dibuat ulang untuk Anda setelah diputar dan tidak perlu menambahkannya lagi. Melakukannya adalah penyebab umum kesalahan ketika bekerja dengan fragmen.

Pendekatan biasa ketika bekerja dengan fragmen adalah ini:

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

    ...

}

Saat menggunakan a FragmentPagerAdapter, kami melepaskan manajemen fragmen ke adaptor, dan tidak harus melakukan langkah-langkah di atas. Secara default, itu hanya akan memuat satu Fragmen di depan dan di belakang posisi saat ini (meskipun tidak menghancurkan mereka kecuali Anda menggunakan FragmentStatePagerAdapter). Ini dikendalikan oleh ViewPager.setOffscreenPageLimit (int) . Karena itu, metode panggilan langsung pada fragmen di luar adaptor tidak dijamin valid, karena mereka bahkan mungkin tidak hidup.

Singkatnya, solusi Anda untuk menggunakan putFragmentuntuk bisa mendapatkan referensi setelah itu tidak begitu gila, dan tidak begitu berbeda dengan cara normal untuk menggunakan fragmen (di atas). Sulit untuk mendapatkan referensi sebaliknya karena fragmen ditambahkan oleh adaptor, dan bukan Anda secara pribadi. Pastikan saja bahwa offscreenPageLimititu cukup tinggi untuk memuat fragmen yang Anda inginkan setiap saat, karena Anda mengandalkannya. Ini memintas kemampuan pemuatan yang malas dari ViewPager, tetapi tampaknya itulah yang Anda inginkan untuk aplikasi Anda.

Pendekatan lain adalah menimpa FragmentPageAdapter.instantiateItem(View, int)dan menyimpan referensi ke fragmen yang dikembalikan dari panggilan super sebelum mengembalikannya (memiliki logika untuk menemukan fragmen, jika sudah ada).

Untuk gambar yang lebih lengkap, lihat beberapa sumber dari FragmentPagerAdapter (pendek) dan ViewPager (panjang).

antonyt
sumber
7
Mencintai bagian terakhir. Memiliki cache untuk fragmen dan memindahkan put dalam cache cache di dalam FragmentPageAdapter.instantiateItem(View, int). Akhirnya memperbaiki bug yang tahan lama yang hanya muncul di rotasi / perubahan konfigurasi dan membuat saya gila ...
Carlos Sobrinho
2
Ini memperbaiki masalah yang saya alami pada perubahan rotasi. Mungkin saya tidak bisa melihat, tetapi apakah ini didokumentasikan? yaitu menggunakan tag untuk memulihkan dari keadaan sebelumnya? Ini mungkin jawaban yang jelas, tapi saya cukup baru dalam pengembangan Android.
1
@ Industri-antidepresan Ini adalah id wadah (misalnya FrameLayout) yang ditambahkan Fragmen.
antonyt
1
btw, bagi saya itu FragmentPageAdapter.instantiateItem(ViewGroup, int)bukan FragmentPageAdapter.instantiateItem(View, int).
Nama Tampilan
1
Btw, tahukah Anda metode siklus hidup fragmen mana (jika ada) yang dipanggil ketika fragmen terhapus dari layar? Apakah itu onDetach(), atau sesuatu yang lain?
Ygesher
36

Saya ingin menawarkan solusi yang mengembang di antonyt's jawaban indah dan menyebutkan utamaFragmentPageAdapter.instantiateItem(View, int)untuk menyimpan referensi untuk menciptakanFragmentssehingga Anda dapat melakukan pekerjaan pada mereka nanti. Ini juga harus bekerja denganFragmentStatePagerAdapter; lihat catatan untuk detailnya.


Berikut adalah contoh sederhana tentang cara mendapatkan referensi ke yang Fragmentsdikembalikan oleh FragmentPagerAdapteryang tidak bergantung pada tagsset internal pada Fragments. Kuncinya adalah menimpainstantiateItem() dan menyimpan referensi di sana alih - alih di 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();
        }
    }
}

atau jika Anda lebih suka bekerja dengan tagsalih - alih variabel / referensi anggota kelas FragmentsAnda juga dapat mengambil tagsset dengan FragmentPagerAdaptercara yang sama: CATATAN: ini tidak berlaku FragmentStatePagerAdapterkarena tidak disetel tagssaat membuatnya 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;
}

Perhatikan bahwa metode ini TIDAK bergantung pada peniruan internal tag set oleh FragmentPagerAdapterdan sebagai gantinya menggunakan API yang tepat untuk mengambilnya. Dengan cara ini bahkan jika tagperubahan dalam versi masa depan SupportLibraryAnda masih akan aman.


Jangan lupa bahwa tergantung pada desain Anda Activity, yang FragmentsAnda coba kerjakan mungkin atau mungkin belum ada, jadi Anda harus memperhitungkannya dengan melakukan nullpemeriksaan sebelum menggunakan referensi Anda.

Juga, jika sebaliknya Anda bekerja dengan FragmentStatePagerAdapter, maka Anda tidak ingin menyimpan referensi sulit untuk Anda Fragmentskarena Anda mungkin memiliki banyak dari mereka dan referensi keras akan tidak perlu menyimpannya dalam memori. Alih-alih menyimpan Fragmentreferensi dalam WeakReferencevariabel, bukan yang standar. Seperti ini:

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...
}
Tony Chan
sumber
pada kekurangan memori android mungkin meminta FragmentManager untuk menghancurkan fragmen yang tidak digunakan, kan? Jika saya benar, versi kedua Anda akan berfungsi, yang pertama mungkin gagal. (Tidak tahu apakah versi android saat ini melakukan ini, tetapi tampaknya kita harus mengharapkan itu.)
Moritz
1
bagaimana dengan membuat FragmetPagerAdapteronCreate of Activity pada setiap rotasi layar. Apakah ini salah karena dapat memotong kembali menggunakan fragmen yang sudah ditambahkan diFragmentPagerAdapter
Bahman
Mengesampingkan instantiateItem()adalah cara untuk pergi; ini membantu saya menangani rotasi layar, dan mengambil instance Fragment yang ada saat Activity dan Adapter dilanjutkan; Saya meninggalkan komentar dalam kode sebagai pengingat: Setelah rotasi, getItem()TIDAK dipanggil; hanya metode ini yang instantiateItem()dipanggil. Implementasi super untuk instantiateItem()benar - benar melampirkan kembali fragmen setelah rotasi (sesuai kebutuhan), alih-alih membuat instance instance baru!
HelloImKevo
Saya mendapatkan null pointer pada Fragment createdFragment = (Fragment) super.instantiateItem..solusi pertama.
AlexS
Setelah mencari semua iterasi duplikat / varian berbagai masalah ini, ini adalah solusi terbaik. (Referensi silang dari Q lain dengan judul yang lebih relevan, jadi terima kasih untuk itu!)
MandisaW
18

Saya menemukan solusi lain yang relatif mudah untuk pertanyaan Anda.

Seperti yang dapat Anda lihat dari kode sumber FragmentPagerAdapter , fragmen yang dikelola oleh FragmentPagerAdaptertoko di FragmentManagerbawah tag yang dihasilkan menggunakan:

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

Ini viewIdadalah container.getId(), containerini adalah ViewPagercontoh Anda . The indexadalah posisi fragmen. Karenanya, Anda dapat menyimpan id objek ke 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);

Jika Anda ingin berkomunikasi dengan fragmen ini, Anda dapat memperolehnya dari FragmentManager, seperti:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
pengguna2110066
sumber
21
hmmm Saya tidak berpikir ini adalah cara yang baik untuk pergi sebagai balasan Anda pada konvensi penamaan tag internal yang tidak berarti akan tetap sama selamanya.
Dori
1
Bagi mereka yang mencari solusi yang tidak bergantung pada internal tag, pertimbangkan untuk mencoba jawaban saya .
Tony Chan
16

Saya ingin menawarkan solusi alternatif untuk kasus yang mungkin sedikit berbeda, karena banyak pencarian saya untuk jawaban terus mengarahkan saya ke utas ini.

Kasus saya - Saya membuat / menambahkan halaman secara dinamis dan menggesernya ke dalam ViewPager, tetapi ketika diputar (onConfigurationChange) saya berakhir dengan halaman baru karena tentu saja OnCreate dipanggil lagi. Tapi saya ingin tetap merujuk ke semua halaman yang dibuat sebelum rotasi.

Masalah - Saya tidak memiliki pengidentifikasi unik untuk setiap fragmen yang saya buat, jadi satu-satunya cara untuk referensi adalah entah bagaimana menyimpan referensi dalam Array yang akan dipulihkan setelah perubahan rotasi / konfigurasi.

Penanganan Masalah - Konsep kuncinya adalah memiliki Activity (yang menampilkan Fragmen) juga mengelola array referensi ke Fragmen yang ada, karena aktivitas ini dapat memanfaatkan Bundel di onSaveInstanceState

public class MainActivity extends FragmentActivity

Jadi dalam Kegiatan ini, saya menyatakan anggota pribadi untuk melacak halaman yang terbuka

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

Ini diperbarui setiap kali onSaveInstanceState dipanggil dan dipulihkan di onCreate

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

... jadi setelah disimpan, itu dapat diambil ...

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

Ini adalah perubahan yang diperlukan untuk aktivitas utama, dan jadi saya membutuhkan anggota dan metode dalam FragmentPagerAdapter saya agar ini berfungsi, jadi dalam

public class ViewPagerAdapter extends FragmentPagerAdapter

konstruk yang identik (seperti yang ditunjukkan di atas di MainActivity)

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

dan sinkronisasi ini (seperti yang digunakan di atas di onSaveInstanceState) didukung secara khusus oleh metode

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

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

Dan akhirnya, di kelas fragmen

public class CustomFragment extends Fragment

Agar semua ini berhasil, ada dua perubahan, pertama

public class CustomFragment extends Fragment implements Serializable

dan kemudian menambahkan ini ke onCreate sehingga Fragmen tidak dihancurkan

setRetainInstance(true);

Saya masih dalam proses membungkus kepala saya di sekitar fragmen dan siklus hidup Android, jadi peringatan di sini adalah mungkin ada redudansi / inefisiensi dalam metode ini. Tetapi ini bekerja untuk saya dan saya harap dapat membantu orang lain dengan kasus yang mirip dengan saya.

Frank Yin
sumber
2
+1 untuk setRetainInstance (true) - sihir hitam yang saya cari!
kemunduran
Itu bahkan lebih mudah dengan menggunakan FragmentStatePagerAdapter (v13). Yang berurusan dengan Anda dengan mengembalikan negara dan melepaskan.
Tukang
Deskripsi yang jelas dan solusi yang sangat bagus. Terima kasih!
Bruno Bieri
8

Solusi saya sangat kasar tetapi berfungsi: menjadi fragmen saya yang dibuat secara dinamis dari data yang disimpan, saya cukup menghapus semua fragmen dari PageAdaptersebelum panggilan super.onSaveInstanceState()dan kemudian membuatnya kembali pada pembuatan aktivitas:

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

Anda tidak dapat menghapusnya onDestroy(), jika tidak Anda akan mendapatkan pengecualian ini:

java.lang.IllegalStateException: Tidak dapat melakukan tindakan ini setelah onSaveInstanceState

Berikut kode di halaman adaptor:

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

Saya hanya menyimpan halaman saat ini dan mengembalikannya onCreate(), setelah fragmen telah dibuat.

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  
Pak John
sumber
Saya tidak tahu mengapa, tetapi notifyDataSetChanged (); menyebabkan aplikasi mogok
Malachiasz
4

Apa itu BasePagerAdapter? Anda harus menggunakan salah satu adaptor pager standar - baik FragmentPagerAdapteratauFragmentStatePagerAdapter , tergantung pada apakah Anda ingin Fragmen yang tidak lagi dibutuhkan ViewPageruntuk disimpan di sekitar (yang pertama) atau membuat negara mereka disimpan (yang terakhir) dan dibuat kembali jika dibutuhkan lagi.

Kode contoh untuk menggunakan ViewPagerdapat ditemukan di sini

Memang benar bahwa manajemen fragmen dalam tampilan pager di seluruh instance aktivitas sedikit rumit, karena FragmentManagerdalam kerangka menjaga keselamatan negara dan mengembalikan setiap fragmen aktif yang telah dibuat pager. Semua ini benar-benar berarti bahwa adaptor ketika menginisialisasi perlu memastikan itu terhubung kembali dengan fragmen apa pun yang dipulihkan. Anda dapat melihat kode untuk FragmentPagerAdapteratau FragmentStatePagerAdapteruntuk melihat bagaimana ini dilakukan.

hackbod
sumber
1
Kode BasePagerAdapter tersedia di pertanyaan saya. Seperti yang dapat dilihat, ia hanya memperluas FragmentPagerAdapter untuk tujuan mengimplementasikan TitleProvider . Jadi semuanya sudah bekerja dengan pendekatan dev android yang disarankan.
Oleksii Malovanyi
Saya tidak mengetahui "FragmentStatePagerAdapter". Anda benar-benar menyelamatkan saya berjam-jam. Terima kasih.
Knossos
2

Jika ada yang mengalami masalah dengan FragmentStatePagerAdapter mereka tidak mengembalikan keadaan fragmen dengan benar ... yaitu ... Fragmen baru sedang dibuat oleh FragmentStatePagerAdapter alih-alih mengembalikannya dari keadaan ...

Pastikan Anda menelepon ViewPager.setOffscreenPageLimit()SEBELUM Anda meneleponViewPager.setAdapter(fragmentStatePagerAdapter)

Saat menelepon ViewPager.setOffscreenPageLimit()... ViewPager akan segera melihat ke adaptornya dan mencoba untuk mendapatkan fragmennya. Ini bisa terjadi sebelum ViewPager memiliki kesempatan untuk mengembalikan Fragmen dari SimpananInstanceState (sehingga menciptakan Fragmen baru yang tidak dapat diinisialisasi ulang dari SavedInstanceState karena mereka baru).

dell116
sumber
0

Saya datang dengan solusi sederhana dan elegan ini. Ini mengasumsikan bahwa aktivitas bertanggung jawab untuk membuat Fragmen, dan Adaptor hanya melayani mereka.

Ini adalah kode adaptor (tidak ada yang aneh di sini, kecuali fakta bahwa mFragmentsini adalah daftar fragmen yang dikelola oleh Activity)

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

Seluruh masalah utas ini mendapatkan referensi dari fragmen "lama", jadi saya menggunakan kode ini di onCreate Activity.

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

Tentu saja Anda dapat lebih lanjut menyempurnakan kode ini jika diperlukan, misalnya memastikan fragmen adalah contoh dari kelas tertentu.

Merlevede
sumber
0

Untuk mendapatkan fragmen setelah perubahan orientasi, Anda harus menggunakan .getTag ().

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

Untuk penanganan yang lebih sedikit saya menulis ArrayList saya sendiri untuk PageAdapter saya untuk mendapatkan fragmen dengan viewPagerId dan FragmentClass di Posisi apa pun:

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

Jadi buat saja MyPageArrayList dengan fragmen:

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

dan menambahkannya ke viewPager:

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

setelah ini, Anda bisa mendapatkan setelah orientasi mengubah fragmen yang benar dengan menggunakan kelasnya:

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
Pengemis
sumber
-30

Menambahkan:

   @SuppressLint("ValidFragment")

sebelum kelas Anda.

itu tidak berfungsi melakukan sesuatu seperti ini:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
andro_stackoverflow
sumber