IllegalArgumentException:未找到片段 id 的视图 --- ViewPager 中的 ViewPager [英] IllegalArgumentException: No view found for id for fragment --- ViewPager in ViewPager

查看:29
本文介绍了IllegalArgumentException:未找到片段 id 的视图 --- ViewPager 中的 ViewPager的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've met the problem that trouble me for days.

主活动中有一个 ViewPager,它包含 3 个 Fragment 作为选项卡片段.在第一个片段中有一个ListView,它包含一些视图,最重要的是另一个ViewPager.我想在子 ViewPager 中保存一些照片,并在此处使用更多片段.

There is a ViewPager in the main activity which holds 3 Fragments as tab fragments. In the first fragment there is a ListView which holds some views, and which is the most important, another ViewPager. I want to hold some photos in the sub ViewPager, and use some more fragments here.

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

Now there is the trouble:
When the first Fragment is stopped (The third fragment in the parent ViewPager is seen on screen) and resumed (the user switch to the second fragment), the app crashes and the debugger says:

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

我已经使用了 getChildFragmentManager() 因为这是 嵌套片段.

I've already use the getChildFragmentManager() as this is a situation of nested fragments.

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

Here is the key code of list adapter corresponding to the first fragment in the parent 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;
        }
    }
}

这里是详细的错误列表:

Here is the detailed error list:

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

推荐答案

更新:

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

I've read the source code of the FragmentManager and finally got the true reason: This exception happens when the fragments want to be attached to the viewpager before the viewpager is attached to its parent. In other words, before the getView() method returns, the fragments are inflated. Then the findViewById() method of the container of the ViewPager is called but the ViewPager is in detached state yet, so null is found and the IllegalArgumentException is thrown.

解决办法是创建一个自定义的ViewPager并延迟设置适配器:

The solution is to create a custom ViewPager and lazy set the adapter:

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.

And in the getView() method, use storeAdapter() instead of setAdapter.

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

终于知道答案了.它由两部分组成.

Finally I've got the answer. It consists of two parts.

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

  1. In the parent ViewPager I used a FragmentPagerAdapter to hold fragments, but now I use a FragmentStatePagerAdapter instead. The difference between these two can be found here: Difference between FragmentPagerAdapter and FragmentStatePagerAdapter.
    Simply speaking, FragmentPagerAdapter will store more information when a fragment is stopped. In this situation, the first fragment in the parent ViewPager is stopped but not destroyed while the views in this fragment are destroyed. After resumed, the fragment try to re-inflate all the views. But before the getView() method is called and the sub-ViewPager is recreated, the child FragmentManager tries to find the sub-ViewPager to hold those previously stored fragments. Thus the "java.lang.IllegalArgumentException: No view found for id" occurs.

将 FragmentPagerAdapter 替换为 FragmentStatePagerAdapter 后,又出现了一个问题.当父片段(父viewpager 中的第一个片段)停止、销毁和恢复时,子viewpager 丢失.This happens when the first fragment is choosen, soon afterwards the third fragment is choosen, and at last the first fragment is re-choosen.
我认为这是android sdk的一个错误.灵感来自此处这里,我使用了一些棘手的方法来解决问题.关键是,当父片段被销毁时,字段成员 --- mChildFragmentManager 以破坏的内部状态结束"并且没有被完全清除.重新创建父片段时,mChildFragmentManager 不为空,但是子片段在父片段被销毁后已经被销毁,由mChildFragmentManager 管理.因此子 ViewPager 在屏幕上显示一个空视图,它响应一个实际上不存在的假片段.有趣的是,在子ViewPager上向右滑动几次后,子片段和视图又出现了.

After I replaced the FragmentPagerAdapter with FragmentStatePagerAdapter, another problem appears. the sub-viewpager is missing when the parent fragment (the first fragment in the parent viewpager) was stopped, destroyed and resumed. This happens when the first fragment is choosen, soon afterwards the third fragment is choosen, and at last the first fragment is re-choosen.
I think this is a bug of android sdk. Inspired by here and here, I use some tricky methods to solve the problem. The point is, when a parent fragment is destroyed, the field member --- mChildFragmentManager "ends up with a broken internal state" and is not totally cleaned up. When the parent fragment is recreated, mChildFragmentManager is not null, but the sub fragments is already been destroyed after the parent fragment being destroyed, which was managered by mChildFragmentManager. Thus the sub ViewPager displays an empty view on the screen, which responds to a fake fragment which is actually not existing. The funny thing is, after swiping right on the sub ViewPager for several times, the sub fragments and the views appear again.

代码如下:

父适配器:

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

这篇关于IllegalArgumentException:未找到片段 id 的视图 --- ViewPager 中的 ViewPager的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆