27

我遇到了困扰我好几天的问题。

主要活动中有一个ViewPager将 3Fragment秒作为选项卡片段。在第一个片段中,有一个ListView持有一些观点的,最重要的是另一个ViewPager。我想在 sub 中保存一些照片ViewPager,并在这里使用更多的片段。

现在有问题了:
当第一个 Fragment停止(在屏幕上看到父级中的第三个ViewPager片段)并恢复(用户切换到第二个片段)时,应用程序崩溃并且调试器说:

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

我已经使用了,getChildFragmentManager()因为这是嵌套片段的情况。

这里是父ViewPager中第一个fragment对应的listadapter的关键代码:

@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
4

11 回答 11

53

更新:

我已经阅读了 FragmentManager 的源代码,终于得到了真正的原因:当片段想要在 viewpager 附加到其父级之前附加到 viewpager 时,就会发生此异常。换句话说,在 getView() 方法返回之前,片段被膨胀了。然后调用 ViewPager 的容器的 findViewById() 方法,但 ViewPager 还处于分离状态,所以找到 null 并抛出 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。

以下陈述不正确。上面的话是真正的原因。


终于我得到了答案。它由两部分组成。

  1. 在父 ViewPager 中,我使用 aFragmentPagerAdapter来保存片段,但现在我使用 aFragmentStatePagerAdapter代替。这两者之间的区别可以在这里找到:FragmentPagerAdapter 和 FragmentStatePagerAdapter 之间的区别
    简单来说,FragmentPagerAdapter 会在一个 Fragment 停止的时候存储更多的信息。在这种情况下,父 ViewPager 中的第一个 Fragment 被停止但不被销毁,而该 Fragment 中的视图被销毁。恢复后,片段尝试重新膨胀所有视图。但是在getView()调用该方法并重新创建 sub-ViewPager 之前,子 FragmentManager 会尝试找到 sub-ViewPager 来保存那些先前存储的片段。因此出现“java.lang.IllegalArgumentException:没有为 id 找到视图”。

  2. 在我用 FragmentStatePagerAdapter 替换 FragmentPagerAdapter 后,又出现了一个问题。当父片段(父视图分页器中的第一个片段)停止、销毁和恢复时,子视图分页器丢失。这发生在选择第一个片段时,然后很快选择第三个片段,最后重新选择第一个片段。
    我认为这是android sdk的一个错误。灵感来自这里这里,我使用一些棘手的方法来解决问题。关键是,当一个父片段被销毁时,字段成员 --- mChildFragmentManager “以一个损坏的内部状态结束”并且没有完全清理。父fragment重新创建时,mChildFragmentManager不为null,但父fragment销毁后子fragment已经被销毁,由mChildFragmentManager管理。因此,子 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);
        }
    }
于 2013-11-11T06:32:15.733 回答
8

谢伊很好地解释了异常背后的原因。

这是我找到的解决方案:

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

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

}

每当我去一个新的片段并回来时,我都会遇到异常。确保在您的“清理”方法中删除片段,然后您可以在返回片段时重新加载您的视图。

于 2017-02-18T03:53:52.667 回答
8

如果您ViewPager已经在 上Fragment,则使用 not getFragmentManagerbut getChildFragmentManager

于 2018-09-17T15:02:51.240 回答
6

当您有一个 Activity 和一个将包含在 Activity 中的 Fragment 时,您将拥有一个带有 id 的 layout-xml 应该放置 Fragmet 的位置。

如果 id 不在 Activity 中,您将收到此异常。

例子:

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

如果 R.id.banner_container 不是 Activity-layout-xml 中的 id,您将收到异常。

于 2014-03-04T18:37:12.247 回答
6

使用嵌套的 ViewPagers 时遇到了同样的问题。我通过将 FragmentPagerAdapter 都替换为FragmentStatePagerAdapter解决问题。

于 2015-07-22T19:40:30.607 回答
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);
}

而已。

于 2017-03-30T08:35:02.717 回答
3

I had a similar problem. I have a fragment (HomeFragment) with a ListView and a ViewPager which has some fragments inside, everything works great when the fragment starts, when I click in some row in the ListView, it will take me to another fragment, but when I want to go back to the previously fragment (HomeFragment) I get the No view found for id error (I have addToBackStack the fragment).

The solution was to verify in the onCreateView of HomeFragment if the View and other components were already been created, like:

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

With this verification when it comes back to the previously fragment everything works fine, the View, pager, list, etc were already created, so there is no need to create it again. =)

于 2015-04-16T15:04:27.763 回答
1

我在类似的上下文中遇到了同样的错误,但是从协义中选择的答案不起作用。我必须将适配器的设置延迟到下一帧。

 viewPager.post(() -> viewPager.setAdapter(adapter));
于 2020-02-04T10:31:02.923 回答
0

我通过避免在创建视图时保留实例来解决这个问题setRetainInstance(true)。您可以评论该行或将其设置为false

@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);
}
于 2019-06-19T14:17:31.660 回答
0

我遇到了类似的问题。在我的情况下,它是一个包含 ViewPager 的 RecyclerView。然后,当 ViewPager 与 RecyclerView 分离时,RecycleView 的容器首先将其状态更改为停止,然后更改为恢复。它的 FragmentManager 将尝试恢复它添加的 Fragment。但是此时, ViewPager 并未附加到其父级。因此 FragmentManager 抛出异常。

这是源代码:

ViewGroup container = null;
    if (f.mContainerId != 0) {
        if (f.mContainerId == -1) {
            this.throwException(new IllegalArgumentException("Cannot create fragment " + f + " for a container view with no id"));
        }

        container = (ViewGroup) this.mContainer.onFindViewById(f.mContainerId);
        if (container == null && !f.mRestored) {
            String resName;
            try {
                resName = f.getResources().getResourceName(f.mContainerId);
            } catch (NotFoundException var9) {
                resName = "unknown";
            }

            this.throwException(new IllegalArgumentException("No view found for id 0x" + Integer.toHexString(f.mContainerId) + " (" + resName + ") for fragment " + f));
        }
    }

我的解决方案是在 ViewPager 与 RecycleView 分离时分离 ViewPager 的子片段。

这是一个例子:

 @Override
public void postUnBindView(BaseCell cell) {
    super.postUnBindView(cell);
    if (vpFragmentManager != null && mPagerAdapter != null && mPagerAdapter.curFragments != null) {
        FragmentTransaction transaction = vpFragmentManager.beginTransaction();
        for (GuessLikePagerFragment guessLikePagerFragment : mPagerAdapter.curFragments.values()) {
            transaction.detach(guessLikePagerFragment);
        }
        transaction.commit();
    }
}

希望能帮助到你。

于 2020-02-19T01:10:59.563 回答
-2

我之前遇到过同样的问题,我尝试了接受的答案,但它对我不起作用。经过更多研究,我找到了解决方案。

这是我的片段

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"/>
于 2016-04-25T20:10:20.810 回答