111 votes

Comment obtenir des fragments existants en utilisant le FragmentPagerAdapter ?

J'ai du mal à faire en sorte que mes fragments communiquent entre eux par l'intermédiaire de l'interface utilisateur. Activity qui utilise le FragmentPagerAdapter comme une classe d'aide qui met en œuvre la gestion des onglets et tous les détails de la connexion d'une carte de crédit. ViewPager avec associé TabHost . J'ai mis en œuvre FragmentPagerAdapter tout comme il est fourni par le projet d'exemple Android Support4Demos .

La question principale est la suivante : comment puis-je obtenir un fragment particulier à partir de FragmentManager alors que je n'ai ni Id ni Tag ? FragmentPagerAdapter crée les fragments et génère automatiquement l'identité et les étiquettes.

85voto

Ismar Slomic Points 797

J'ai trouvé la réponse à ma question dans le post suivant : réutilisation des fragments dans un adaptateur de fragmentation

J'ai appris quelques petites choses :

  1. getItem(int position) dans le FragmentPagerAdapter est un nom plutôt trompeur de ce que fait réellement cette méthode. Elle crée de nouveaux fragments, et ne retourne pas les fragments existants. Dans ce sens, la méthode devrait être renommée en quelque chose comme createItem(int position) dans le SDK Android. Donc cette méthode ne nous aide pas à obtenir des fragments.
  2. Basé sur l'explication dans le post le support FragmentPagerAdapter contient une référence aux anciens fragments vous devez laisser la création des fragments à l'équipe de l'entreprise. FragmentPagerAdapter et en ce sens vous n'avez aucune référence aux Fragments ou à leurs étiquettes. Cependant, si vous avez une balise de fragment, vous pouvez facilement récupérer la référence à celle-ci à partir de la balise FragmentManager en appelant findFragmentByTag() . Nous avons besoin d'un moyen de trouver la balise d'un fragment à une position donnée de la page.

Solution

Ajoutez la méthode d'aide suivante dans votre classe pour récupérer la balise du fragment et l'envoyer à l'utilisateur. findFragmentByTag() méthode.

private String getFragmentTag(int viewPagerId, int fragmentPosition)
{
     return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}

REMARQUE : cette méthode est identique à celle utilisée par le FragmentPagerAdapter à utiliser lors de la création de nouveaux fragments. Voir ce lien http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/Android/support/v2/app/FragmentPagerAdapter.java#104

18voto

morgwai Points 81

Vous n'avez pas besoin de remplacer instantiateItem ni se fonder sur la compatibilité avec les systèmes internes de makeFragmentName en créant manuellement des balises de fragment.
instantiateItem est un public afin de pouvoir l'appeler dans onCreate de votre activité entourée d'appels à startUpdate y finishUpdate comme décrit dans PagerAdapter javadoc :

Un appel à la méthode startUpdate(ViewGroup) du PagerAdapter indique que le contenu du ViewPager est sur le point de changer. Un ou plusieurs appels à instantiateItem(ViewGroup, int) et/ou destroyItem(ViewGroup, int, Object) suivront, et la fin d'une mise à jour sera signalée par un appel à finishUpdate(ViewGroup).

Vous pouvez alors, par le biais de ce qui précède, stocker les références aux instances de vos fragments sur des variables locales si nécessaire. Voir l'exemple :

public class MyActivity extends AppCompatActivity {

    Fragment0 tab0; Fragment1 tab1;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        tab0 = (Fragment0) adapter.instantiateItem(viewPager, 0);
        tab1 = (Fragment1) adapter.instantiateItem(viewPager, 1);
        adapter.finishUpdate(viewPager);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

        public MyPagerAdapter(FragmentManager manager) {super(manager);}

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

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
    }
}

instantiateItem essaiera d'abord d'obtenir des références à des instances de fragment existantes à partir de FragmentManager . Seulement s'ils n'existent pas encore, il en créera de nouveaux à l'aide de getItem de votre adaptateur et de les "stocker" dans le fichier FragmentManager pour toute utilisation future.

MISE À JOUR 05/2022 : en fonction de ce commentaire la partie ci-dessous peut conduire à des performances sous-optimales dans l'implémentation actuelle.

En suivant la javadoc ci-dessus, vous devez toujours appeler instantiateItem pour tous vos onglets entourés de startUpdate / finishUpdate dans votre onCreate même si vous n'avez pas besoin d'obtenir des références à vos fragments :

    adapter.startUpdate(viewPager);
    // ignoring return values of the below 2 calls, just side effects matter:
    adapter.instantiateItem(viewPager, 0);
    adapter.instantiateItem(viewPager, 1);
    adapter.finishUpdate(viewPager);

Si vous ne le faites pas, vous risquez que vos instances de fragments ne soient jamais engagé a FragmentManager : quand votre activité passe au premier plan instantiateItem sera appelé automatiquement pour obtenir vos fragments, mais startUpdate / finishUpdate mai pas (selon les détails d'implémentation) et ce qu'ils font fondamentalement est de commencer/commettre un FragmentTransaction .
Ce site mai font que les références aux instances de fragment créées sont perdues très rapidement (par exemple lorsque vous faites pivoter votre écran) et recréées beaucoup plus souvent que nécessaire. Selon la "lourdeur" de vos fragments, cela peut avoir des conséquences non négligeables sur les performances.
De plus, dans ce cas, les instances des fragments stockés sur les vars locaux mai deviennent périmés : si la plate-forme Android tente de les obtenir à partir de FragmentManager pour quelque raison que ce soit, il échouera et créera et utilisera donc de nouvelles variables, alors que vos variables feront toujours référence aux anciennes.

13voto

personne3000 Points 512

La façon dont je l'ai fait est de définir une table de hachage de WeakReferences comme suit :

protected Hashtable<Integer, WeakReference<Fragment>> fragmentReferences;

Puis j'ai écrit la méthode getItem() comme ceci :

@Override
public Fragment getItem(int position) {

    Fragment fragment;
    switch(position) {
    case 0:
        fragment = new MyFirstFragmentClass();
        break;

    default:
        fragment = new MyOtherFragmentClass();
        break;
    }

    fragmentReferences.put(position, new WeakReference<Fragment>(fragment));

    return fragment;
}

Ensuite, vous pouvez écrire une méthode :

public Fragment getFragment(int fragmentId) {
    WeakReference<Fragment> ref = fragmentReferences.get(fragmentId);
    return ref == null ? null : ref.get();
}

Cette méthode semble bien fonctionner et je la trouve un peu moins compliquée que la méthode du

"android:switcher:" + viewId + ":" + position

car il ne dépend pas de la façon dont le FragmentPagerAdapter est implémenté. Bien sûr, si le fragment a été libéré par le FragmentPagerAdapter ou s'il n'a pas encore été créé, getFragment retournera null.

Si quelqu'un trouve quelque chose à redire à cette approche, ses commentaires sont plus que bienvenus.

10voto

Pepijn Points 336

J'ai créé cette méthode qui fonctionne pour moi pour obtenir une référence au fragment actuel.

public static Fragment getCurrentFragment(ViewPager pager, FragmentPagerAdapter adapter) {
    try {
        Method m = adapter.getClass().getSuperclass().getDeclaredMethod("makeFragmentName", int.class, long.class);
        Field f = adapter.getClass().getSuperclass().getDeclaredField("mFragmentManager");
        f.setAccessible(true);
        FragmentManager fm = (FragmentManager) f.get(adapter);
        m.setAccessible(true);
        String tag = null;
        tag = (String) m.invoke(null, pager.getId(), (long) pager.getCurrentItem());
        return fm.findFragmentByTag(tag);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } 
    return null;
}

5voto

Eugene Points 81

FragmentStateAdapter pour ViewPager2 UPDATE

FragmentStateAdapter a createFragment() au lieu de getItem() . Et il n'y a pas de méthode telle que instantiateView() .

Donc quand l'hébergement Activity / Fragment est recréé après le changement de configuration, createFragment() n'est pas appelé. Cela signifie que les fragments de l'adaptateur ne sont pas créés à nouveau, mais que leurs instances sont récupérées dans la base de données de l'adaptateur. FragmentManager . Donc, si vous avez besoin de travailler sur les fragments, vous pouvez simplement les obtenir à partir de FragmentManager .

Création d'un adaptateur :

CustomFragmentAdapter adapter = new CustomFragmentAdapter(getChildFragmentManager(), getLifecycle());

Récupération de fragments de FragmentManager après Activity / Fragment recharger :

    FragmentManager manager = getChildFragmentManager();
    ArrayList<Fragment> fragments = new ArrayList<>(manager.getFragments());

    for (Fragment fr : fragments) {

        // do something
    }

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