Un fragment n'est pas rattaché à son ViewPager d'hébergement après son retour d'un autre fragment.
Une activité hébergeant un fragment dont la disposition contient un ViewPager (PageListFragment
dans l'exemple ci-dessous). Le ViewPager est rempli par un FragmentStateViewPagerAdapter. Les fragments uniques hébergés à l'intérieur du pager (PageFragment
dans l'exemple ci-dessous) peuvent ouvrir des listes de sous-pages, contenant un nouvel ensemble de pages.
Tout fonctionne bien tant que le bouton de retour n'est pas enfoncé. Dès que l'utilisateur ferme l'une des sous-listes de pages, la liste précédente est recréée, mais sans la page qui était affichée précédemment. Glisser à travers les autres pages de la PageList parent fonctionne toujours.
Un exemple d'application peut être trouvé sur github :
public class MainActivity extends FragmentActivity {
private static final String CURRENT_FRAGMENT = MainActivity.class.getCanonicalName() + ".CURRENT_FRAGMENT";
public static final String ARG_PARENTS = "Parents";
public void goInto(String mHostingLevel, String mPosition) {
Fragment hostingFragment = newHostingFragment(mHostingLevel, mPosition);
addFragment(hostingFragment);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addBaseFragment();
}
private void addBaseFragment() {
Fragment hostingFragment = newHostingFragment("", "");
addFragment(hostingFragment);
}
private Fragment newHostingFragment(String mHostingLevel, String oldPosition) {
Fragment hostingFragment = new PageListFragment();
Bundle args = new Bundle();
args.putString(ARG_PARENTS, mHostingLevel + oldPosition +" > ");
hostingFragment.setArguments(args);
return hostingFragment;
}
private void addFragment(Fragment hostingFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentSpace, hostingFragment, CURRENT_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
}
public class PageListFragment extends Fragment {
private String mParentString;
public PageListFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_hosting, container, false);
}
@Override
public void onResume() {
mParentString = getArguments().getString(MainActivity.ARG_PARENTS);
ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
super.onResume();
}
private static class SimpleFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
private String mHostingLevel;
public SimpleFragmentStatePagerAdapter(FragmentManager fm, String hostingLevel) {
super(fm);
this.mHostingLevel = hostingLevel;
}
@Override
public Android.support.v4.app.Fragment getItem(int position) {
PageFragment pageFragment = new PageFragment();
Bundle args = new Bundle();
args.putString(MainActivity.ARG_PARENTS, mHostingLevel);
args.putInt(PageFragment.ARG_POSITION, position);
pageFragment.setArguments(args);
return pageFragment;
}
@Override
public int getCount() {
return 5;
}
}
}
public class PageFragment extends Fragment {
public static final String ARG_POSITION = "Position";
private String mHostingLevel;
private int mPosition;
public PageFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_page, container, false);
setupTextView(contentView);
setupButton(contentView);
return contentView;
}
private void setupTextView(View contentView) {
mPosition = getArguments().getInt(ARG_POSITION);
mHostingLevel = getArguments().getString(MainActivity.ARG_PARENTS);
TextView text = (TextView) contentView.findViewById(R.id.textView);
text.setText("Parent Fragments " + mHostingLevel + " \n\nCurrent Fragment "+ mPosition);
}
private void setupButton(View contentView) {
Button button = (Button) contentView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openNewLevel();
}
});
}
protected void openNewLevel() {
MainActivity activity = (MainActivity) getActivity();
activity.goInto(mHostingLevel, Integer.toString(mPosition));
}
}
Après une longue enquête, cela s'avère être un problème avec le gestionnaire de fragments.
Lorsque vous utilisez une construction comme celle ci-dessus, la transaction de fragment pour rattacher le fragment à la liste de pages est ignorée en silence. C’est essentiellement le même problème qui cause
Java.lang.IllegalStateException: Recursive entry to executePendingTransactions
lorsque vous essayez de modifier les fragments à l'intérieur du FragmentPager.
La même solution, comme pour les problèmes avec cette erreur, est également applicable ici. Lors de la construction de FragmentStatePagerAdapter, fournissez le gestionnaire de fragments enfant correct.
Au lieu de
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
faire
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));
Voir aussi: github
Ce que Paul a omis de mentionner, c'est que si vous utilisez getChildFragmentManager, vous rencontrerez le problème "écran vide à contre-pression".
La hiérarchie dans mon cas était:
MainActivity
-> MainFragment
-> TabLayout
+ ViewPager
-> AccountsFragment
+ SavingsFragment
+ InvestmentsFragment
etc.
Le problème que j'avais était que je ne pouvais pas utiliser childFragmentManager
pour la raison qu'un clic sur l'élément Account view
(Qui réside dans l'un des Fragment
s du ViewPager
) nécessaire pour remplacer MainFragment
, c'est-à-dire tout l'écran.
L'utilisation de MainFragment
s Host Fragment
c'est-à-dire le passage de getFragmentManager()
a permis le remplacement, MAIS lors du pop-back, j'ai fini avec cet écran:
Cela était également apparent en regardant l'inspecteur de disposition où le ViewPager
est vide.
Apparemment, en regardant les Fragment
restaurés, vous remarquerez que leur View
est restauré mais ne correspondra pas à la hiérarchie de l'état sauté. Afin d'avoir un impact minimum et de ne pas forcer une recréation des Fragment
s j'ai réécrit FragmentStatePagerAdapter
avec les changements suivants:
J'ai copié le code entier de FragmentStatePagerAdapter
et changé
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
...
}
avec
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(f);
mCurTransaction.attach(f);
return f;
}
}
...
}
De cette façon, je m'assure effectivement que les Fragment
restaurés sont ré-attachés au ViewPager
.
Les fragments de page ne sont pas attachés lorsque vous revenez à l'écran du visualiseur car FragmentStatePagerAdapter ne les reconnecte pas. Pour contourner le problème, supprimez tous les fragments dans le viewpager après l'appel de popbackstack (), ce qui leur permettra d'être rajoutés par votre code initial.
[Cet exemple est écrit en Kotlin]
//Clear all fragments from the adapter before they are re-added.
for (i: Int in 0 until adapter.count) {
val item = childFragmentManager.findFragmentByTag("f$i")
if (item != null) {
adapter.destroyItem(container!!, i, item)
}
}