IllegalArgumentException: для id для фрагмента не найдено никакого представления --- ViewPager в ViewPager

Я встречал проблему, которая беспокоила меня в течение нескольких дней.

В основной деятельности есть ViewPager, который содержит 3 Fragment в качестве фрагментов вкладки. В первом фрагменте есть ListView, который содержит некоторые представления, а это самый важный, другой ViewPager. Я хочу сохранить некоторые фотографии в суб ViewPager и использовать здесь еще несколько фрагментов.

Теперь есть проблема:
Когда останавливается первый Fragment (фрагмент третий в родительском ViewPager отображается на экране) и возобновляется (пользователь переключается на второй), приложение вылетает, и отладчик говорит:

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

Я уже использовал getChildFragmentManager(), так как это ситуация вложенные фрагменты.

Вот key code адаптера списка, соответствующий первому фрагменту родительского ViewPager:

@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;
}

Вот код 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;
        }
    }
}

Вот подробный список ошибок:

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

Ответ 1

Update:

Я прочитал исходный код FragmentManager и, наконец, получил истинную причину: это исключение происходит, когда фрагменты хотят быть прикреплены к viewpager до того, как viewpager привязан к его родительскому элементу. Другими словами, перед возвратом метода getView() фрагменты раздуваются. Затем вызывается метод findViewById() контейнера ViewPager, но ViewPager еще находится в состоянии отсоединения, поэтому нуль обнаружен и исключение IllegalArgumentException.

Решением является создание настраиваемого ViewPager и ленивый набор адаптера:

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);
    }

}

И в методе getView() используйте storeAdapter() вместо setAdapter.

Следующие утверждения неверны. Слова, приведенные выше, являются фактической причиной.


Наконец, я получил ответ. Он состоит из двух частей.
  • В родительском ViewPager я использовал FragmentPagerAdapter для хранения фрагментов, но теперь вместо этого использую FragmentStatePagerAdapter. Разницу между этими двумя можно найти здесь: Разница между FragmentPagerAdapter и FragmentStatePagerAdapter.
    Проще говоря, FragmentPagerAdapter будет хранить больше информации при остановке фрагмента. В этой ситуации первый фрагмент в родительском ViewPager останавливается, но не уничтожается, а виды в этом фрагменте уничтожаются. После возобновления фрагмент попытается повторно раздуть все взгляды. Но до вызова метода getView() и воссоздания суб-ViewPager дочерний FragmentManager пытается найти суб-ViewPager для хранения ранее сохраненных фрагментов. Таким образом, появляется "java.lang.IllegalArgumentException: No view found for id".

  • После замены FragmentPagerAdapter на FragmentStatePagerAdapter появляется другая проблема. отсутствует суб-просмотрщик, когда родительский фрагмент (первый фрагмент в родительском представлении) был остановлен, уничтожен и возобновлен. Это происходит, когда выбирается первый фрагмент, вскоре после этого выбирается третий фрагмент, и, наконец, первый фрагмент повторно выбирается. Я думаю, что это ошибка android sdk. Вдохновленный здесь и здесь, я использую несколько сложных методов для решения проблемы. Дело в том, что когда родительский фрагмент уничтожается, член поля --- mChildFragmentManager "заканчивается сломанным внутренним состоянием" и не полностью очищается. Когда родительский фрагмент воссоздается, mChildFragmentManager не является нулевым, но субфрагменты уже уничтожены после уничтожения родительского фрагмента, который управлялся mChildFragmentManager. Таким образом, sub ViewPager отображает пустой экран на экране, который отвечает на фальшивый фрагмент, который на самом деле не существует. Самое смешное, что после прокрутки прямо на дополнительном ViewPager несколько раз снова появляются фрагменты и представления.

Вот коды:

Родительский адаптер:

@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;
}

Ключевой метод:

    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);
        }
    }

Ответ 2

Когда у вас есть Activity и Fragment, которые будут содержаться в Activity, у вас будет layout-xml с идентификатором, где должен быть помещен фрагмент.

Если идентификатор не находится в Activity, вы получите это исключение.

Пример:

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

Если R.id.banner_container не является идентификатором в Activity-layout-xml, вы получите исключение.

Ответ 3

Столкнулся с той же проблемой при использовании вложенных ViewPager. Я исправил проблему, заменив оба FragmentPagerAdapter на FragmentStatePagerAdapter.

Ответ 4

Xieyi проделал хорошую работу по объяснению причин, лежащих в основе исключения.

Вот решение, которое я нашел:

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

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

}

Я получал исключение всякий раз, когда возвращался к новому фрагменту и возвращался. Не забудьте удалить фрагменты в методе "cleaup", после чего вы можете загрузить, когда вы вернетесь к фрагменту, можете перезагрузить свои представления.

Ответ 5

Хотя решение Xieyi работает отлично, оно в значительной степени основано на отражении, что не очень хорошо.

В качестве альтернативного ответа мне удалось удалить дочерние фрагменты, используя фрагментную транзакцию, что, по-моему, должно быть предпочтительным для этого.

Сначала зарегистрируйте обратный вызов жизненного цикла фрагмента для диспетчера дочерних фрагментов, чтобы мы могли ссылаться на дочерние фрагменты.

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);

Далее нам нужно только перебрать список фрагментов, чтобы удалить их из диспетчера дочерних фрагментов, внутри 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);
}

Что это.

Ответ 6

У меня была аналогичная проблема. У меня есть фрагмент (HomeFragment) со списком ListView и ViewPager, в котором есть фрагменты внутри, все отлично работает, когда начинается фрагмент, когда я нажимаю какую-либо строку в ListView, это приведет меня к другому фрагменту, но когда я захочу вернитесь к ранее фрагменту (HomeFragment). Я получаю No view found для ошибки id (у меня есть addToBackStack фрагмент).

Решение должно было проверить в onCreateView HomeFragment, если View и другие компоненты уже были созданы, например:

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);
     //...
  }
}

С этой проверкой, когда она возвращается к предыдущему фрагменту, все работает отлично, View, пейджер, список и т.д. уже созданы, поэтому нет необходимости создавать его снова. =)

Ответ 7

Если ваш ViewPager был во Fragment, используйте не getFragmentManager а getChildFragmentManager.

Ответ 8

Я решил это, избегая сохранения экземпляра при создании представления с помощью setRetainInstance(true). Вы можете прокомментировать строку или поставить в ложь

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

Ответ 9

Раньше у меня была такая же проблема, и я попробовал принятый ответ, но это не сработало для меня. После нескольких исследований я нашел свое решение.

Это в моем фрагменте

CustomViewPager customViewPager = (customViewPager) View.inflate(parentActivity, R.layout.custom_viewpager, null);

И это мой макет

<com.test.package.ui.CustomViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>