参数异常:在ViewPager中找不到id对应的视图 --- 一个ViewPager中嵌套了另一个ViewPager

27

我遇到了困扰我多日的问题。

在主活动中有一个 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(),因为这是嵌套片段的情况。
以下是与父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

post relevant snippet of code - Blackbelt
@blackbelt 代码已添加。 - Xieyi
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中,我使用了一个FragmentPagerAdapter来保存片段,但现在我改用FragmentStatePagerAdapter。这两者之间的区别可以在这里找到:FragmentPagerAdapter和FragmentStatePagerAdapter之间的差异
    简单来说,FragmentPagerAdapter会在停止片段时存储更多信息。在这种情况下,父ViewPager中的第一个片段被停止但未被销毁,而这个片段中的视图被销毁。在恢复后,片段尝试重新填充所有视图。但在调用getView()方法和重新创建子ViewPager之前,子FragmentManager会尝试查找子ViewPager来保存那些先前存储的片段。因此会出现"java.lang.IllegalArgumentException: No view found for id"的错误。

  2. 当我用FragmentStatePagerAdapter替换FragmentPagerAdapter后,出现了另一个问题。当停止、销毁和恢复父片段(父ViewPager中的第一个片段)时,子ViewPager消失了。这发生在选择第一个片段后不久选择第三个片段,最后重新选择第一个片段时。
    我认为这是android sdk的一个bug。受这里这里的启发,我使用了一些巧妙的方法来解决这个问题。关键是,当父片段被销毁时,成员变量mChildFragmentManager "会出现破损的内部状态",并且没有完全清除。当父片段重新创建时,mChildFragmentManager不为null,但是子片段已经在父片段被销毁后被销毁了,这些子片段是由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);
        }
    }

2
这对我来说似乎是有效的 :) 但我不确定为什么我们必须创建一个新方法而不是在重写的setAdapter()中完成相同的工作? - delrocco
5
我对此并不熟悉,但我应该从哪里获取变量mPageIndicator?它位于您的自定义ViewPager中的onAttachedToWindow()函数中。 - ProgDevCode
4
我认为在 onDetachedFromWindow 中调用 super.setAdapter(null) 也是必要的才能使这个方法起作用。我有一个 ViewPager 放在一个选项卡 Fragment 中,它是包含在另一个 ViewPager 中的多个选项卡之一,并且即使使用内部 ViewPager 上的懒惰存储适配器方法,我的应用也会崩溃。在 onDetachedFromWindow 中添加 super.setAdapter(null) 确实似乎可以解决这个问题。 - Dhruv Kapoor
1
非常感谢原始答案,它几乎完美地解决了我的问题,但是使用@DhruvKapoor的方法,在onDetachedFromWindow中将适配器设置为null使其正常工作。感谢你们两个! - Daniel Julio
我有同样的问题,但解决方案对我没有帮助。 - ilyamuromets
显示剩余4条评论

8
如果您的 ViewPager 位于 Fragment 上,则应使用 getChildFragmentManager 而不是 getFragmentManager

8

Xieyi在解释异常背后的原因方面做得很好。

以下是我找到的解决方案:

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

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

}

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


我在哪里可以调用这个方法? - Aditya Vyas-Lakhan
我在我的片段中添加了这个,它启动了我的viewPager。所以,在那里,我检查了f instanceof myViewPager是否需要删除它,一切都正常:D真的非常感谢这个!! - angelos_lex

6

当您有一个活动和一个应该由该活动包含的片段时,您将拥有一个布局xml文件,其中包含应放置片段的id。

如果id未在活动中找到,则会出现此异常。

示例:

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

如果 R.id.banner_container 不是 Activity-layout-xml 中的一个 id,那么你将会遇到异常。

6

替换成什么? - faraday

5
尽管协议的解决方案运行良好,但它基本上是基于反射实现的,这并不是一个好的做法。作为替代方案,我成功地使用碎片事务来删除子碎片,我认为这应该是首选的方法。首先,向子碎片管理器注册一个碎片生命周期回调,以便我们可以保留对子碎片的引用。
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);
}

这就是它。


3

我有一个类似的问题。我有一个带有ListView和ViewPager的碎片(HomeFragment),其中包含一些内部碎片,在碎片启动时,一切都运行良好。当我单击ListView中的某一行时,它将带我到另一个片段,但是当我想返回先前的碎片(HomeFragment)时,我会收到"未找到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)-> 切换到Fragment2 -> 如果我返回到Fragment1,屏幕会变成空白!现在通过您的检查,一切都正常工作了!我没有使用getChildFragmentManager! - basti12354

1
我遇到了与上下文类似的错误,但是Xieyi提供的选定答案并没有起作用。我必须将适配器的设置延迟到下一帧。
 viewPager.post(() -> viewPager.setAdapter(adapter));

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

0
我遇到了类似的问题。 在我的情况下,是一个RecyclerView包含了一个ViewPager。然后,当ViewPager从RecyclerView中分离出来,并且RecycleView的容器首先改变其状态以停止,然后再改变为恢复时。它的FragmentManager将尝试恢复它添加的片段。但是此时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的子Fragment。
以下是一个示例:
 @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();
    }
}

希望能有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接