web-dev-qa-db-fra.com

La vue du fragment de ViewPager a été perdue lorsque le fragment parent de ViewPager a été masqué puis affiché.

J'ai constaté un comportement étrange avec mon ViewPager avec mon propre FragmentStatePagerAdapter.

Ma hiérarchie de vues ressemble à ceci:

-> (1) Fragment root view (RelativeLayout)
 -> (2) ViewPager
  -> (3) ViewPager's current fragment view

Lorsque le fragment responsable de la vue racine du fragment (1) est masqué (à l'aide de .hide () dans une transaction de fragment), puis affiché (avec .show ()), la vue fragmentée actuellement affichée dans ViewPager (3 ) devient nul, bien que le fragment existe toujours. Fondamentalement, mon ViewPager devient complètement vide/transparent.

Le seul moyen que j’ai trouvé pour résoudre ce problème est d’appeler 

int current = myViewPager.getCurrentItem();
myViewPager.setAdapter(myAdapter);
myViewPager.setCurrentItem(current);

après que le fragment parent est montré. Cela déclenche en quelque sorte que les vues soient recréées et apparaissent à l'écran. Malheureusement, cela provoque parfois des exceptions lorsqu’un adaptateur pager appelle unregisterDataSetObserver() à deux reprises sur un ancien observateur.

Y a-t-il une meilleure manière de faire cela? Je suppose que ce que je demande est: 

Pourquoi mes vues de fragments à l'intérieur de mon ViewPager sont-elles détruites lorsque le fragment parent du ViewPager est masqué?

Mise à jour: cela se produit également lorsque l'application est "réduite" puis "restaurée" (en appuyant sur la touche d'action Accueil puis en retournant).

Sur demande, voici ma classe d’adaptateur de téléavertisseur:

public class MyInfoSlidePagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<MyInfo> infos = new ArrayList<MyInfo>();

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

    public MyInfoSlidePagerAdapter (FragmentManager fm, MyInfo[] newInfos) {
        super(fm);
        setInfos(newInfos);
    }

    @Override
    public int getItemPosition(Object object) {
        int position = infos.indexOf(((MyInfoDetailsFragment)object).getMyInfo());
        return position > 0 ? position : POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return infos.get(position).getName();
    }

    @Override
    public Fragment getItem(int i) {
        return infos.size() > 0 ? MyInfoDetailsFragment.getNewInstance(infos.get(i)) : null;
    }

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

    public Location getMyInfoAtPosition(int i) {
        return infos.get(i);
    }

    public void setInfos(MyInfo[] newInfos) {
        infos = new ArrayList<MyInfo>(Arrays.asList(newInfos));
    }

    public int getPositionOfMyInfo(MyInfo info) {
        return infos.indexOf(info);
    }
}

J'ai renommé certaines variables, mais à part cela, c'est exactement ce que j'ai.

61
John Leehey

Vous ne fournissez pas assez d'informations pour votre problème spécifique, j'ai donc construit un exemple de projet qui tente de reproduire votre problème: l'application a une activité qui contient un fragment (PagerFragment) dans une présentation relative et sous cette présentation, j'ai un bouton qui cache et montre ci-dessus PagerFragment. PagerFragment a une ViewPager et chaque fragment de l'adaptateur de pagineur affiche simplement une étiquette - ce fragment s'appelle DataFragment. La liste d'étiquettes est créée dans l'activité parent et transmise à PagerFragment puis à son adaptateur à chaque DataFragment. La modification de la visibilité PagerFragment se fait sans problème et chaque fois qu’elle redevient visible, elle affiche l’étiquette précédente. 

La clé du problème: Utilisez Fragment # getChildFragmentManager () lorsque vous créez l'adaptateur Viewpager et non pas getFragmentManager!

Peut-être que vous pouvez comparer ce projet simple avec ce que vous avez et vérifier où sont les différences. Alors, voici (top-down):

PagerActivity (la seule activité du projet):

public class PagerActivity extends FragmentActivity {

    private static final String PAGER_TAG = "PagerActivity.PAGER_TAG";

    @Override
    protected void onCreate(Bundle savedInstance) {
        super.onCreate(savedInstance);
        setContentView(R.layout.pager_activity);
        if (savedInstance == null) {
            PagerFragment frag = PagerFragment.newInstance(buildPagerData());
            FragmentManager fm = getSupportFragmentManager();
            fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
        }
        findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                changeFragmentVisibility();
            }
        });
    }

    private List<String> buildPagerData() {
        ArrayList<String> pagerData = new ArrayList<String>();
        pagerData.add("Robert de Niro");
        pagerData.add("John Smith");
        pagerData.add("Valerie Irons");
        pagerData.add("Metallica");
        pagerData.add("Rammstein");
        pagerData.add("Zinedine Zidane");
        pagerData.add("Ronaldo da Lima");
        return pagerData;
    }

    protected void changeFragmentVisibility() {
        Fragment frag = getSupportFragmentManager().findFragmentByTag(PAGER_TAG);
        if (frag == null) {
            Toast.makeText(this, "No PAGER fragment found", Toast.LENGTH_SHORT).show();
            return;
        }
        boolean visible = frag.isVisible();
        Log.d("APSampler", "Pager fragment visibility: " + visible);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        if (visible) {
            ft.hide(frag);
        } else {
            ft.show(frag);
        }
        ft.commit();
        getSupportFragmentManager().executePendingTransactions();
    }
}

son fichier de présentation pager_activity.xml

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:padding="4dp" >

    <Button
        Android:id="@+id/btnFragments"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignParentBottom="true"
        Android:layout_centerHorizontal="true"
        Android:text="Hide/Show fragments" />

    <RelativeLayout
        Android:id="@+id/layout_fragments"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_above="@+id/btnFragments"
        Android:layout_marginBottom="4dp" >
    </RelativeLayout>

</RelativeLayout>

Observez que j'ajoute la PagerFragment lorsque l'activité est affichée pour la première fois - et la classe PagerFragment:

public class PagerFragment extends Fragment {

    private static final String DATA_ARGS_KEY = "PagerFragment.DATA_ARGS_KEY";

    private List<String> data;

    private ViewPager pagerData;

    public static PagerFragment newInstance(List<String> data) {
        PagerFragment pagerFragment = new PagerFragment();
        Bundle args = new Bundle();
        ArrayList<String> argsValue = new ArrayList<String>(data);
        args.putStringArrayList(DATA_ARGS_KEY, argsValue);
        pagerFragment.setArguments(args);
        return pagerFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        data = getArguments().getStringArrayList(DATA_ARGS_KEY);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.pager_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        pagerData = (ViewPager) view.findViewById(R.id.pager_data);
        setupPagerData();
    }

    private void setupPagerData() {
        PagerAdapter adapter = new LocalPagerAdapter(getChildFragmentManager(), data);
        pagerData.setAdapter(adapter);
    }

}

sa mise en page (uniquement le ViewPager qui prend en taille réelle):

<Android.support.v4.view.ViewPager xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:id="@+id/pager_data"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent" />

et son adaptateur:

public class LocalPagerAdapter extends FragmentStatePagerAdapter {
    private List<String> pagerData;

    public LocalPagerAdapter(FragmentManager fm, List<String> pagerData) {
        super(fm);
        this.pagerData = pagerData;
    }

    @Override
    public Fragment getItem(int position) {
        return DataFragment.newInstance(pagerData.get(position));
    }

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

Cet adaptateur crée une variable DataFragment pour chaque page:

public class DataFragment extends Fragment {
    private static final String DATA_ARG_KEY = "DataFragment.DATA_ARG_KEY";

    private String localData;

    public static DataFragment newInstance(String data) {
        DataFragment df = new DataFragment();
        Bundle args = new Bundle();
        args.putString(DATA_ARG_KEY, data);
        df.setArguments(args);
        return df;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        localData = getArguments().getString(DATA_ARG_KEY);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.data_fragment, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        view.findViewById(R.id.btn_page_action).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), localData, Toast.LENGTH_SHORT).show();
            }
        });
        ((TextView) view.findViewById(R.id.txt_label)).setText(localData);
    }
}

et la disposition de DataFragment:

<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:orientation="vertical"
    Android:padding="4dp" >

    <Button
        Android:id="@+id/btn_page_action"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_alignParentBottom="true"
        Android:layout_centerHorizontal="true"
        Android:text="Interogate" />

    <TextView
        Android:id="@+id/txt_label"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_centerInParent="true"
        Android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

Profitez de la programmation!

87
gunar

peut-être que cela aidera mViewPager.setOffscreenPageLimit (5)

Définissez le nombre de pages à conserver de chaque côté du fichier page en cours dans la hiérarchie des vues dans un état inactif. Pages au-delà La limite sera recréée à partir de l'adaptateur si nécessaire.

Ceci est offert comme une optimisation. Si vous connaissez d'avance le numéro des pages que vous aurez besoin de supporter ou d’avoir des mécanismes de chargement différé dans placer sur vos pages, modifier ce paramètre peut avoir des avantages en lissage perçu d'animations de pagination et d'interaction. Si tu as un petit nombre de pages (3-4) que vous pouvez garder actives en même temps, moins de temps sera consacré à la mise en page pour les sous-arbres de vue nouvellement créés en tant que les pages utilisateur dans les deux sens.

Vous devriez garder cette limite basse, surtout si vos pages ont complexe mises en page. La valeur par défaut de ce paramètre est 1.

18
Malder

View Pager est assez catégorique pour garder ses fragments toujours frais et optimiser ainsi les performances en libérant de la mémoire lorsqu'un fragment n'est pas utilisé. Clairement, c’est un trait utile et utile dans un système mobile. Mais en raison de cette désallocation persistante des ressources, le fragment est créé chaque fois qu’il gagne le focus. 

mViewPager.setOffscreenPageLimit(NUMBEROFFRAGMENTSCREENS);

Voici la documentation.

this Old Post a une solution intéressante pour votre problème .. S'il vous plaît se référer

3
Augustus Francis

J'ai eu le même problème. Mon application (FragmentActivity) a un pager (ViewPager) avec 3 framgents. En glissant entre les fragments, ils sont détruits et recréés tout le temps. En fait, cela ne pose aucun problème de fonctionnalité (attendez-vous à des curseurs non fermés), mais je m'interrogeais également sur cette question.

Je ne sais pas s'il existe une solution de contournement pour changer le comportement du ViewPager, mais je suggère d'avoir un objet de configuration (peut-être un objet statique) et avant de détruire, enregistrez votre objet myViewPager dans l'objet config.

public class App extends FragmentActivity {

    static MyData data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         data = (MyData) getLastCustomNonConfigurationInstance();
         if (data == null) {
             data = new MyData();
             data.savedViewPager = myViewPager;
         } else {
             myViewPager = data.savedViewPager;
        }
    }

    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        Log.d("onRetainCustomNonConfigurationInstance", "Configuration call");
        return data;
    }
}

public class MyData {
    public ViewPager savedViewPager;
}

De cette façon, vous pouvez enregistrer la référence à un objet qui ne sera pas détruit, il y est donc référencé et vous pouvez recharger tous vos objets cruciaux.

J'espère que vous trouverez ma suggestion utile!

1
csikos.balint

Pour moi, j'ai changé pour getChildFragmentManager() au lieu de getFragmentManager() Et fonctionne bien . Ex:

pagerAdapt = new PagerAdapt(getChildFragmentManager());
0
Mahmoud Ayman