support FragmentPagerAdapter contient des références à d'anciens fragments

109

DERNIÈRES INFORMATIONS:

J'ai réduit mon problème à un problème avec le fragmentManager conservant des instances d'anciens fragments et mon viewpager étant désynchronisé avec mon FragmentManager. Consultez ce problème ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Je n'ai toujours aucune idée de comment résoudre ce problème. Aucune suggestion...

J'ai essayé de déboguer ceci pendant une longue période et toute aide serait grandement appréciée. J'utilise un FragmentPagerAdapter qui accepte une liste de fragments comme ceci:

List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName())); 
...
new PagerAdapter(getSupportFragmentManager(), fragments);

La mise en œuvre est standard. J'utilise ActionBarSherlock et la bibliothèque de calculabilité v4 pour les fragments.

Mon problème est qu'après avoir quitté l'application et ouvert plusieurs autres applications et y être revenu, les fragments perdent leur référence à FragmentActivity (c'est-à-dire. getActivity() == null). Je ne peux pas comprendre pourquoi cela se produit. J'ai essayé de définir manuellement setRetainInstance(true);mais cela n'aide pas. J'ai pensé que cela se produit lorsque mon FragmentActivity est détruit, mais cela se produit toujours si j'ouvre l'application avant de recevoir le message de journal. Y a-t-il des idées?

@Override
protected void onDestroy(){
    Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
    super.onDestroy();
}

L'adaptateur:

public class PagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragments;

    public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);

        this.fragments = fragments;

    }

    @Override
    public Fragment getItem(int position) {

        return this.fragments.get(position);

    }

    @Override
    public int getCount() {

        return this.fragments.size();

    }

}

Un de mes fragments a été dépouillé mais j'ai tout fait disparaître qui est dépouillé et ne fonctionne toujours pas ...

public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...

@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    handler = new Handler();    
    setHasOptionsMenu(true);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
    context = activity;
    if(context== null){
        Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
    }else{
        Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
    }

}

@Override
public void onActivityCreated(Bundle savedState) {
    super.onActivityCreated(savedState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.my_fragment,container, false);

    return v;
}


@Override
public void onResume(){
    super.onResume();
}

private void callService(){
    // do not call another service is already running
    if(startLoad || !canSet) return;
    // set flag
    startLoad = true;
    canSet = false;
    // show the bottom spinner
    addFooter();
    Intent intent = new Intent(context, MyService.class);
    intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
    context.startService(intent);
}

private ResultReceiver resultReceiver = new ResultReceiver(null) {
    @Override
    protected void onReceiveResult(int resultCode, final Bundle resultData) {
        boolean isSet = false;
        if(resultData!=null)
        if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
            if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
                removeFooter();
                startLoad = false;
                isSet = true;
            }
        }

        switch(resultCode){
        case MyService.STATUS_FINISHED: 
            stopSpinning();
            break;
        case SyncService.STATUS_RUNNING:
            break;
        case SyncService.STATUS_ERROR:
            break;
        }
    }
};

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    menu.clear();
    inflater.inflate(R.menu.activity, menu);
}

@Override
public void onPause(){
    super.onPause();
}

public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
    boolean loadMore = /* maybe add a padding */
        firstVisible + visibleCount >= totalCount;

    boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;

    if(away){
        // startLoad can now be set again
        canSet = true;
    }

    if(loadMore) 

}

public void onScrollStateChanged(AbsListView arg0, int state) {
    switch(state){
    case OnScrollListener.SCROLL_STATE_FLING: 
        adapter.setLoad(false); 
        lastState = OnScrollListener.SCROLL_STATE_FLING;
        break;
    case OnScrollListener.SCROLL_STATE_IDLE: 
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_IDLE;
        break;
    case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
        adapter.setLoad(true);
        if(lastState == SCROLL_STATE_FLING){
            // load the images on screen
        }

        lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
        break;
    }
}

@Override
public void onDetach(){
    super.onDetach();
    if(this.adapter!=null)
        this.adapter.clearContext();

    Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}

public void update(final int id, String name) {
    if(name!=null){
        getActivity().getSupportActionBar().setTitle(name);
    }

}

}

La méthode de mise à jour est appelée lorsqu'un utilisateur interagit avec un fragment différent et que getActivity renvoie null. Voici la méthode que l'autre fragment appelle ...

((MyFragment) pagerAdapter.getItem(1)).update(id, name);

Je pense que lorsque l'application est détruite puis créée à nouveau au lieu de simplement démarrer l'application jusqu'au fragment par défaut, l'application démarre, puis viewpager accède à la dernière page connue. Cela semble étrange, l'application ne devrait-elle pas simplement se charger dans le fragment par défaut?

Maurycy
la source
27
Le fragment d'Android est nul!
Hamidreza Sadegh
13
Dieu j'aime vos messages de journal: D
oli.G

Réponses:

120

Vous rencontrez un problème car vous instanciez et gardez des références à vos fragments en dehors de PagerAdapter.getItem, et essayez d'utiliser ces références indépendamment du ViewPager. Comme le dit Seraph, vous avez la garantie qu'un fragment a été instancié / ajouté dans un ViewPager à un moment donné - cela devrait être considéré comme un détail d'implémentation. Un ViewPager effectue le chargement paresseux de ses pages; par défaut, il ne charge que la page actuelle et celle de gauche et de droite.

Si vous mettez votre application en arrière-plan, les fragments qui ont été ajoutés au gestionnaire de fragments sont enregistrés automatiquement. Même si votre application est tuée, ces informations sont restaurées lorsque vous relancez votre application.

Considérez maintenant que vous avez vu quelques pages, Fragments A, B et C. Vous savez que celles-ci ont été ajoutées au gestionnaire de fragments. Parce que vous utilisez FragmentPagerAdapteret nonFragmentStatePagerAdapter , ces fragments seront toujours ajoutés (mais potentiellement détachés) lorsque vous faites défiler vers d'autres pages.

Considérez que vous avez ensuite l'arrière-plan de votre application, puis elle est tuée. Lorsque vous reviendrez, Android se souviendra que vous aviez les fragments A, B et C dans le gestionnaire de fragments et qu'il les recrée pour vous, puis les ajoute.Cependant, ceux qui sont maintenant ajoutés au gestionnaire de fragments ne sont PAS ceux que vous avez dans votre liste de fragments dans votre activité.

Le FragmentPagerAdapter n'essaiera pas d'appeler getPositions'il y a déjà un fragment ajouté pour cette position de page particulière. En fait, comme le fragment recréé par Android ne sera jamais supprimé, vous n'avez aucun espoir de le remplacer par un appel àgetPosition . Il est également assez difficile d'en obtenir une référence car il a été ajouté avec une balise qui vous est inconnue. C'est par conception; vous êtes découragé de jouer avec les fragments que le pager de vue gère. Vous devez effectuer toutes vos actions dans un fragment, communiquer avec l'activité et demander à basculer vers une page particulière, si nécessaire.

Maintenant, revenons à votre problème avec l'activité manquante. L'appel une pagerAdapter.getItem(1)).update(id, name)fois que tout cela s'est produit vous renvoie le fragment de votre liste, qui n'a pas encore été ajouté au gestionnaire de fragments , et il n'aura donc pas de référence d'activité. Je suggérerais que votre méthode de mise à jour modifie une structure de données partagée (éventuellement gérée par l'activité), puis lorsque vous passez à une page particulière, elle peut se dessiner en fonction de ces données mises à jour.

Antonyt
la source
1
J'aime votre solution car elle est très élégante et va probablement refactoriser mon code, mais comme vous l'avez dit, je voulais conserver 100% de la logique et des données dans le fragment. Votre solution nécessiterait à peu près de conserver toutes les données dans FragmentActivity, puis d'utiliser chaque fragment simplement pour gérer la logique d'affichage. Comme je l'ai dit, je préfère cela, mais l'interaction entre les fragments est super lourde et cela peut devenir ennuyeux à gérer. En tout cas Merci pour votre explication détaillée. Vous avez expliqué cela bien mieux que moi.
Maurycy
28
Bref: ne tenez jamais de référence à un fragment en dehors de l'adaptateur
passsy le
2
Alors, comment une instance Activity accède-t-elle à l'instance FragmentPagerAdapter si elle n'a pas instancié le FragmentPagerAdapter? Les futures instances ne réinstaureront-elles pas simplement le FragmentPagerAdapter et toutes ses instances de fragment? Le FragmentPagerAdapter doit-il implémenter toutes les interfaces de fragment pour gérer la communication entre les fragments?
Eric H.
la déclaration "Il est également assez difficile d'obtenir une référence dessus car il a été ajouté avec une balise qui vous est inconnue. C'est par conception; vous êtes découragé de jouer avec les fragments que le pager de vue gère . " c'est faux. il est très facile d'obtenir une référence en appelant instantiateItemet vous devriez le faire dans onCreatevotre activité. voir les détails ici: stackoverflow.com/questions/14035090/…
morgwai
en général, il est douteux d'instancier des fragments vous-même sans les charger, car dans un scénario «détruire et recréer», vous pourriez finir par gérer un fragment distinct de celui réellement recréé et affiché
hmac
108

J'ai trouvé une solution simple qui a fonctionné pour moi.

Faites en sorte que votre adaptateur de fragment étende FragmentStatePagerAdapter au lieu de FragmentPagerAdapter et remplacez la méthode onSave pour retourner null

@Override
public Parcelable saveState()
{
    return null;
}

Cela empêche Android de recréer le fragment


Un jour plus tard, j'ai trouvé une autre et meilleure solution.

Appelez setRetainInstance(true)tous vos fragments et enregistrez les références à eux quelque part. Je l'ai fait en variable statique dans mon activité, car il est déclaré comme singleTask et les fragments peuvent rester les mêmes tout le temps.

De cette façon, Android ne recrée pas de fragments mais utilise les mêmes instances.

mc.dev
la source
4
Merci, merci, merci beaucoup, je n'ai vraiment pas de mots pour vous remercier Mik, je chassais ce problème depuis 10 jours et j'ai essayé tant de méthodes.Mais ces quatre lignes magiques m'ont sauvé la vie :)
7
avoir une référence statique à des fragments et / ou des activités est une chose très risquée à faire, car elle peut provoquer des fuites de mémoire très facilement. Bien sûr, si vous faites attention, vous pouvez le gérer assez facilement en les définissant sur null lorsqu'ils ne sont plus nécessaires.
développeur android
Cela a fonctionné pour moi. Les fragments et leurs vues respectives conservent leurs liens après le crash et le redémarrage de l'application. Merci!
swebal
J'utilise setRetainInstance(true)avec FragmentPagerAdapter. Tout fonctionne bien. Mais lorsque je fais pivoter l'appareil, l'adaptateur contient toujours les fragments mais les fragments ne sont pas affichés. Les méthodes de cycle de vie des fragments ne sont pas non plus appelées. Quelqu'un peut-il aider?
Jonas
1
tu as sauvé ma journée !!! Recherche ce bogue depuis 3 jours jusqu'à présent. J'avais un Viewpager avec 2 fragments à l'intérieur d'une activité SingleTask et avec le drapeau "Ne pas garder les activités" activé. Merci beaucoup!!!!
matrice du
29

J'ai résolu ce problème en accédant à mes fragments directement via le FragmentManager au lieu de via le FragmentPagerAdapter comme ça. Je dois d'abord comprendre la balise du fragment généré automatiquement par le FragmentPagerAdapter ...

private String getFragmentTag(int pos){
    return "android:switcher:"+R.id.viewpager+":"+pos;
}

Ensuite, j'obtiens simplement une référence à ce fragment et je fais ce dont j'ai besoin, alors ...

Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);

À l'intérieur de mes fragments, j'ai défini le setRetainInstance(false);afin que je puisse ajouter manuellement des valeurs au bundle savedInstanceState.

@Override
public void onSaveInstanceState(Bundle outState) {
    if(this.my !=null)
        outState.putInt("myId", this.my.getId());

    super.onSaveInstanceState(outState);
}

puis dans OnCreate, je saisis cette clé et je restaure l'état du fragment si nécessaire. Une solution facile et difficile (pour moi du moins) à comprendre.

Maurycy
la source
J'ai un problème similaire à vous, mais je n'ai pas tout à fait compris votre explication, pouvez-vous fournir plus de détails? J'ai également un adaptateur qui stocke les fragments dans la liste, mais lorsque je reprends mon application à partir d'applications récentes, les applications se bloquent car il y a un fragment détaché. Le problème est ici stackoverflow.com/questions/11631408/…
Georgy Gobozov
3
Quel est votre «mon» dans le référencement du fragment?
Josh
Nous savons tous que cette solution n'est pas une bonne approche, mais c'est le moyen le plus simple à utiliser FragmentByTagdans ViewPager.
Youngjae
voici un moyen d'accéder aux fragments sans compter sur la compatibilité avec la manière interne d'attribuer des balises: stackoverflow.com/questions/14035090/…
morgwai
26

Solution testée au niveau mondial.

getSupportFragmentManager()conserve la référence nulle quelques fois et View pager ne crée pas de nouveau car il trouve une référence au même fragment. Donc, surmonter cette utilisation getChildFragmentManager()résout le problème de manière simple.

Ne fais pas ça:

new PagerAdapter(getSupportFragmentManager(), fragments);

Faites ceci:

new PagerAdapter(getChildFragmentManager() , fragments);

Vinayak
la source
6
cela ne serait possible que si vous hébergez (et instanciez) le pagerAdapter dans un Fragment et non dans votre activité. Dans ce cas, vous avez raison, vous devez utiliser le childFragmentManager par défaut. L'utilisation de SupportFragmentManager serait erronée par défaut
Klitos G.
Tres précis. J'ai résolu mon problème sans utiliser de hacks. Merci! Ma version Kotlin est FragmentStatePagerAdapter(activity!!.supportFragmentManager)FragmentStatePagerAdapter(childFragmentManager)
passée
7

N'essayez pas d'interagir entre les fragments dans ViewPager. Vous ne pouvez pas garantir qu'un autre fragment est attaché ou même existe. Au lieu de changer le titre de la barre d'action du fragment, vous pouvez le faire à partir de votre activité. Utilisez le modèle d'interface standard pour cela:

public interface UpdateCallback
{
    void update(String name);
}

public class MyActivity extends FragmentActivity implements UpdateCallback
{
    @Override
    public void update(String name)
    {
        getSupportActionBar().setTitle(name);
    }

}

public class MyFragment extends Fragment
{
    private UpdateCallback callback;

    @Override
    public void onAttach(SupportActivity activity)
    {
        super.onAttach(activity);
        callback = (UpdateCallback) activity;
    }

    @Override
    public void onDetach()
    {
        super.onDetach();
        callback = null;
    }

    public void updateActionbar(String name)
    {
        if(callback != null)
            callback.update(name);
    }
}
Séraphin
la source
Bien sûr, je vais inclure le code maintenant ... Je suis déjà en train d'enregistrer ces méthodes. OnDetach est appelé lorsque l'onStop est appelé dans fragmentActivity. le onAttach est appelé juste avant le onCreate de FragmentActivity.
Maurycy
Hmm merci mais le problème persiste. J'ai fait de la définition du nom un rappel à la place. Puis-je ne pas mettre de la logique dans le fragment? J'ai mes fragments déclencher des services et d'autres éléments qui nécessitent une référence à l'activité. Je devrais déplacer toute la logique de mes applications vers l'activité principale pour éviter le problème que je rencontre. Cela semble un peu inutile.
Maurycy
Ok donc je viens de tester cela complètement et commenté tout mon code ... à l'exception du rappel pour changer le titre. Lorsque l'application est lancée pour la première fois et que je passe à cet écran, le nom du titre est changé np. Après avoir chargé plusieurs applications puis revenu, le titre ne change plus. Il semble que le rappel soit reçu sur une ancienne instance d'activité. Je ne peux pas penser à une autre explication
Maurycy
J'ai considéré cela comme un problème avec le fragmentManager et ce problème ... code.google.com/p/android/issues/detail?id=19211 . Je ne sais toujours pas comment résoudre ce problème
Maurycy
5

Vous pouvez supprimer les fragments lors de la destruction du viewpager, dans mon cas, je les ai supprimés sur onDestroyView()de mon fragment:

@Override
public void onDestroyView() {

    if (getChildFragmentManager().getFragments() != null) {
        for (Fragment fragment : getChildFragmentManager().getFragments()) {
            getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
        }
    }

    super.onDestroyView();
}
Raoni Novellino
la source
Merci, votre solution fonctionne. Il est nécessaire qui ViewPagerest également basé sur l' childFragmentManageradaptateur (non fragmentManager). Une autre variante fonctionne également: ne pas utiliser onDestroyView, mais supprimer les fragments enfants avant la ViewPagercréation de l'adaptateur.
CoolMind
4

Après quelques heures de recherche d'un problème similaire, je pense avoir une autre solution. Celui-ci au moins a fonctionné pour moi et je n'ai qu'à changer quelques lignes.

C'est le problème que j'ai eu, j'ai une activité avec un pager de vue qui utilise un FragmentStatePagerAdapter avec deux fragments. Tout fonctionne bien jusqu'à ce que je force l'activité à être détruite (options du développeur) ou que je fasse pivoter l'écran. Je garde une référence aux deux fragments après leur création dans la méthode getItem.

À ce stade, l'activité sera créée à nouveau et tout fonctionne bien à ce stade, mais j'ai perdu la référence à mes fragmetns car getItem n'est plus appelé.

Voici comment j'ai résolu ce problème, à l'intérieur du FragmentStatePagerAdapter:

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

        //Update the references to the Fragments we have on the view pager
        if(position==0){
            fragTabOne = (FragOffersList)aux;
        }
        else{
            fragTabTwo = (FragOffersList) aux;
        }

        return aux;
    }

Vous ne recevrez plus un appel sur getItem si l'adaptateur y fait déjà référence en interne, et vous ne devriez pas changer cela. Au lieu de cela, vous pouvez obtenir le fragment qu'il est utilisé en regardant cette autre méthode instantiateItem () qui sera appelée pour chacun de vos fragments.

J'espère que cela aide n'importe qui.

Lancelot
la source
Et si vous avez beaucoup de fragments? préféreriez-vous vraiment enregistrer une référence pour chacun d'eux? N'est-ce pas mauvais pour la mémoire?
développeur android
Tout dépend de ce que vous essayez de réaliser exactement. Normalement, avec ces pagers d'affichage, vous ne détenez pas plus de 3 fragments à la fois. Celui du milieu et celui de chaque côté. Pour ce que je veux, j'ai vraiment besoin d'une référence aux fragments, mais je sais que je n'ai besoin que de deux.
Lancelot
0

Puisque le FragmentManager se chargera de restaurer vos Fragments pour vous dès que la méthode onResume () est appelée, je fais appeler le fragment à l'activité et s'ajouter à une liste. Dans mon cas, je stocke tout cela dans mon implémentation PagerAdapter. Chaque fragment connaît sa position car il est ajouté aux arguments de fragment lors de la création. Désormais, chaque fois que j'ai besoin de manipuler un fragment à un index spécifique, tout ce que j'ai à faire est d'utiliser la liste de mon adaptateur.

Voici un exemple d'adaptateur pour un ViewPager personnalisé qui agrandira le fragment au fur et à mesure qu'il se déplacera dans le focus, et le réduira à mesure qu'il se déplace. Outre les classes Adapter et Fragment que j'ai ici, tout ce dont vous avez besoin est que l'activité parent puisse référencer la variable d'adaptateur et vous êtes défini.

Adaptateur

public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {

public final String TAG = this.getClass().getSimpleName();

private final int COUNT = 4;

public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;

private int mCurrentPage = 0;
private boolean mScrollingLeft;

private List<SummaryTabletFragment> mFragments;

public int getCurrentPage() {
    return mCurrentPage;
}

public void addFragment(SummaryTabletFragment fragment) {
    mFragments.add(fragment.getPosition(), fragment);
}

public GrowPagerAdapter(FragmentManager fm) {
    super(fm);

    mFragments = new ArrayList<SummaryTabletFragment>();
}

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

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

@Override
public void onPageScrollStateChanged(int state) {}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    adjustSize(position, positionOffset);
}

@Override
public void onPageSelected(int position) {
    mCurrentPage = position;
}

/**
 * Used to adjust the size of each view in the viewpager as the user
 * scrolls.  This provides the effect of children scaling down as they
 * are moved out and back to full size as they come into focus.
 * 
 * @param position
 * @param percent
 */
private void adjustSize(int position, float percent) {

    position += (mScrollingLeft ? 1 : 0);
    int secondary = position + (mScrollingLeft ? -1 : 1);
    int tertiary = position + (mScrollingLeft ? 1 : -1);

    float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
    float scaleDown = mScrollingLeft ? 1.0f - percent : percent;

    float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
    float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;

    if (scaleUp < BASE_SIZE)
        scaleUp = BASE_SIZE;

    if (scaleDown < BASE_SIZE)
        scaleDown = BASE_SIZE;

    // Adjust the fragments that are, or will be, on screen
    SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
    SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
    SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;

    if (current != null && next != null) {

        // Apply the adjustments to each fragment
        current.transitionFragment(percentIn, scaleUp);
        next.transitionFragment(percentOut, scaleDown);

        if (afterNext != null) {
            afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
        }
    }
}

@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {

    // Keep track of which direction we are scrolling
    mScrollingLeft = (oldl - l) < 0;
}
}

Fragment

public class SummaryTabletFragment extends BaseTabletFragment {

public final String TAG = this.getClass().getSimpleName();

private final float SCALE_SIZE = 0.8f;

private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;

private String mTitleText;
private Integer mColor;

private boolean mInit = false;
private Float mScale, mPercent;

private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;

public String getTitleText() {
    return mTitleText;
}

public void setTitleText(String titleText) {
    this.mTitleText = titleText;
}

public static SummaryTabletFragment newInstance(int position) {

    SummaryTabletFragment fragment = new SummaryTabletFragment();
    fragment.setRetainInstance(true);

    Bundle args = new Bundle();
    args.putInt("position", position);
    fragment.setArguments(args);

    return fragment;
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);

    mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);

    setupViews();
    configureView();

    return mRoot;
}

@Override
public void onViewStateRestored(Bundle savedInstanceState) {
    super.onViewStateRestored(savedInstanceState);

    if (savedInstanceState != null) {
        mColor = savedInstanceState.getInt("color", Color.BLACK);
    }

    configureView();
}

@Override
public void onSaveInstanceState(Bundle outState)  {

    outState.putInt("color", mColor);

    super.onSaveInstanceState(outState);
}

@Override
public int getPosition() {
    return getArguments().getInt("position", -1);
}

@Override
public void setPosition(int position) {
    getArguments().putInt("position", position);
}

public void onResume() {
    super.onResume();

    mAdapter = mActivity.getPagerAdapter();
    mAdapter.addFragment(this);
    mCurrentPosition = mAdapter.getCurrentPage();

    if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
        mInit = true;
        transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
        return;
    }

    if (getPosition() == mCurrentPosition && !mInit) {
        mInit = true;
        transitionFragment(0.00f, 1.0f);
    }
}

private void setupViews() {

    mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
    mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
    mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
    mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
    mTitle = (TextView) mRoot.findViewById(R.id.title);
}

private void configureView() {

    Fonts.applyPrimaryBoldFont(mLeft, 15);
    Fonts.applyPrimaryBoldFont(mRight, 15);

    float[] size = UiUtils.getScreenMeasurements(mActivity);
    int width = (int) (size[0] * SCALE_SIZE);
    int height = (int) (size[1] * SCALE_SIZE);

    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
    mBackground.setLayoutParams(params);

    if (mScale != null)
        transitionFragment(mPercent, mScale);

    setRandomBackground();

    setTitleText("Fragment " + getPosition());

    mTitle.setText(getTitleText().toUpperCase());
    mLeft.setText(getTitleText().toUpperCase());
    mRight.setText(getTitleText().toUpperCase());

    mLeft.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showNextPage();
        }
    });

    mRight.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            mActivity.showPrevPage();
        }
    });
}

private void setRandomBackground() {

    if (mColor == null) {
        Random r = new Random();
        mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
    }

    mBackground.setBackgroundColor(mColor);
}

public void transitionFragment(float percent, float scale) {

    this.mScale = scale;
    this.mPercent = percent;

    if (getView() != null && mCover != null) {

        getView().setScaleX(scale);
        getView().setScaleY(scale);

        mCover.setAlpha(percent);
        mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
    }
}

@Override
public String getFragmentTitle() {
    return null;
}
}
saulpower
la source
0

Ma solution: j'ai défini presque toutes les vues comme static. Maintenant, mon application interagit parfaitement. Pouvoir appeler les méthodes statiques de partout n'est peut-être pas un bon style, mais pourquoi jouer avec du code qui ne fonctionne pas? J'ai lu beaucoup de questions et leurs réponses ici sur SO et aucune solution n'a apporté de succès (pour moi).

Je sais que cela peut fuir la mémoire et gaspiller du tas, et mon code ne sera pas adapté à d'autres projets, mais je n'ai pas peur à ce sujet - j'ai testé l'application sur différents appareils et conditions, aucun problème, l'Android La plate-forme semble être en mesure de gérer cela. L'interface utilisateur est actualisée toutes les secondes et même sur un appareil S2 ICS (4.0.3), l'application est capable de gérer des milliers de géomarqueurs.

Martin Pfeffer
la source
0

J'ai rencontré le même problème mais mon ViewPager était à l'intérieur d'un TopFragment qui a créé et défini un adaptateur à l'aide de setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).

J'ai résolu ce problème onAttachFragment(Fragment childFragment)en remplaçant dans TopFragment comme ceci:

@Override
public void onAttachFragment(Fragment childFragment) {
    if (childFragment instanceof OnboardingDiamondsFragment) {
        mChildFragment = (ChildFragment) childFragment;
    }

    super.onAttachFragment(childFragment);
}

Comme on le sait déjà (voir les réponses ci-dessus), lorsque le childFragmentManager se recrée, il crée également les fragments qui se trouvaient à l'intérieur du viewPager.
La partie importante est qu'après cela, il appelle onAttachFragment et nous avons maintenant une référence au nouveau fragment recréé!

J'espère que cela aidera quiconque à obtenir ce vieux Q comme moi :)

Shirane85
la source
0

J'ai résolu le problème en enregistrant les fragments dans SparceArray:

public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {

    SparseArray<Fragment> fragments = new SparseArray<>();

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

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        fragments.append(position, fragment);
        return fragment;
    }

    @Nullable
    public Fragment getFragmentByPosition(int position){
        return fragments.get(position);
    }

}
xaxtix
la source
0

Juste pour que vous sachiez ...

En plus de la litanie de malheurs avec ces classes, il y a un bug plutôt intéressant qui mérite d'être partagé.

J'utilise un ViewPager pour naviguer dans une arborescence d'éléments (sélectionnez un élément et le pager de vue s'anime en défilant vers la droite, et la branche suivante apparaît, revenez en arrière et le ViewPager défile dans la direction opposée pour revenir au nœud précédent) .

Le problème survient lorsque je pousse et dépose des fragments à la fin de FragmentStatePagerAdapter. Il est suffisamment intelligent pour remarquer que les éléments changent et suffisamment intelligent pour créer et remplacer un fragment lorsque l'élément a changé. Mais pas assez intelligent pour supprimer l'état de fragment, ou assez intelligent pour couper les états de fragment enregistrés en interne lorsque la taille de l'adaptateur change. Ainsi, lorsque vous affichez un élément et en poussez un nouveau à la fin, le fragment du nouvel élément obtient l'état enregistré du fragment pour l'ancien élément, ce qui a causé des ravages absolus dans mon code. Mes fragments contiennent des données qui peuvent nécessiter beaucoup de travail pour être récupérées sur Internet, donc ne pas enregistrer l'état n'était vraiment pas une option.

Je n'ai pas de solution de contournement propre. J'ai utilisé quelque chose comme ça:

  public void onSaveInstanceState(Bundle outState) {
    IFragmentListener listener = (IFragmentListener)getActivity();
    if (listener!= null)
    {
        if (!listener.isStillInTheAdapter(this.getAdapterItem()))
        {
            return; // return empty state.
        }

    }
    super.onSaveInstanceState(outState);

    // normal saving of state for flips and 
    // paging out of the activity follows
    ....
  }

Une solution imparfaite car la nouvelle instance de fragment reçoit toujours un bundle savedState, mais au moins elle ne contient pas de données périmées.

Robin Davies
la source
0

Puisque les gens n'ont pas tendance à lire les commentaires, voici une réponse qui reproduit principalement ce que j'ai écrit ici :

la cause première du problème est le fait que le système Android n'appelle pas getItempour obtenir des fragments qui sont réellement affichés, mais instantiateItem. Cette méthode essaie d'abord de rechercher et de réutiliser une instance de fragment pour un onglet donné dans FragmentManager. Uniquement si cette recherche échoue (ce qui ne se produit que la première fois lors de sa FragmentManagercréation), elle getItemest appelée. Il s'agit pour des raisons évidentes de ne pas recréer des fragments (qui peuvent être lourds) par exemple à chaque fois qu'un utilisateur fait tourner son appareil.
Pour résoudre ce problème, au lieu de créer des fragments avec Fragment.instantiatedans votre activité, vous devez le faire avec pagerAdapter.instantiateItemet tous ces appels doivent être entourés d' startUpdate/finishUpdateappels de méthode qui démarrent / valident respectivement la transaction de fragment.getItem devrait être le lieu où les fragments sont réellement créés à l'aide de leurs constructeurs respectifs.

List<Fragment> fragments = new Vector<Fragment>();

@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);
        fragments.add(adapter.instantiateItem(viewPager, 0));
        fragments.add(adapter.instantiateItem(viewPager, 1));
        // and so on if you have more tabs...
        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
        }
}
Morgwai
la source