IllegalArgumentException: No view found for id for fragment --- ViewPager in ViewPager

Posted

技术标签:

【中文标题】IllegalArgumentException: No view found for id for fragment --- ViewPager in ViewPager【英文标题】: 【发布时间】:2013-11-10 10:19:08 【问题描述】:

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

在主活动中有一个ViewPager,其中包含3 个Fragments 作为选项卡片段。在第一个片段中有一个ListView,其中包含一些视图,其中最重要的是另一个ViewPager。我想在子ViewPager里放一些照片,这里再用一些碎片。

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

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

我已经使用了getChildFragmentManager(),因为这是nested fragments的情况。

这里是父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 ScreenSlidePageFragment428d8ea0 #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

【问题讨论】:

发布相关的sn-p代码 @blackbelt 代码已添加。 【参考方案1】:

更新:

我已经阅读了 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。

以下说法不正确。以上是实际原因。


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

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

    我把FragmentPagerAdapter换成FragmentStatePagerAdapter后,又出现了一个问题。当父片段(父视图分页器中的第一个片段)停止、销毁和恢复时,子视图分页器丢失。这发生在选择第一个片段时,然后很快选择第三个片段,最后重新选择第一个片段。 我认为这是android sdk的一个错误。受here 和here 的启发,我使用了一些棘手的方法来解决问题。关键是,当一个父片段被销毁时,字段成员 --- 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);
        
    

【讨论】:

这似乎对我有用 :) 但我不确定为什么我们必须创建一个新方法而不是在覆盖的 setAdapter() 中做同样的工作? 我是个菜鸟,但我从哪里获得变量 mPageIndicator?它位于 onAttachedToWindow() 中的自定义 ViewPager @occorled 我认为我们创建新方法的原因是因为我们不希望 setAdapter() 方法在 ViewPager 正确附加到父级之前触发。因此,我们通过新方法 storeAdapter() 存储 PageAdapter,以便在附加 ViewPager 时使用。我们知道一旦 onAttachedToWindow() 被调用,它就会被附加;因此,一旦 onAttachedToWindow() 被调用,我们在该方法内部调用 setAdapter() 并使用保存的 PageAdapter。 我相信在 onDetachedFromWindow 中调用 super.setAdapter(null) 也是需要的。我在一个选项卡片段中有一个 ViewPager,它是另一个 ViewPager 中包含的多个选项卡之一,即使在内部 ViewPager 上使用惰性存储适配器方法,我的应用程序也会崩溃。在 onDetachedFromWindow 中添加 super.setAdapter(null) 似乎可以解决这个问题。 很棒的原始答案对我来说非常接近,但是使用@DhruvKapoor 方法将适配器设置为null onDetachedFromWindow 使它起作用。谢谢你们!【参考方案2】:

协义很好地解释了异常背后的原因。

这是我找到的解决方案:

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

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


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

【讨论】:

我在哪里可以调用这个方法 我在我的片段上添加了这个,它启动了我的 viewPager。所以在那里,我检查了f instanceof myViewPager 是否将其删除,并且一切正常:D 非常感谢!【参考方案3】:

如果您的ViewPager 已在Fragment 上,则不要使用getFragmentManager,而是使用getChildFragmentManager

【讨论】:

【参考方案4】:

当您有一个 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,则会出现异常。

【讨论】:

【参考方案5】:

在使用嵌套的 ViewPagers 时遇到了同样的问题。 我通过用FragmentStatePagerAdapters 替换FragmentPagerAdapters 解决了这个问题。

【讨论】:

换成什么?【参考方案6】:

虽然协义的解决方案工作得非常好,但它几乎是基于做一个反射,这并不是一件好事。

作为替代答案,我设法使用片段事务删除子片段,我认为这应该是这里的首选方法。

首先,向子片段管理器注册一个片段生命周期回调,以便我们可以保持对子片段的引用。

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

就是这样。

【讨论】:

【参考方案7】:

我遇到了类似的问题。我有一个带有 ListView 和 ViewPager 的片段(HomeFragment),其中有一些片段,片段开始时一切正常,当我单击 ListView 中的某行时,它会将我带到另一个片段,但是当我想要返回上一个片段 (HomeFragment) 我得到 No view found for id 错误(我有 addToBackStack 片段)。

解决方案是在 HomeFragment 的 onCreateView 中验证 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);
     //...
  

通过此验证,当返回到之前的片段时,一切正常,视图、寻呼机、列表等已经创建,因此无需再次创建。 =)

【讨论】:

完美运行!我的问题是:Fragment1(ViewPager inside)-> 更改为 Fragment2-> 如果我返回 Fragment1,屏幕是空白的!现在,通过您的检查,一切正常!我没有使用 getChildFragmentManager!【参考方案8】:

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

 viewPager.post(() -> viewPager.setAdapter(adapter));

【讨论】:

【参考方案9】:

我通过避免在使用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);

【讨论】:

【参考方案10】:

我遇到了类似的问题。 在我的情况下,它是一个包含 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 的子 Fragments。

这是一个例子:

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

希望对你有帮助。

【讨论】:

【参考方案11】:

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

这是在我的片段中

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_
android:layout_/>

【讨论】:

以上是关于IllegalArgumentException: No view found for id for fragment --- ViewPager in ViewPager的主要内容,如果未能解决你的问题,请参考以下文章

IllegalArgumentException:无效的列纬度

Retrofit-IllegalArgumentException:意外的 url

引起:java.lang.IllegalArgumentException:属性'driverClassName'不能为空

IllegalArgumentException:接收方未注册

IllegalArgumentException 介绍

java.lang.IllegalArgumentException:基本 URI 不能为空