Fragmen tampaknya sangat bagus untuk pemisahan logika UI menjadi beberapa modul. Namun seiring dengan ViewPager
siklus hidupnya masih berkabut bagi saya. Jadi pikiran Guru sangat dibutuhkan!
Edit
Lihat solusi bodoh di bawah ;-)
Cakupan
Aktivitas utama memiliki ViewPager
dengan 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 ViewPager
fragmennya. Kode (Anda akan temukan di bawah) mengatakan bahwa setiap kali aktivitas dibuat, saya mencoba membuat ViewPager
adaptor 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();
...
}
sumber
Jawaban:
Ketika
FragmentPagerAdapter
menambahkan 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 dengannyaFragmentManager.findFragmentByTag()
, alih-alih membuat yang baru. Semua ini datang gratis ketika menggunakanFragmentPagerAdapter
dan itulah sebabnya biasanya memiliki kode inisialisasi fragmen Anda di dalamgetItem(int)
metode.Bahkan jika kami tidak menggunakan
FragmentPagerAdapter
, itu bukan ide yang baik untuk membuat fragmen baru setiap saat diActivity.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:
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 menggunakanFragmentStatePagerAdapter
). 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
putFragment
untuk 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 bahwaoffscreenPageLimit
itu 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).
sumber
FragmentPageAdapter.instantiateItem(View, int)
. Akhirnya memperbaiki bug yang tahan lama yang hanya muncul di rotasi / perubahan konfigurasi dan membuat saya gila ...FragmentPageAdapter.instantiateItem(ViewGroup, int)
bukanFragmentPageAdapter.instantiateItem(View, int)
.onDetach()
, atau sesuatu yang lain?Saya ingin menawarkan solusi yang mengembang di
antonyt
's jawaban indah dan menyebutkan utamaFragmentPageAdapter.instantiateItem(View, int)
untuk menyimpan referensi untuk menciptakanFragments
sehingga 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
Fragments
dikembalikan olehFragmentPagerAdapter
yang tidak bergantung padatags
set internal padaFragments
. Kuncinya adalah menimpainstantiateItem()
dan menyimpan referensi di sana alih - alih digetItem()
.atau jika Anda lebih suka bekerja dengan
tags
alih - alih variabel / referensi anggota kelasFragments
Anda juga dapat mengambiltags
set denganFragmentPagerAdapter
cara yang sama: CATATAN: ini tidak berlakuFragmentStatePagerAdapter
karena tidak diseteltags
saat membuatnyaFragments
.Perhatikan bahwa metode ini TIDAK bergantung pada peniruan internal
tag
set olehFragmentPagerAdapter
dan sebagai gantinya menggunakan API yang tepat untuk mengambilnya. Dengan cara ini bahkan jikatag
perubahan dalam versi masa depanSupportLibrary
Anda masih akan aman.Jangan lupa bahwa tergantung pada desain Anda
Activity
, yangFragments
Anda coba kerjakan mungkin atau mungkin belum ada, jadi Anda harus memperhitungkannya dengan melakukannull
pemeriksaan sebelum menggunakan referensi Anda.Juga, jika sebaliknya Anda bekerja dengan
FragmentStatePagerAdapter
, maka Anda tidak ingin menyimpan referensi sulit untuk AndaFragments
karena Anda mungkin memiliki banyak dari mereka dan referensi keras akan tidak perlu menyimpannya dalam memori. Alih-alih menyimpanFragment
referensi dalamWeakReference
variabel, bukan yang standar. Seperti ini:sumber
FragmetPagerAdapter
onCreate of Activity pada setiap rotasi layar. Apakah ini salah karena dapat memotong kembali menggunakan fragmen yang sudah ditambahkan diFragmentPagerAdapter
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 yanginstantiateItem()
dipanggil. Implementasi super untukinstantiateItem()
benar - benar melampirkan kembali fragmen setelah rotasi (sesuai kebutuhan), alih-alih membuat instance instance baru!Fragment createdFragment = (Fragment) super.instantiateItem..
solusi pertama.Saya menemukan solusi lain yang relatif mudah untuk pertanyaan Anda.
Seperti yang dapat Anda lihat dari kode sumber FragmentPagerAdapter , fragmen yang dikelola oleh
FragmentPagerAdapter
toko diFragmentManager
bawah tag yang dihasilkan menggunakan:Ini
viewId
adalahcontainer.getId()
,container
ini adalahViewPager
contoh Anda . Theindex
adalah posisi fragmen. Karenanya, Anda dapat menyimpan id objek keoutState
:Jika Anda ingin berkomunikasi dengan fragmen ini, Anda dapat memperolehnya dari
FragmentManager
, seperti:sumber
tag
, pertimbangkan untuk mencoba jawaban saya .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
Jadi dalam Kegiatan ini, saya menyatakan anggota pribadi untuk melacak halaman yang terbuka
Ini diperbarui setiap kali onSaveInstanceState dipanggil dan dipulihkan di onCreate
... jadi setelah disimpan, itu dapat diambil ...
Ini adalah perubahan yang diperlukan untuk aktivitas utama, dan jadi saya membutuhkan anggota dan metode dalam FragmentPagerAdapter saya agar ini berfungsi, jadi dalam
konstruk yang identik (seperti yang ditunjukkan di atas di MainActivity)
dan sinkronisasi ini (seperti yang digunakan di atas di onSaveInstanceState) didukung secara khusus oleh metode
Dan akhirnya, di kelas fragmen
Agar semua ini berhasil, ada dua perubahan, pertama
dan kemudian menambahkan ini ke onCreate sehingga Fragmen tidak dihancurkan
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.
sumber
Solusi saya sangat kasar tetapi berfungsi: menjadi fragmen saya yang dibuat secara dinamis dari data yang disimpan, saya cukup menghapus semua fragmen dari
PageAdapter
sebelum panggilansuper.onSaveInstanceState()
dan kemudian membuatnya kembali pada pembuatan aktivitas:Anda tidak dapat menghapusnya
onDestroy()
, jika tidak Anda akan mendapatkan pengecualian ini:java.lang.IllegalStateException:
Tidak dapat melakukan tindakan ini setelahonSaveInstanceState
Berikut kode di halaman adaptor:
Saya hanya menyimpan halaman saat ini dan mengembalikannya
onCreate()
, setelah fragmen telah dibuat.sumber
Apa itu
BasePagerAdapter
? Anda harus menggunakan salah satu adaptor pager standar - baikFragmentPagerAdapter
atauFragmentStatePagerAdapter
, tergantung pada apakah Anda ingin Fragmen yang tidak lagi dibutuhkanViewPager
untuk disimpan di sekitar (yang pertama) atau membuat negara mereka disimpan (yang terakhir) dan dibuat kembali jika dibutuhkan lagi.Kode contoh untuk menggunakan
ViewPager
dapat ditemukan di siniMemang benar bahwa manajemen fragmen dalam tampilan pager di seluruh instance aktivitas sedikit rumit, karena
FragmentManager
dalam 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 untukFragmentPagerAdapter
atauFragmentStatePagerAdapter
untuk melihat bagaimana ini dilakukan.sumber
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).sumber
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
mFragments
ini adalah daftar fragmen yang dikelola oleh Activity)Seluruh masalah utas ini mendapatkan referensi dari fragmen "lama", jadi saya menggunakan kode ini di onCreate Activity.
Tentu saja Anda dapat lebih lanjut menyempurnakan kode ini jika diperlukan, misalnya memastikan fragmen adalah contoh dari kelas tertentu.
sumber
Untuk mendapatkan fragmen setelah perubahan orientasi, Anda harus menggunakan .getTag ().
Untuk penanganan yang lebih sedikit saya menulis ArrayList saya sendiri untuk PageAdapter saya untuk mendapatkan fragmen dengan viewPagerId dan FragmentClass di Posisi apa pun:
Jadi buat saja MyPageArrayList dengan fragmen:
dan menambahkannya ke viewPager:
setelah ini, Anda bisa mendapatkan setelah orientasi mengubah fragmen yang benar dengan menggunakan kelasnya:
sumber
Menambahkan:
sebelum kelas Anda.
itu tidak berfungsi melakukan sesuatu seperti ini:
sumber