FragmentPagerAdapter:IllegalStateException无法更改片段的容器ID

时间:2015-06-07 13:37:03

标签: android android-fragments android-viewpager

首先:对不起文本/代码的墙,但我认为大部分需要了解这个问题。

我正在使用FragmentsViewPager中的TabHost创建应用。 ViewPager有一个自定义FragmentPagerAdapter,可以提供ViewPager中的各个页面。我遇到了一个问题,即自定义FragmentPagerAdapter开始将各种Fragments添加到BackStack,但它在检查容器ID(在这种情况下是ID)时失败了ViewPager)对要添加的Fragments的ID。这些是不同的,因此程序失败。我使用Fragments相当新,所以我不确定我的代码是否遵循最佳实践。以下可能是什么错误?

活动,它会扩展主要的XML布局。

public class MyActivity extends FragmentActivity implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener{
    private MyViewPager                 mViewPager;
    private FragmentTabHost             mFragmentTabHost;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);
        setupViewPager();
        setupFragmentTabHost();
    }

    private void setupViewPager()
    {
        mViewPager = (MyViewPager) findViewById(R.id.my_pager);
    }

    private void setupFragmentTabHost()
    {
        mFragmentTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        mFragmentTabHost.setOnTabChangedListener(this);
        mFragmentTabHost.setup(this, getSupportFragmentManager(), android.R.id.tabcontent);

        mFragmentTabHost.addTab(mFragmentTabHost.newTabSpec("tab1").setIndicator("Tab 1", null), TabFragment.class, null);
        mFragmentTabHost.addTab(mFragmentTabHost.newTabSpec("tab2").setIndicator("Tab 2", null), TabFragment.class, null);
        mFragmentTabHost.addTab(mFragmentTabHost.newTabSpec("tab3").setIndicator("Tab 3", null), TabFragment.class, null);
    }

    @Override
    protected void onDestroy()
    {
    }

    public MyViewPager getMyPager()
    {
        return mViewPager;
    }

    @Override
    public void onTabChanged(String tabId) {
        int position = mFragmentTabHost.getCurrentTab();
        mViewPager.setCurrentItem(position);
    }

    @Override
    public void onPageSelected(int position)
    {
        mFragmentTabHost.setCurrentTab(position);
    }

    @Override
    public void onPageScrollStateChanged(int arg0) {}

    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {}
}

主要的XML文件my_activity.xml,包含ViewPager,TabHost和ViewPager的片段:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <android.support.v4.app.FragmentTabHost
        android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="0dp"
                android:layout_height="0dp"
                android:layout_weight="0" />

            <com.mycompany.myapp.gui.mypager.MyViewPager
                android:id="@+id/my_pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_weight="1" />

            <TabWidget 
                android:id="@android:id/tabs"
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

        </LinearLayout>
    </android.support.v4.app.FragmentTabHost>

    <fragment
        android:name="com.mycompany.myapp.gui.mypager.FilteredRecipesFragment"
        android:id="@+id/filtered_recipes_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true" />

    <fragment
        android:name="com.mycompany.myapp.gui.mypager.SelectedRecipesFragment"
        android:id="@+id/selected_recipes_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true" />

    <fragment
        android:name="com.mycompany.myapp.gui.mypager.ShoppingListFragment"
        android:id="@+id/shopping_list_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:focusable="true" />

</LinearLayout>

请注意,为每个自定义onCreateView调用Fragment,它们会被夸大,并返回每个自定义的根View。以下是FilteredRecipesFragment的一个示例。另一个自定义Fragments类似。

public class FilteredRecipesFragment extends Fragment {

    private FilteredRecipesListFragment mFilteredRecipesListFragment;
    private Button showRecipeFilterButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.filtered_recipes_fragment, container, false);
        mFilteredRecipesListFragment = (FilteredRecipesListFragment)
                getFragmentManager().findFragmentById(R.id.filtered_recipes_list_fragment);
        showRecipeFilterButton = (Button) rootView.findViewById(R.id.show_recipe_filter_dialog_button);
        showRecipeFilterButton.setOnClickListener(new RecipeFilterButtonListener());
        return rootView;
    }
}

最后,自定义ViewPager及其自定义FragmentPagerAdapter,程序失败。

public class MyViewPager extends ViewPager{

    private MyActivity mMyActivity;
    private MyPagerAdapter mMyPagerAdapter;

    public MyViewPager(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mMyActivity = (MyActivity) context;
        mMyPagerAdapter = new MyPagerAdapter(mMyActivity.getSupportFragmentManager(), mMyActivity);
        this.setAdapter(mMyPagerAdapter);
        this.setOnPageChangeListener(mMyActivity);
        this.setCurrentItem(PagerConstants.PAGE_SHOPPING_LIST); // Page 0
    }
}

MyPagerAdapter.java:

public class MyPagerAdapter extends FragmentPagerAdapter{

    private MyActivity mMyActivity;

    public MyPagerAdapter(FragmentManager fragmentManager, MyActivity myActivity)
    {
        super(fragmentManager);
        mMyActivity = myActivity;
    }

    @Override
    public Fragment getItem(int position)
    {
        switch (position) {
        case PagerConstants.PAGE_FILTER_RECIPES: // 0
            return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.filtered_recipes_fragment);

        case PagerConstants.PAGE_SELECTED_RECIPES: // 1
            return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.selected_recipes_fragment);

        case PagerConstants.PAGE_SHOPPING_LIST: // 2
            return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.shopping_list_fragment);

        default:
            return null;
        }
    }

    @Override
    public int getCount()
    {
        return PagerConstants.NUMBER_OF_PAGES; // 3
    }

    @Override
    public CharSequence getPageTitle(int position)
    {
        return PagerConstants.PAGE_TITLES(position);
    }

}

所有似乎都可以正常工作,但在每个自定义Fragment充气后,自定义ViewPager也会开始添加它们。以下是Eclipse

的堆栈输出
06-07 15:37:49.815: E/AndroidRuntime(793): java.lang.IllegalStateException: Can't change container ID of fragment FilteredRecipesFragment{4605b0e0 #0 id=0x7f09003f android:switcher:2131296318:0}: was 2131296319 now 2131296318
BackStackRecord.doAddOp(int, Fragment, String, int) line: 407   
BackStackRecord.add(int, Fragment, String) line: 389    
MyPagerAdapter(FragmentPagerAdapter).instantiateItem(ViewGroup, int) line: 99   
MyViewPager(ViewPager).addNewItem(int, int) line: 832   
MyViewPager(ViewPager).populate(int) line: 982  
MyViewPager(ViewPager).populate() line: 914 
MyViewPager(ViewPager).onMeasure(int, int) line: 1436   
MyViewPager(View).measure(int, int) line: 8171  
LinearLayout(ViewGroup).measureChildWithMargins(View, int, int, int, int) line: 3132
... More calls <snipped>

BackStackRecord.doAppOp中,它失败了,因为容器ID(即MyViewPager的ID与Fragment ID不同。以下是该方法的代码:

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
    fragment.mFragmentManager = mManager;

    if (tag != null) {
        if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
            throw new IllegalStateException("Can't change tag of fragment "
                    + fragment + ": was " + fragment.mTag
                    + " now " + tag);
        }
        fragment.mTag = tag;
    }

    if (containerViewId != 0) {
        if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
            // IT FAILS HERE!
            throw new IllegalStateException("Can't change container ID of fragment "
                    + fragment + ": was " + fragment.mFragmentId
                    + " now " + containerViewId);
        }
        fragment.mContainerId = fragment.mFragmentId = containerViewId;
    }

    Op op = new Op();
    op.cmd = opcmd;
    op.fragment = fragment;
    addOp(op);
}

我知道容器ID是自定义ViewPager的ID,因为它是在instantiateItem(ViewGroup, int)调用中传递的ID。就我而言,MyViewPager实例的ID为213129631 9 Fragment的ID为213129631 8 ,因此失败。< / p>

我在哪里转错了?我在整个ViewPager / FragmentPagerAdapter / Fragment概念中误解了什么?

2 个答案:

答案 0 :(得分:2)

问题在于:

@Override
public Fragment getItem(int position)
{
     switch (position) {
     case PagerConstants.PAGE_FILTER_RECIPES: // 0
        return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.filtered_recipes_fragment);

        case PagerConstants.PAGE_SELECTED_RECIPES: // 1
            return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.selected_recipes_fragment);

        case PagerConstants.PAGE_SHOPPING_LIST: // 2
            return mMyActivity.getSupportFragmentManager().findFragmentById(R.id.shopping_list_fragment);

        default:
            return null;
}

你必须返回Fragment的新实例,而不是现有实例,它已经是父实例,因此是一个容器。移除您在布局中声明的Fragment,并更改getItem之类的

@Override
public Fragment getItem(int position)
{
    switch (position) {
    case PagerConstants.PAGE_FILTER_RECIPES: // 0
        return new FilteredRecipesFragment();

    case PagerConstants.PAGE_SELECTED_RECIPES: // 1
        return new SelectedRecipesFragment();

    case PagerConstants.PAGE_SHOPPING_LIST: // 2
        return new ShoppingListFragment()

    default:
        return null;
}

答案 1 :(得分:1)

此问题的可能原因之一是,您尝试两次添加相同的片段。如下图所示

[
  {
    Name: 'test',
    coolProperty: 'yeahCool1'
  },
  {
    Name: 'test',
    coolProperty: 'yeahCool5'
  }
]