J'ai cette activité qui contient un fragment. Cette disposition de fragment consiste en un pageur de vue avec plusieurs fragments (deux en réalité).
Lorsque le pageur de vue est créé, son adaptateur est créé, getItem
est appelé et mes sous-fragments sont créés. Génial.
Maintenant, lorsque je fais pivoter l'écran, le framework gère la recréation de fragment, l'adaptateur est créé à nouveau dans ma onCreate
à partir du fragment principal, mais getItem
ne s'appelle jamais, mon adaptateur contient donc des références fausses (en fait des null) des deux fragments.
Ce que j'ai trouvé, c'est que le gestionnaire de fragments (c'est-à-dire le gestionnaire de fragments enfants) contient un tableau de fragments appelé mActive
, qui n'est bien sûr pas accessible à partir du code. Cependant, il y a cette méthode 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;
}
Je ne commenterai pas la faute de frappe :)
Voici le hack que j'ai mis en place pour mettre à jour les références à mes fragments, dans le constructeur de mon adaptateur:
// 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's the first creation and mActive is empty
}
Je ne suis pas fier Cela fonctionne, mais c'est moche. Quelle est la manière réelle d'avoir un adaptateur valide après une rotation d'écran?
PS: voici le code complet
J'ai eu le même problème - je suppose que vous sous-classez FragmentPagerAdapter
pour votre adaptateur de pagette (car getItem()
est spécifique à FragmentPagerAdapter
).
Ma solution consistait à placer à la place la sous-classe PagerAdapter
et à gérer vous-même la création/la suppression du fragment (en réimplémentant une partie du code 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];
}
}
J'espère que cela t'aides.
Ma réponse est un peu similaire à celle de Joshua Hunt, mais en engageant la transaction selon la méthode finishUpdate
, vous obtenez de bien meilleures performances. Une transaction au lieu de deux par mise à jour . Voici le code:
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;
}
}
Pourquoi les solutions ci-dessus si complexes? On dirait que overkill . Je résous simplement en remplaçant l'ancienne référence à new dans la classe étendue à partir de FragmentPagerAdapter
@Override
public Object instantiateItem(ViewGroup container, int position) {
frags[position] = (Fragment) super.instantiateItem(container, position);
return frags[position];
}
Le code de tous les adaptateurs ressemble à ceci
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];
}
}
Le problème est que getItem()
dans FragmentPageAdapter
porte un nom incorrect. Il aurait dû s'appeler createItem()
. En raison de son fonctionnement, getItem()
sert à créer des fragments et il n'est pas prudent de l'appeler pour interroger/trouver un fragment.
Ma recommandation est de faire une copie de FragmentPagerAdapter actuel et de le changer de cette façon:
Ajouter:
public abstract Fragment createFragment(int position);
Et remplacez getItem par:
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;
}
}
Enfin, ajoutez cela à instantiateItem:
if(containerId==null)
containerId = container.getId();
else if(containerId!=container.getId())
throw new RuntimeException("Container id not expected to change");
Code complet à this Gist
Je pense que cette implémentation est plus sûre et plus facile à utiliser et offre les mêmes performances que l'adaptateur d'origine des ingénieurs de Google.
Mon code:
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's 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());
}
}
}
En ce qui concerne la solution de simekadam , mFragments n'est pas renseigné dans instantiateItem et nécessite mFragments.put(position, fragment);
dedans ou vous allez vous retrouver avec cette erreur: Essayer de supprimer un fragment de la vue me donne une exception NullPointerException sur mNextAnim .