Как правильно обрабатывать вращение экрана с помощью ViewPager и вложенных фрагментов?
У меня есть эта активность, которая содержит фрагмент. Эта компоновка фрагмента состоит из пейджера представления с несколькими фрагментами (два, фактически).
Когда создается пейджер представления, его адаптер создается, getItem
получает вызов, и мои подфрагменты создаются. Отлично.
Теперь, когда я поворачиваю экран, среда обрабатывает повторное создание фрагмента, адаптер создается снова в моем onCreate
из основного фрагмента, но getItem
никогда не вызывается, поэтому мой адаптер содержит неправильные ссылки (фактически нули) вместо двух фрагментов.
Я обнаружил, что диспетчер фрагментов (т.е. диспетчер дочерних фрагментов) содержит массив фрагментов под названием mActive
, который, конечно, недоступен из кода. Однако существует этот метод getFragment
:
@Override
public Fragment getFragment(Bundle bundle, String key) {
int index = bundle.getInt(key, -1);
if (index == -1) {
return null;
}
if (index >= mActive.size()) {
throwException(new IllegalStateException("Fragement no longer exists for key "
+ key + ": index " + index));
}
Fragment f = mActive.get(index);
if (f == null) {
throwException(new IllegalStateException("Fragement no longer exists for key "
+ key + ": index " + index));
}
return f;
}
Я не буду комментировать опечатку:)
Это хак, который я реализовал для обновления ссылок на мои фрагменты, в моем конструкторе адаптера:
// fm holds a reference to a FragmentManager
Bundle hack = new Bundle();
try {
for (int i = 0; i < mFragments.length; i++) {
hack.putInt("hack", i);
mFragments[i] = fm.getFragment(hack, "hack");
}
} catch (Exception e) {
// No need to fail here, likely because it the first creation and mActive is empty
}
Я не горжусь. Это работает, но это уродливо. Какой фактический способ иметь действительный адаптер после вращения экрана?
PS: здесь полный код
Ответы
Ответ 1
У меня была такая же проблема - я предполагаю, что вы подклассифицируете FragmentPagerAdapter
для вашего пейджера-адаптера (поскольку getItem()
имеет значение для FragmentPagerAdapter
).
Мое решение состояло в том, чтобы вместо подкласса PagerAdapter
и обрабатывать создание/удаление фрагмента самостоятельно (переопределяя некоторый код FragmentPagerAdapter
):
public class ListPagerAdapter extends PagerAdapter {
FragmentManager fragmentManager;
Fragment[] fragments;
public ListPagerAdapter(FragmentManager fm){
fragmentManager = fm;
fragments = new Fragment[5];
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
assert(0 <= position && position < fragments.length);
FragmentTransaction trans = fragmentManager.beginTransaction();
trans.remove(fragments[position]);
trans.commit();
fragments[position] = null;
}
@Override
public Fragment instantiateItem(ViewGroup container, int position){
Fragment fragment = getItem(position);
FragmentTransaction trans = fragmentManager.beginTransaction();
trans.add(container.getId(),fragment,"fragment:"+position);
trans.commit();
return fragment;
}
@Override
public int getCount() {
return fragments.length;
}
@Override
public boolean isViewFromObject(View view, Object fragment) {
return ((Fragment) fragment).getView() == view;
}
public Fragment getItem(int position){
assert(0 <= position && position < fragments.length);
if(fragments[position] == null){
fragments[position] = ; //make your fragment here
}
return fragments[position];
}
}
Надеюсь, что это поможет.
Ответ 2
Мой ответ похож на Joshua Hunt one, но, совершая транзакцию в методе finishUpdate
, вы получаете гораздо лучшую производительность. Одна транзакция вместо двух за обновление.
Вот код:
private class SuchPagerAdapter extends PagerAdapter{
private final FragmentManager mFragmentManager;
private SparseArray<Fragment> mFragments;
private FragmentTransaction mCurTransaction;
private SuchPagerAdapter(FragmentManager fragmentManager) {
mFragmentManager = fragmentManager;
mFragments = new SparseArray<>();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = getItem(position);
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.add(container.getId(),fragment,"fragment:"+position);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(mFragments.get(position));
mFragments.remove(position);
}
@Override
public boolean isViewFromObject(View view, Object fragment) {
return ((Fragment) fragment).getView() == view;
}
public Fragment getItem(int position) {
return YoursVeryFragment.instantiate();
}
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
@Override
public int getCount() {
return countOfPages;
}
}
Ответ 3
Почему решения выше настолько сложны? Похож на излишество.
Я решаю это только с заменой старой ссылки на новую в классе, расширенную от FragmentPagerAdapter
@Override
public Object instantiateItem(ViewGroup container, int position) {
frags[position] = (Fragment) super.instantiateItem(container, position);
return frags[position];
}
Весь код адаптера выглядит следующим образом
public class RelationsFragmentsAdapter extends FragmentPagerAdapter {
private final String titles[] = new String[3];
private final Fragment frags[] = new Fragment[titles.length];
public RelationsFragmentsAdapter(FragmentManager fm) {
super(fm);
frags[0] = new FriendsFragment();
frags[1] = new FriendsRequestFragment();
frags[2] = new FriendsDeclinedFragment();
Resources resources = AppController.getAppContext().getResources();
titles[0] = resources.getString(R.string.my_friends);
titles[1] = resources.getString(R.string.my_new_friends);
titles[2] = resources.getString(R.string.followers);
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
@Override
public Fragment getItem(int position) {
return frags[position];
}
@Override
public int getCount() {
return frags.length;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
frags[position] = (Fragment) super.instantiateItem(container, position);
return frags[position];
}
}
Ответ 4
@Override public void onCreate(Bundle savedState) {
...
// Retain Fragment instance across Activity re-creation.
// onCreate() and onDestroy() will no longer be called on re-creation.
// onAttach(), onDetach(), and onActivityCreated() will still be called.
setRetainInstance(true);
...
}
Ответ 5
Мой код:
public class SampleAdapter extends FragmentStatePagerAdapter {
private Fragment mFragmentAtPos2;
private FragmentManager mFragmentManager;
private Fragment[] mFragments = new Fragment[3];
public SampleAdapter(FragmentManager mgr) {
super(mgr);
mFragmentManager = mgr;
Bundle hack = new Bundle();
try {
for (int i = 0; i < mFragments.length; i++) {
hack.putInt("hack", i);
mFragments[i] = mFragmentManager.getFragment(hack, "hack");
}
} catch (Exception e) {
// No need to fail here, likely because it the first creation and mActive is empty
}
}
public void switchFrag(Fragment frag) {
if (frag == null) {
Dbg.e(TAG, "- switch(frag) frag is NULL");
return;
} else Dbg.v(TAG, "- switch(frag) - frag is " + frag.getClass());
// We have to check for mFragmentAtPos2 null in case of first time (only Mytrips fragment being instatiante).
if (mFragmentAtPos2!= null)
mFragmentManager.beginTransaction()
.remove(mFragmentAtPos2)
.commit();
mFragmentAtPos2 = frag;
notifyDataSetChanged();
}
@Override
public int getCount() {
return(3);
}
@Override
public int getItemPosition(Object object) {
Dbg.v(TAG,"getItemPosition : "+object.getClass());
if (object instanceof MyTripsFragment
|| object instanceof FindingDriverFragment
|| object instanceof BookingAcceptedFragment
|| object instanceof RideStartedFragment
|| object instanceof RideEndedFragment
|| object instanceof ContactUsFragment
)
return POSITION_NONE;
else return POSITION_UNCHANGED;
}
@Override
public Fragment getItem(int position) {
Dbg.v("SampleAdapter", "getItem called on: "+position);
switch (position) {
case 0:
if (snapbookFrag==null) {
snapbookFrag = new SnapBookingFragment();
Dbg.e(TAG, "snapbookFrag created");
}
return snapbookFrag;
case 1:
if(bookingFormFrag==null) {
bookingFormFrag = new BookingFormFragment();
Dbg.e(TAG, "bookingFormFrag created");
}
return bookingFormFrag;
case 2:
if (mFragmentAtPos2 == null) {
myTripsFrag = new MyTripsFragment();
mFragmentAtPos2 = myTripsFrag;
return mFragmentAtPos2;
}
return mFragmentAtPos2;
default:
return(new SnapBookingFragment());
}
}
}
Ответ 6
Что касается решения simekadam, mFragments не заполняется в instantiateItem и не нуждается в mFragments.put(position, fragment);
внутри, или вы получите эту ошибку: Попытка удалить фрагмент из представления дает мне NullPointerException в mNextAnim.
Ответ 7
Проблема заключается в том, что getItem()
в FragmentPageAdapter
имеет неправильное имя. Его следует назвать createItem()
.
Поскольку способ работы getItem()
предназначен для создания фрагментов, и небезопасно вызывать его для запроса/поиска фрагмента.
Моя рекомендация - сделать копию текущего FragmentPagerAdapter и изменить его таким образом:
Добавить
public abstract Fragment createFragment(int position);
И измените getItem на:
public Fragment getItem(int position) {
if(containerId!=null) {
final long itemId = getItemId(position);
String name = makeFragmentName(containerId, itemId);
return mFragmentManager.findFragmentByTag(name);
} else {
return null;
}
}
Наконец добавьте, что для instantiateItem:
if(containerId==null)
containerId = container.getId();
else if(containerId!=container.getId())
throw new RuntimeException("Container id not expected to change");
Заполните код этот параметр
Я считаю, что эта реализация более безопасна и проста в использовании, а также имеет ту же производительность от первоначального адаптера от инженеров Google.