Comment obtenir des fragments existants en utilisant FragmentPagerAdapter

j'ai du mal à faire communiquer mes fragments les uns avec les autres à travers le Activity , qui utilise le FragmentPagerAdapter , comme classe d'aide qui implémente la gestion des onglets et tous les détails de connexion d'un ViewPager avec associé TabHost . J'ai mis en œuvre FragmentPagerAdapter tout comme il est fourni par L'Android sample project Support4Demos .

la question principale est Comment puis-je obtenir un fragment particulier à partir de FragmentManager quand je n'ai ni pièce d'identité ni étiquette? FragmentPagerAdapter crée les fragments et génère automatiquement L'Id et les Tags.

79
demandé sur Wonil 2012-12-26 05:07:56

14 réponses

Résumé du problème

Note: dans cette réponse, je vais faire référence à FragmentPagerAdapter et à son code source. Mais la solution générale devrait également s'appliquer à FragmentStatePagerAdapter .

si vous lisez ceci vous savez probablement déjà que FragmentPagerAdapter / FragmentStatePagerAdapter est destiné à créer Fragments pour votre ViewPager , mais sur la récréation D'activité (que ce soit à partir d'une rotation de l'appareil ou le système tuer votre application à ces Fragments ne seront pas créés à nouveau, mais à la place leurs instances extraites de la FragmentManager . Maintenant, dites que votre Activity a besoin d'une référence à ces Fragments pour travailler dessus. Vous n'avez pas de id ou tag pour ceux créés Fragments parce que FragmentPagerAdapter mettez-les en interne . Donc, le problème est de savoir comment obtenir une référence sans cette information...

Problème avec les solutions actuelles: en s'appuyant sur le code interne

beaucoup de solutions que j'ai vu sur cette question et des questions similaires reposent sur l'obtention d'une référence à l'existant Fragment en appelant FragmentManager.findFragmentByTag() et imitant le étiquette créée à l'interne: "android:switcher:" + viewId + ":" + id . Le problème avec ça, c'est que tu te fies au code source interne, qui comme nous tous savoir n'est pas garanti de rester le même pour toujours. Les ingénieurs Android de Google pourraient facilement décider de changer la structure tag qui briserait votre code vous laissant incapable de trouver une référence à l'existant Fragments .

solution Alternative sans compter sur interne tag

voici un exemple simple de la façon d'obtenir une référence à la Fragments retourné par FragmentPagerAdapter qui ne repose pas sur l'interne tags sur le Fragments . La clé est de passer outre instantiateItem() et sauvegardez les références dans à la place de dans getItem() .

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

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

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

ou si vous préférez travailler avec des tags à la place de membre de la classe des variables/les références à la Fragments , vous pouvez aussi saisir le tags , établi par le FragmentPagerAdapter de la même manière: REMARQUE: cette ne s'applique pas à FragmentStatePagerAdapter car il ne définit pas tags lors de la création de son Fragments .

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

notez que cette méthode ne repose pas sur l'imitation du tag interne défini par FragmentPagerAdapter et utilise à la place APIs appropriés pour les extraire. De cette façon, même si le tag change dans les versions futures du SupportLibrary , vous serez toujours en sécurité.


N'oubliez pas que selon la conception de votre Activity , le Fragments vous essayez de travailler sur Mai ou peut ne pas exister encore, donc vous devez en tenir compte en faisant des null vérifications avant d'utiliser vos références.

aussi, si à la place vous travaillez avec FragmentStatePagerAdapter , alors vous ne voulez pas garder des références dures à votre Fragments parce que vous pourriez avoir beaucoup d'entre eux et des références dures les garderaient inutilement dans la mémoire. Plutôt sauvegardez les références Fragment dans les variables WeakReference au lieu des références standard. Comme ceci:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
149
répondu Tony Chan 2015-03-26 20:26:29

j'ai trouvé la réponse à ma question basée sur le post suivant: réutiliser des fragments dans un fragmentpageradapter

peu de choses que j'ai apprises:

  1. getItem(int position) dans le FragmentPagerAdapter est plutôt trompeur nom de ce que cette méthode ne fait. Il crée de nouveaux fragments, et ne renvoie pas les fragments existants. Dans ce sens, la méthode devrait être renommée à quelque chose comme createItem(int position) dans le SDK Android. Cette méthode ne nous aide donc pas à obtenir des fragments.
  2. basé sur l'explication dans le post support FragmentPagerAdapterholds référence à de vieux fragments vous devez laisser la création des fragments à la FragmentPagerAdapter et dans le sens où vous n'avez aucune référence aux Fragments ou leurs étiquettes. Si vous avez l'étiquette de fragment, vous pouvez facilement récupérer la référence à partir du FragmentManager en appelant findFragmentByTag() . Nous avons besoin d'un moyen pour trouver l'étiquette d'un fragment à la position donnée de la page.

Solution

ajouter la méthode d'aide suivante dans votre classe pour récupérer l'étiquette de fragment et l'Envoyer à la méthode findFragmentByTag() .

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

NOTE! C'est la même méthode que FragmentPagerAdapter utilise pour créer 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

76
répondu Ismar Slomic 2017-05-23 12:34:41

j'ai créé cette méthode qui fonctionne 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;
}
9
répondu Pepijn 2013-03-14 12:04:56

la façon dont je l'ai fait est défini un Hashtable de faibles références 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;
}

alors vous pouvez écrire une méthode:

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

Cela semble bien fonctionner et je le trouve un peu moins hacky que le

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

trick, car il ne repose pas sur la façon dont le FragmentPagerAdapter est mis en œuvre. Bien sûr, si le fragment a a été libéré par FragmentPagerAdapter ou s'il n'a pas encore été créé, getFragment retournera null.

si quelqu'un trouve quelque chose qui ne va pas avec cette approche, les commentaires sont plus que bienvenus.

9
répondu personne3000 2014-06-21 04:08:13

vous n'avez pas besoin de surcharger instantiateItem ni de compter sur la compatibilité de créer des étiquettes de fragment avec la méthode interne makeFragmentName . instantiateItem est une public méthode de sorte que vous pouvez (et en fait vous devrait ) l'appeler dans onCreate méthode de votre activité pour obtenir des références à des instances de vos fragments et de les stocker sur les var locales si vous avez besoin. N'oubliez pas d'entourer un ensemble d'appels instantiateItem avec startUpdate et finishUpdate méthodes décrites dans PagerAdapter javadoc :

un appel à la mise à jour de la méthode PagerAdapter(ViewGroup) 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).

donc pour exemple c'est la façon de stocker des références à vos fragments d'onglet dans onCreate méthode:

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 tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.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 va d'abord essayer d'obtenir des références aux instances de fragment existantes de FragmentManager . Seulement s'ils n'existent pas encore, il en créera de nouveaux en utilisant la méthode getItem de votre adaptateur et les stockera dans le FragmentManager pour une utilisation future.

quelques informations supplémentaires:

Si vous n'appelez pas instantiateItem entouré par startUpdate / finishUpdate dans votre méthode onCreate alors vous risquez que vos instances de fragments ne seront jamais commis à FragmentManager : lorsque votre activité devient premier plan instantiateItem sera appelé automatiquement pour obtenir vos fragments, mais startUpdate / finishUpdate pas (selon les détails de la mise en œuvre) et ce qu'ils font essentiellement est commencer / commettre un FragmentTransaction .

Ce peut avoir pour résultat que les références aux instances de fragment créées soient perdues très rapidement (par exemple lorsque vous tournez 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 la performance.

Plus important encore, dans de tels cas, les fragments stockés sur les var locales deviendront périmés: android plate-forme n'a pas été en mesure d'obtenir les mêmes instances à partir de FragmentManager il peut créer et utiliser de nouvelles instances, alors que vos vars seront encore référencement des anciennes.

6
répondu morgwai 2018-02-27 05:34:54

la solution suggérée par @personne3000 est sympa, mais elle a un problème: quand l'activité passe à l'arrière-plan et se fait tuer par le système (afin d'obtenir un peu de mémoire libre) puis restaurée, la fragmentReferences sera vide, parce que getItem ne s'appellerait pas.

la classe ci-dessous s'occupe d'une telle situation:

public abstract class AbstractHolderFragmentPagerAdapter<F extends Fragment> extends FragmentPagerAdapter {

    public static final String FRAGMENT_SAVE_PREFIX = "holder";
    private final FragmentManager fragmentManager; // we need to store fragment manager ourselves, because parent's field is private and has no getters.

    public AbstractHolderFragmentPagerAdapter(FragmentManager fm) {
        super(fm);
        fragmentManager = fm;
    }

    private SparseArray<WeakReference<F>> holder = new SparseArray<WeakReference<F>>();

    protected void holdFragment(F fragment) {
        holdFragment(holder.size(), fragment);
    }

    protected void holdFragment(int position, F fragment) {
        if (fragment != null)
            holder.put(position, new WeakReference<F>(fragment));
    }

    public F getHoldedItem(int position) {
        WeakReference<F> ref = holder.get(position);
        return ref == null ? null : ref.get();
    }

    public int getHolderCount() {
        return holder.size();
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) { // code inspired by Google's FragmentStatePagerAdapter implementation
        super.restoreState(state, loader);
        Bundle bundle = (Bundle) state;
        for (String key : bundle.keySet()) {
            if (key.startsWith(FRAGMENT_SAVE_PREFIX)) {
                int index = Integer.parseInt(key.substring(FRAGMENT_SAVE_PREFIX.length()));
                Fragment f = fragmentManager.getFragment(bundle, key);
                holdFragment(index, (F) f);
            }
        }
    }

    @Override
    public Parcelable saveState() {
        Bundle state = (Bundle) super.saveState();
        if (state == null)
            state = new Bundle();

        for (int i = 0; i < holder.size(); i++) {
            int id = holder.keyAt(i);
            final F f = getHoldedItem(i);
            String key = FRAGMENT_SAVE_PREFIX + id;
            fragmentManager.putFragment(state, key, f);
        }
        return state;
    }
}
2
répondu netimen 2014-08-27 08:34:30

le principal obstacle à l'obtention d'une poignée aux fragments est que vous ne pouvez pas compter sur getItem(). Après un changement d'orientation, les références aux fragments seront nulles et getItem() n'est plus appelé.

Voici une approche qui ne repose pas sur la mise en œuvre de FragmentPagerAdapter pour obtenir l'étiquette. Override instantiateItem() qui renvoie le fragment créé à partir de getItem () ou trouvé à partir du gestionnaire de fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object value =  super.instantiateItem(container, position);

    if (position == 0) {
        someFragment = (SomeFragment) value;
    } else if (position == 1) {
        anotherFragment = (AnotherFragment) value;
    }

    return value;
}
2
répondu Adam 2015-06-22 20:16:21

j'utilise toujours cette classe de base, quand j'ai besoin d'accéder à des fragments d'enfant ou primaire (actuellement visible) fragment. Il ne s'appuie sur aucun détail d'implémentation et s'occupe des changements de cycle de vie, parce que les méthodes écrasées sont appelées dans les deux cas - quand une nouvelle instance de fragment est créée et quand l'instance est reçue de FragmentManager.

public abstract class FragmentPagerAdapterExt extends FragmentPagerAdapter {

    private final ArrayList<Fragment> mFragments;
    private Fragment mPrimaryFragment;

    public FragmentPagerAdapterExt(FragmentManager fm) {
        super(fm);
        mFragments = new ArrayList<>(getCount());
    }

    @Override public Object instantiateItem(ViewGroup container, int position) {
        Object object = super.instantiateItem(container, position);
        mFragments.add((Fragment) object);
        return object;
    }

    @Override public void destroyItem(ViewGroup container, int position, Object object) {
        mFragments.remove(object);
        super.destroyItem(container, position, object);
    }

    @Override public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
        mPrimaryFragment = (Fragment) object;
    }

    /** Returns currently visible (primary) fragment */
    public Fragment getPrimaryFragment() {
        return mPrimaryFragment;
    }

    /** Returned list can contain null-values for not created fragments */
    public List<Fragment> getFragments() {
        return Collections.unmodifiableList(mFragments);
    }
}
2
répondu sergej shafarenka 2016-03-02 21:25:37

j'ai réussi à résoudre ce problème en utilisant des ID au lieu de tags. (J'utilise J'ai défini FragmentStatePagerAdapter qui utilise mes Fragments personnalisés dans lesquels j'ai surmonté la méthode onAttach, où vous sauvegardez l'id quelque part:

@Override
public void onAttach(Context context){
    super.onAttach(context);
    MainActivity.fragId = getId();
}

et ensuite vous accédez simplement au fragment facilement à l'intérieur de l'activité:

Fragment f = getSupportFragmentManager.findFragmentById(fragId);
1
répondu user2740905 2015-10-03 15:01:44

Voir ce post sur le retour des fragments de la FragmentPagerAdapter. Il compte sur vous, sachant que l'index de votre fragment - mais ce serait fixé getItem() (à l'instanciation uniquement)

0
répondu Community 2017-05-23 12:18:15

je ne sais pas si c'est la meilleure approche, mais rien d'autre n'a fonctionné pour moi. Toutes les autres options, y compris getActiveFragment retourné null ou causé le plantage de l'application.

j'ai remarqué que sur la rotation de l'écran le fragment était attaché, donc je l'ai utilisé pour renvoyer le fragment à l'activité.

dans le fragment:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnListInteractionListener) activity;
        mListener.setListFrag(this);
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

puis dans l'activité:

@Override
public void setListFrag(MyListFragment lf) {
    if (mListFragment == null) {
        mListFragment = lf;
    }
}

et enfin en activité onCreate ():

if (savedInstanceState != null) {
    if (mListFragment != null)
        mListFragment.setListItems(items);
}

cette approche attache le fragment visible réel à l'activité sans en créer un nouveau.

0
répondu TacoEater 2017-02-17 16:54:37

Je ne suis pas sûr que ma méthode était la bonne ou la meilleure façon de le faire car je suis un débutant relatif avec Java/Android, mais cela a fonctionné (je suis sûr que cela viole les principes orientés objet, mais aucune autre solution n'a fonctionné pour mon cas d'utilisation).

j'avais une activité d'hébergement qui utilisait un ViewPager avec un FragmentStatePagerAdapter. Pour obtenir des références aux Fragments créés par FragmentStatePagerAdapter, j'ai créé une interface de rappel dans la classe fragment.:

public interface Callbacks {
    public void addFragment (Fragment fragment);
    public void removeFragment (Fragment fragment);
}

dans l'activité d'hébergement, j'ai implémenté l'interface et créé un Hasetlié pour garder la trace des fragments:

public class HostingActivity extends AppCompatActivity implements ViewPagerFragment.Callbacks {

    private LinkedHashSet<Fragment> mFragments = new LinkedHashSet<>();

    @Override
    public void addFragment (Fragment fragment) {
        mFragments.add(fragment);
    }

    @Override
    public void removeFragment (Fragment fragment) {
        mFragments.remove(fragment);
    }
}

dans la classe ViewPagerFragment I a ajouté les fragments à la liste dans onAttach et les a retirés dans onDetach:

public class ViewPagerFragment extends Fragment {

    private Callbacks mCallbacks;

    public interface Callbacks {
        public void addFragment (Fragment fragment);
        public void removeFragment (Fragment fragment);
    } 

    @Override
    public void onAttach (Context context) {
        super.onAttach(context);
        mCallbacks = (Callbacks) context;
        // Add this fragment to the HashSet in the hosting activity
        mCallbacks.addFragment(this);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        // Remove this fragment from the HashSet in the hosting activity
        mCallbacks.removeFragment(this);
        mCallbacks = null;
    }
}

dans le cadre de l'activité d'hébergement, vous pourrez désormais utiliser mFragments pour itérer à travers les fragments qui existent actuellement dans le FragmentStatePagerAdapter.

0
répondu sbearben 2018-05-23 03:23:04

cette classe fait l'affaire sans s'appuyer sur des étiquettes internes. Attention: les Fragments doivent être accessibles en utilisant la méthode getFragment et non la méthode getItem.

public class ViewPagerAdapter extends FragmentPagerAdapter {

    private final Map<Integer, Reference<Fragment>> fragments = new HashMap<>();
    private final List<Callable0<Fragment>> initializers = new ArrayList<>();
    private final List<String> titles = new ArrayList<>();

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

    void addFragment(Callable0<Fragment> initializer, String title) {
        initializers.add(initializer);
        titles.add(title);
    }

    public Optional<Fragment> getFragment(int position) {
        return Optional.ofNullable(fragments.get(position).get());
    }

    @Override
    public Fragment getItem(int position) {
        Fragment fragment =  initializers.get(position).execute();
        return fragment;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.put(position, new WeakReference<>(fragment));
        return fragment;
    }

    @Override
    public int getCount() {
        return initializers.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return titles.get(position);
    }
}
0
répondu Alex Miragall 2018-08-30 11:58:48

essayez ce code,

public class MYFragmentPAdp extends FragmentPagerAdapter {

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

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

     @Override
     public Fragment getItem(int position) {
         if (position == 0)
             Fragment fragment = new Fragment1();
         else (position == 1)
             Fragment fragment = new Fragment2();
         return fragment;
     }
}
-5
répondu Najib Puthawala 2012-12-26 11:03:21