web-dev-qa-db-fra.com

IllegalArgumentException: Aucune vue trouvée pour id pour fragment --- ViewPager dans ViewPager

J'ai rencontré le problème qui me dérange pendant des jours.

Il y a une ViewPager dans l'activité principale qui contient 3 Fragments en tant que fragments d'onglets. Dans le fragment first, il y a une ListView qui contient certaines vues et la plus importante, une autre ViewPager. Je veux conserver quelques photos dans la sous-variable ViewPager et utiliser quelques fragments supplémentaires ici.

Maintenant, il y a le problème:
Lorsque firstFragment est arrêté (le fragment third du parent ViewPager est affiché à l'écran) et repris (l'utilisateur passe au fragment second), l'application se bloque le débogueur dit:

Java.lang.IllegalArgumentException: No view found for id 0x7f05008b (com.example.viewpager:id/sub_viewpager) for fragment ScreenSlidePageFragment

J'ai déjà utilisé getChildFragmentManager() car il s'agit d'une situation de fragments imbriqués .

Voici le code de clé de l'adaptateur de liste correspondant au premier fragment du ViewPager parent:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    int type = getItemViewType(position);
    switch (type) {
        case TYPE_BANNER:
            if (convertView == null) {
                convertView = mBannerView.getBannerView(parent);
            }
            mBannerView.update(convertView);
            break;
        case TYPE_ITEM:
            break;
    }
    return convertView;
}

Voici le code de mBannerView:

public class BannerView {

    private static final DisplayImageOptions IMAGE_OPTIONS_SCALE_STRETCHED =
            new DisplayImageOptions.Builder()
                    .cacheInMemory()
                    .cacheOnDisc()
                    .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
                    .build();

    private FragmentActivity mActivity;
    private Fragment mFragment;
    private List<Banner> mBanners;
    private ScreenSlidePagerAdapter mPagerAdapter;
    private ViewPager mViewPager;

    public BannerView(FragmentActivity activity, Fragment fragment) {
        mActivity = activity;
        mFragment = fragment;
    }

    public void update(View convertView) {
        mViewPager = (ViewPager) convertView;
        if (mBanners != null && !mBanners.isEmpty()) {
            if (mPagerAdapter == null) {
                mPagerAdapter = new ScreenSlidePagerAdapter(mFragment.getChildFragmentManager());
                mViewPager.setAdapter(mPagerAdapter);
            }
        }
        mViewPager.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnBannerClickListener != null) {
                    mOnBannerClickListener.onBannerClick();
                }
            }
        });
    }

    class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
        public ScreenSlidePagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return new ScreenSlidePageFragment(mBanners.get(position).getImageUrl());
        }

        @Override
        public int getCount() {
            return mBanners == null ? 0 : mBanners.size();
        }
    }

    class ScreenSlidePageFragment extends Fragment {

        private String mUrl;

        ScreenSlidePageFragment(String url) {
            super();
            mUrl = url;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.item_banner, container, false);
            if (view != null) {
                ImageView imageView = (ImageView) view.findViewById(R.id.item_banner_image);
                imageView.setLayoutParams(new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
                ImageLoader.getInstance().displayImage(mUrl, imageView, IMAGE_OPTIONS_SCALE_STRETCHED);
            }
            return view;
        }
    }
}

Voici la liste d'erreur détaillée:

11-10 18:12:19.217    1444-1444/? E/MessageQueue-JNI﹕ Java.lang.IllegalArgumentException: No view found for id 0x7f05008b (com.example.viewpager:id/sub_viewpager) for fragment ScreenSlidePageFragment{428d8ea0 #0 id=0x7f05008b}
        at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.Java:919)
        at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.Java:1104)
        at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.Java:1086)
        at Android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.Java:1884)
        at Android.support.v4.app.Fragment.performActivityCreated(Fragment.Java:1514)
        at Android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.Java:947)
        at Android.support.v4.app.FragmentManagerImpl.attachFragment(FragmentManager.Java:1280)
        at Android.support.v4.app.BackStackRecord.run(BackStackRecord.Java:672)
        at Android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.Java:1467)
        at Android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.Java:472)
        at Android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.Java:141)
        at Android.support.v4.view.ViewPager.populate(ViewPager.Java:1068)
        at Android.support.v4.view.ViewPager.populate(ViewPager.Java:914)
        at Android.support.v4.view.ViewPager$3.run(ViewPager.Java:244)
        at Android.support.v4.view.ViewPager.completeScroll(ViewPager.Java:1761)
        at Android.support.v4.view.ViewPager.onInterceptTouchEvent(ViewPager.Java:1896)
        at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:1854)
        at Android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.Java:2211)
        at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:1912)
        at Android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.Java:2211)
        at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:1912)
        at Android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.Java:2211)
        at Android.view.ViewGroup.dispatchTouchEvent(ViewGroup.Java:1912)
        at com.Android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.Java:2228)
        at com.Android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.Java:1471)
        at Android.app.Activity.dispatchTouchEvent(Activity.Java:2424)
        at com.Android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.Java:2176)
        at Android.view.View.dispatchPointerEvent(View.Java:7571)
        at Android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.Java:3883)
        at Android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.Java:3778)
        at Android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.Java:3379)
        at Android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.Java:3429)
        at Android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.Java:3398)
        at Android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.Java:3483)
        at Android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.Java:3406)
        at Android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.Java:3540)
        at Android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.Java:3379)
        at Android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.Java:3429)
        at Android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.Java:3398)
        at Android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.Java:3406)
        at Android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.Java:3379)
        at Android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.Java:5419)
        at Android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.Java:5399)
        at Android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.Java:5370)
        at Android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.Java:5493)
        at Android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.Java:182)
        at Android.os.MessageQueue.nativePollOnce(Native Method)
        at Android.os.MessageQueue.next(MessageQueue.Java:132)
        at Android.os.Looper.loop(Looper.Java:124)
        at Android.app.ActivityThread.main(ActivityThread.Java:5289)
        at Java.lang
18
Xieyi

Mettre à jour:

J'ai lu le code source de FragmentManager et j'ai enfin trouvé la vraie raison: cette exception se produit lorsque les fragments veulent être attachés au viewpager avant que celui-ci ne soit attaché à son parent. En d'autres termes, avant le retour de la méthode getView (), les fragments sont gonflés. Ensuite, la méthode findViewById () du conteneur du ViewPager est appelée, mais le ViewPager est encore à l'état détaché. Null est donc trouvé et l'exception IllegalArgumentException est levée.

La solution consiste à créer un ViewPager personnalisé et à définir l'adaptateur en différé: 

public class BannerViewPager extends ViewPager {
    PagerAdapter mPagerAdapter;

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mPagerAdapter != null) {
            super.setAdapter(mPagerAdapter);
            mPageIndicator.setViewPager(this);
        }
    }

    @Override
    public void setAdapter(PagerAdapter adapter) {
    }

    public void storeAdapter(PagerAdapter pagerAdapter) {
        mPagerAdapter = pagerAdapter;
    }

    public BannerViewPager(Context context) {
        super(context);
    }

    public BannerViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}

Et dans la méthode getView (), utilisez storeAdapter () au lieu de setAdapter.

Les affirmations suivantes sont incorrectes. Les mots ci-dessus sont la raison réelle.


Enfin j'ai la réponse. Il se compose de deux parties.

  1. Dans le ViewPager parent, j’utilisais une FragmentPagerAdapter pour contenir des fragments, mais j’utilise maintenant une FragmentStatePagerAdapter à la place. La différence entre ces deux peut être trouvée ici: Différence entre FragmentPagerAdapter et FragmentStatePagerAdapter .
    En termes simples, FragmentPagerAdapter stockera plus d'informations lorsqu'un fragment est arrêté. Dans cette situation, le premier fragment du ViewPager parent est arrêté mais pas détruit, alors que les vues de ce fragment sont détruites. Une fois repris, le fragment tente de regonfler toutes les vues. Mais avant que la méthode getView() soit appelée et que le sous-ViewPager soit recréé, le FragmentManager enfant essaie de trouver le sous-ViewPager qui contiendra ces fragments précédemment stockés. Ainsi, "Java.lang.IllegalArgumentException: Aucune vue trouvée pour id" se produit.

  2. Après avoir remplacé FragmentPagerAdapter par FragmentStatePagerAdapter, un autre problème apparaît. le sous-viewpager est manquant lorsque le fragment parent (le premier fragment du viewpager parent) a été arrêté, détruit et repris. Cela se produit lorsque le premier fragment est choisi, peu de temps après, le troisième fragment est choisi et enfin, le premier fragment est re-choisi.
    Je pense que c'est un bug d'Android SDK. Inspiré par ici et ici , j'utilise des méthodes compliquées pour résoudre le problème. Le fait est que, lorsqu'un fragment parent est détruit, le membre de champ --- mChildFragmentManager "se termine avec un état interne cassé" et n'est pas totalement nettoyé. Lorsque le fragment parent est recréé, mChildFragmentManager n'est pas null, mais les sous-fragments sont déjà détruits après la destruction du fragment parent, géré par mChildFragmentManager. Ainsi, le sous-ViewPager affiche une vue vide à l'écran, qui répond à un faux fragment qui n'existe pas. Ce qui est amusant, c’est qu’après avoir balayé le sous-ViewPager à plusieurs reprises, les sous-fragments et les vues réapparaissent.

Voici les codes:

Adaptateur parent:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = getBannerView(mParent);
    }
    mViewPager = (ViewPager) convertView;
    if (mBanners != null && !mBanners.isEmpty()) {
        if (mPagerAdapter == null) {
            FragmentManager childFM = mFragment.getChildFragmentManager();
            removeOldFragment(childFM);
            mPagerAdapter = new ScreenSlidePagerAdapter(childFM, mBanners);
            mViewPager.setAdapter(mPagerAdapter);
        }
    }
    return convertView;
}

La méthode clé: 

    private void removeOldFragment(FragmentManager fm) {
        try {
            Field added = fm.getClass().getDeclaredField("mAdded");
            added.setAccessible(true);
            added.set(fm, null);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        try {
            Field active = fm.getClass().getDeclaredField("mActive");
            active.setAccessible(true);
            active.set(fm, null);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
49
Xieyi

Couru dans le même problème lorsque vous utilisez ViewPagers imbriqués . J'ai résolu le problème en remplaçant les deux FragmentPagerAdapter s avec FragmentStatePagerAdapter s. 

5
Simon Raes

Xieyi a bien expliqué le raisonnement qui sous-tend l'exception.

Voici la solution que j'ai trouvée:

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

    for ( Fragment f : getChildFragmentManager().getFragments() ) {
        if ( f instanceof MyFragmentType ) {
            getChildFragmentManager().beginTransaction().remove( f ).commit();
        }
    }

}

Je recevais l'exception à chaque fois que j'allais dans un nouveau fragment et que je revenais. Assurez-vous de supprimer les fragments dans votre méthode "cleaup", vous pourrez alors charger et recharger vos vues lorsque vous revenez au fragment.

5
zMan

Bien que la solution de Xieyi fonctionne parfaitement, elle est plutôt basée sur une réflexion, ce qui n’est pas vraiment une bonne chose à faire.

En guise de réponse alternative, j'ai réussi à supprimer les fragments enfants en utilisant plutôt une transaction de fragment, ce qui, à mon avis, devrait être l'approche privilégiée ici.

Commencez par enregistrer un rappel de cycle de vie de fragment dans le gestionnaire de fragments enfants afin de pouvoir conserver une référence aux fragments enfants.

private List<Fragment> mFragments = new ArrayList<>();

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    …(DO YOUR STUFF)
    //register a fragment lifecycle callback
    getChildFragmentManager().registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {

        @Override
        public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
            super.onFragmentCreated(fm, f, savedInstanceState);
            mFragments.add(f);
        }

        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
            super.onFragmentDestroyed(fm, f);
            mFragments.remove(f);
        }
    }, false);

Ensuite, il suffit de parcourir la liste des fragments pour les supprimer du gestionnaire de fragments enfant, dans onCreateView:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                         @Nullable Bundle savedInstanceState) {
    //remove child fragments before inflating view
    Iterator<Fragment> iterator = mFragments.iterator();
    while (iterator.hasNext()) {
        Fragment fragment = iterator.next();
        iterator.remove();
        getChildFragmentManager().beginTransaction()
                .remove(fragment)
                .commitNow();
    }
    return inflater.inflate(R.layout.your_fragment, container, false);
}

C'est tout.

4
Jagoan Neon

Lorsque vous avez une activité et un fragment qui seraient contenus par l'activité, vous aurez layout-xml avec un identifiant où le parfum doit être placé.

Si l'ID ne se trouve pas dans l'activité, vous obtiendrez cette exception.

Exemple:

getSupportFragmentManager().beginTransaction()
            .add(R.id.banner_container, bannerFragment)
            .commit();

si R.id.banner_container n'est pas un identifiant dans Activity-layout-xml, vous obtiendrez une exception.

4
Skywalker

J'avais un problème similaire. J'ai un fragment (HomeFragment) avec un ListView et un ViewPager qui contient des fragments, tout fonctionne à merveille lorsque le fragment commence, lorsque je clique dans une ligne de la liste, cela me mènera à un autre fragment, mais quand je veux revenir au fragment précédent (HomeFragment) Je reçois l'erreur Aucune vue trouvée pour id (j'ai le fragment addToBackStack).

La solution consistait à vérifier dans onCreateView of HomeFragment si la vue et d'autres composants avaient déjà été créés, par exemple: 

View view;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
  if (view == null) {
     //All the code in the onCreateView
     view = inflater.inflate(R.layout.item_banner, container, false);
     //...
  }
}

Avec cette vérification, quand tout revient au fragment précédent, tout fonctionne correctement: la vue, le pageur, la liste, etc. ont déjà été créés, il n’est donc pas nécessaire de les recréer. =)

3
ElCharro

Si votre ViewPager a été sur la Fragment, utilisez non pas getFragmentManager mais getChildFragmentManager.

0
Minum