IllegalArgumentException:在ViewPager中找不到用于片段--- ViewPager的id的视图

时间:2013-11-10 10:19:09

标签: android android-fragments android-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(),因为这是nested fragments的情况。

以下是与父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

11 个答案:

答案 0 :(得分:49)

更新

我已经阅读了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代替。这两者之间的区别可以在这里找到:Difference between FragmentPagerAdapter and FragmentStatePagerAdapter 简单来说,FragmentPagerAdapter将在片段停止时存储更多信息。在这种情况下,父ViewPager中的第一个片段会被停止但不会被销毁,而此片段中的视图将被销毁。恢复后,片段尝试重新膨胀所有视图。但是在调用getView()方法并重新创建子ViewPager之前,子FragmentManager尝试查找子ViewPager以保存先前存储的片段。因此,“java.lang.IllegalArgumentException:找不到id的视图”。

  2. 用FragmentStatePagerAdapter替换FragmentPagerAdapter后,出现另一个问题。当父片段(父视图中的第一个片段)被停止,销毁和恢复时,子视图抓取器丢失。选择第一个片段时会发生这种情况,之后很快就会选择第三个片段,最后重新选择第一个片段。
    我认为这是android sdk的一个bug。受herehere的启发,我使用一些棘手的方法来解决问题。关键是,当一个父片段被销毁时,字段成员--- mChildFragmentManager“最终会破坏内部状态”并且不会被彻底清除。重新创建父片段时,mChildFragmentManager不为null,但子片段在被销毁之后已被销毁,该片段由mChildFragmentManager管理。因此,子ViewPager在屏幕上显示一个空视图,该视图响应实际上不存在的假片段。有趣的是,在子ViewPager上直接滑动几次之后,子片段和视图再次出现。

  3. 以下是代码:

    父适配器:

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

答案 1 :(得分:5)

如果您拥有Activity所包含的Activity和Fragment,那么您将拥有一个带有id的layout-xml,其中应该放置fragmet。

如果ID不在活动中,您将获得此例外。

示例:

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

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

答案 2 :(得分:5)

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

答案 3 :(得分:5)

谢毅在解释异常背后的推理方面做得很好。

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

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

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

}

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

答案 4 :(得分:4)

虽然谢毅的解决方案完美无缺,但它几乎都是基于反思,这不是一件好事。

作为另一种选择,我设法使用片段事务删除子片段,我相信这应该是这里的首选方法。

首先,将片段生命周期回调注册到子片段管理器,以便我们可以继续引用子片段。

var testStuff="First State";
var Test = React.createClass({
    loadTestData: function () {
        $.ajax({
            url: "http://localhost:64443/api/articles/apitest",
            type: 'GET',
            cache: false,
            dataType: 'json',
            success: function (response) {
                console.log(response);
                testStuff= response;
            },
            error: function (error) {
                alert(error);
            }
        });

    },
    componentDidMount() {
        this.loadTestData();
    },

    render() {
        console.log(this.testStuff);
        return (
          <div onClick={this.loadTestData}>
        {testStuff}
      </div>
    );
}
});
ReactDOM.render(
  <Test />,
  document.getElementById('content')
);

接下来,我们只需要迭代片段列表,将它们从子片段管理器中删除,在onCreateView中:

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

就是这样。

答案 5 :(得分:3)

我有类似的问题。我有一个带有ListView的片段(HomeFragment)和一个里面有一些片段的ViewPager,片段开始时一切都很好用,当我点击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);
     //...
  }
}

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

答案 6 :(得分:1)

如果您的ViewPagerFragment上,则不要使用getFragmentManager,而要使用getChildFragmentManager

答案 7 :(得分:1)

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

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

答案 8 :(得分: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);
}

答案 9 :(得分: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的子片段。

这里是一个例子:

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

希望有帮助。

答案 10 :(得分:-1)

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

这是我的片段

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"/>