55 votes

Comment ajouter un Fragment à l'intérieur d'un ViewPager en utilisant Nested Fragment (Android 4.2)

J'ai un ViewPager avec trois Fragments chacun montre un List (ou Grid ).

Dans le nouveau Android API level 17 (Jelly Bean 4.2), l'une des caractéristiques est la suivante Fragments imbriqués . La description de la nouvelle fonctionnalité dit :

si vous utilisez ViewPager pour créer des fragments qui glissent de gauche à droite et qui et qui occupent la majorité de l'espace de l'écran, vous pouvez désormais insérer des fragments dans chaque page de fragments.

Donc, si je comprends bien, maintenant je peux créer une ViewPager avec Fragments (avec un bouton à l'intérieur par exemple) à l'intérieur, et lorsque l'utilisateur appuie sur le bouton, il affiche un autre fichier Fragment sans perdre le ViewPager en utilisant cette nouvelle fonctionnalité.

J'ai passé ma matinée à essayer d'implémenter ceci de plusieurs façons différentes, mais je n'arrive pas à le faire fonctionner... Quelqu'un peut-il ajouter un exemple simple de mise en œuvre ?

PS : Je ne suis intéressé que par cette façon de faire, avec getChildFragmentManager pour apprendre comment ça marche.

3 votes

Je vais travailler sur un échantillon pour ce début de semaine prochaine. Si personne n'a répondu d'ici là, j'essaierai de poster quelque chose à ce moment-là.

100voto

Marco Points 2932

En supposant que vous avez créé les mises en page xml correctes. Il est maintenant très simple d'afficher des fragments dans un ViewPager qui est hébergé par un autre Fragment.

Le fragment suivant est un fragment parent qui affiche des fragments enfants :

class ParentViewPagerFragment : Fragment() {

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val root = inflater.inflate(R.layout.fragment_parent_viewpager, container, false)

    val viewPager = root.findViewById(R.id.viewPager) as ViewPager
    // Important: Must use the child FragmentManager or you will see side effects.
    viewPager.adapter = MyAdapter(childFragmentManager)

    val tabStrip = root.findViewById<TabLayout>(R.id.pagerTabStrip)
    tabStrip.setupWithViewPager(viewPager)

    return root
  }

  class MyAdapter internal constructor(fm: FragmentManager) : FragmentPagerAdapter(fm) {

    override fun getCount(): Int = 4

    override fun getItem(position: Int): Fragment {
      val args = Bundle().apply { putInt(ChildFragment.POSITION_KEY, position) }
      return ChildFragment.newInstance(args)
    }

    override fun getPageTitle(position: Int): CharSequence = "Tab $position"
  }

  companion object {
    val TAG: String = ParentViewPagerFragment::class.java.name
  }
}

Il est important d'utiliser Fragment.getChildFragmentManager() lors de l'instanciation du FragmentPagerAdapter. Notez également que vous ne pouvez pas utiliser Fragment.setRetainInstance() sur les fragments d'enfants ou vous obtiendrez une exception. Les importations ont été omises pour des raisons de brièveté.

Le code source peut être trouvé à l'adresse suivante : https://github.com/marcoRS/nested-fragments

1 votes

Je rencontre des problèmes en faisant cela (Fragment contenant le ViewPager, c'est-à-dire) avec SherlockFragments et support.v4 release11, le contenu du ViewPager est défini via l'adaptateur mais les fragments enfants ne s'affichent pas :(

0 votes

Bonjour Straya. J'ai testé mon exemple avec SherlockFragments et je n'ai pas vu d'erreur. Quelle est l'erreur que vous voyez ?

1 votes

Heya, j'ai aussi Android-ViewPagerIndicator dans le mix. Le ViewPager et le ViewPagerIndicator sont affichés (j'ai un TextView au-dessus du ViewPager dans son Fragment donc je peux voir que ce Fragment est affiché), mais les Fragments enfants du ViewPager (fournis via un FragmentPagerAdapter) ne sont pas affichés. Je peux passer d'une page à l'autre, mettre à jour l'indicateur, mais toujours pas les fragments enfants.

12voto

Luksprog Points 52767

Editar: Si vous souhaitez remplacer l'ensemble du contenu d'une page dans un fichier ViewPager vous pouvez toujours utiliser des fragments imbriqués, mais certaines modifications sont nécessaires. Regardez l'exemple ci-dessous (le FragmentActivity en fixant le ViewPager y el PagerAdapter sont les mêmes que celles du bout de code précédent) :

// this will act as a fragment container, representing one page in the ViewPager
public static class WrapperFragment extends Fragment implements
        ReplaceListener {

    public static WrapperFragment newInstance(int position) {
        WrapperFragment wp = new WrapperFragment();
        Bundle args = new Bundle();
        args.putInt("position", position);
        wp.setArguments(args);
        return wp;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        FrameLayout fl = new FrameLayout(getActivity());
        fl.setId(10000);
        if (getChildFragmentManager().findFragmentByTag("initialTag") == null) {
            InitialInnerFragment iif = new InitialInnerFragment();
            Bundle args = new Bundle();
            args.putInt("position", getArguments().getInt("position"));
            iif.setArguments(args);
            getChildFragmentManager().beginTransaction()
                    .add(10000, iif, "initialTag").commit();
        }
        return fl;
    }

    // required because it seems the getChildFragmentManager only "sees"
    // containers in the View of the parent Fragment.   
    @Override
    public void onReplace(Bundle args) {
        if (getChildFragmentManager().findFragmentByTag("afterTag") == null) {
            InnerFragment iif = new InnerFragment();
            iif.setArguments(args);
            getChildFragmentManager().beginTransaction()
                    .replace(10000, iif, "afterTag").addToBackStack(null)
                    .commit();
        }
    }

}

// the fragment that would initially be in the wrapper fragment
public static class InitialInnerFragment extends Fragment {

    private ReplaceListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mListener = (ReplaceListener) this.getParentFragment();
        LinearLayout ll = new LinearLayout(getActivity());
        Button b = new Button(getActivity());
        b.setGravity(Gravity.CENTER_HORIZONTAL);
        b.setText("Frame " + getArguments().getInt("position"));
        b.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Bundle args = new Bundle();
                args.putInt("positionInner",
                        getArguments().getInt("position"));
                if (mListener != null) {
                    mListener.onReplace(args);
                }
            }
        });
        ll.setOrientation(LinearLayout.VERTICAL);
        ll.addView(b, new LinearLayout.LayoutParams(250,
                LinearLayout.LayoutParams.WRAP_CONTENT));
        return ll;
    }

}

public static class InnerFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        TextView tv = new TextView(getActivity());
        tv.setText("InnerFragment in the outher Fragment with position "
                + getArguments().getInt("positionInner"));
        return tv;
    }

}

public interface ReplaceListener {
    void onReplace(Bundle args);
}

A première vue, cela fonctionne, mais des problèmes peuvent apparaître car je ne l'ai pas beaucoup testé.

Quelqu'un peut-il montrer un exemple simple de la manière de procéder ?

L'utilisation de fragments imbriqués semble assez facile, en attendant que Commonsware propose un exemple plus élaboré, vous pouvez essayer le code ci-dessous :

public class NestedFragments extends FragmentActivity {

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        ViewPager vp = new ViewPager(this);
        vp.setId(5000);
        vp.setAdapter(new MyAdapter(getSupportFragmentManager()));
        setContentView(vp);
    }

    private static class MyAdapter extends FragmentPagerAdapter {

        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return WrapperFragment.newInstance(position);
        }

        @Override
        public int getCount() {
            return 8;
        }
    }

    public static class WrapperFragment extends Fragment {

        public static WrapperFragment newInstance(int position) {
            WrapperFragment wp = new WrapperFragment();
            Bundle args = new Bundle();
            args.putInt("position", position);
            wp.setArguments(args);
            return wp;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            LinearLayout ll = new LinearLayout(getActivity());
            FrameLayout innerFragContainer = new FrameLayout(getActivity());
            innerFragContainer.setId(1111);
            Button b = new Button(getActivity());
            b.setText("Frame " + getArguments().getInt("position"));
            b.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    InnerFragment innerFragment = new InnerFragment();
                    Bundle args = new Bundle();
                    args.putInt("positionInner",
                            getArguments().getInt("position"));
                    innerFragment.setArguments(args);
                    FragmentTransaction transaction = getChildFragmentManager()
                            .beginTransaction();
                    transaction.add(1111, innerFragment).commit();
                }
            });
            ll.setOrientation(LinearLayout.VERTICAL);
            ll.addView(b, new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            ll.addView(innerFragContainer, new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.MATCH_PARENT));
            return ll;
        }

    }

    public static class InnerFragment extends Fragment {

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            TextView tv = new TextView(getActivity());
            tv.setText("InnerFragment in the outher Fragment with position "
                    + getArguments().getInt("positionInner"));
            return tv;
        }

    }

}

J'ai été paresseux et j'ai tout fait en code mais je suis sûr que cela peut fonctionner avec des mises en page xml gonflées.

1 votes

Merci beaucoup, j'essaye et refactorise pour l'utiliser avec des fichiers de mise en page XML et cela semble fonctionner correctement ;)

0 votes

Je pense que là où vous utilisez transaction.add(1111, innerFragment).commit(); doit utiliser le replace car chaque fois que le bouton est pressé, une mise en page est ajoutée à la hiérarchie. De plus, j'ai remanié l'exemple pour utiliser des layouts XML et j'ai essayé de changer pour remplacer todo le contenu de la page. Mais je n'ai pas encore réussi à le faire, toujours les Button rester au sommet. Une suggestion ?

0 votes

@Luksprog : Ce n'est pas le scénario auquel je pensais. Je vais travailler sur un exemple où la fonction ViewPager est lui-même hébergé par un fragment et a des fragments pour ses pages. Ce debe il s'agit juste de passer getChildFragmentManager() a la PagerAdapter mais je veux d'abord l'essayer. Votre exemple démontre une ViewPager détenir des fragments qui eux-mêmes détiennent d'autres fragments - parfaitement raisonnable, mais pas ce à quoi je pensais quand j'ai lu la question.

6voto

Rukmal Dias Points 1077

J'ai créé un ViewPager avec 3 éléments et 2 sous-éléments pour les indices 2 et 3 et voici ce que je voulais faire

enter image description here

J'ai implémenté ceci avec l'aide des questions et réponses précédentes de StackOverFlow et voici le lien.

ViewPagerChildFragments

0 votes

Il y a quelques bogues avec ce code, comme le plantage lors d'un changement d'orientation. Les avez-vous corrigés ?

0 votes

Quelqu'un a-t-il réussi à corriger le bug de ce code qui fait que l'application se plante lors d'un changement d'orientation ?

0voto

ParsaCoding Points 1

Utilisez facilement le composant de navigation dans votre application ! puis dans votre listener, copiez ce code :

findNavController().navigate('Your action Id)
  • Votre identifiant d'action que l'IDE a créé automatiquement pour vous.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X