Android FragmentPagerAdapter:IllegalStateException可以';t更改片段的容器ID

Android FragmentPagerAdapter:IllegalStateException可以';t更改片段的容器ID,android,android-fragments,android-viewpager,Android,Android Fragments,Android Viewpager,第一:很抱歉有这么多的文本/代码,但我认为大部分内容都是理解问题所必需的 我正在使用ViewPager和TabHost中的Fragments创建一个应用程序。ViewPager有一个自定义的FragmentPagerAdapter,它将为ViewPager中的各个页面提供信息。我遇到了一个问题,自定义FragmentPagerAdapter开始将各种片段添加到backbackback,但在检查容器ID(在本例中是查看页面的ID)与要添加的片段的ID时失败。这些是不同的,因此程序失败。我对使用片段

第一:很抱歉有这么多的文本/代码,但我认为大部分内容都是理解问题所必需的

我正在使用
ViewPager
TabHost
中的
Fragments
创建一个应用程序。
ViewPager
有一个自定义的
FragmentPagerAdapter
,它将为
ViewPager
中的各个页面提供信息。我遇到了一个问题,自定义
FragmentPagerAdapter
开始将各种
片段添加到
backbackback
,但在检查容器ID(在本例中是
查看页面的ID
)与要添加的
片段的ID时失败。这些是不同的,因此程序失败。我对使用
片段相当陌生,所以我不确定我的代码是否遵循最佳实践。下面的错误是什么

活动,它使主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>
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);
    }

}
最后,自定义
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:

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

}
看起来一切正常,但在每个自定义
片段
膨胀后,自定义
查看页面
也开始添加它们。下面是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>
我知道容器ID是自定义
ViewPager
的ID,因为它是在
实例化项(ViewGroup,int)
调用中传递的ID。在我的例子中,
MyViewPager
实例的ID是2131296319
片段的ID是2131296318,因此它失败了


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

问题在于:

@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;
}
您必须返回
片段
的新实例,而不是现有实例,该实例已经有一个父实例,因此有一个已分配的容器。删除您在布局中声明的
片段
s,并更改
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;
}

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

public void populateFragments() {
        Fragment fragment = new Fragment();
        //fragment is added for the first time
        addFragment(fragment);

        // fragment is added for the second time
        // This call will be responsible for the issue. The 
        addFragment(fragment);
    }

    public void addFragment(Fragment fragment) {
        FrameLayout frameLayout = AppView.createFrameLayout(context);
        view.addView(frameLayout);
        getSupportFragmentManager().beginTransaction().add(frameLayout.getId(), fragment).commit();
    } 

我这样做的原因首先是我需要维护每个
片段中的状态。它们都有一些数组,其中包含从数据库更新的
ListFragments
的数据,因此在我滑动或更改选项卡时需要保留这些数据。这方面的最佳做法是什么<代码>FragmentStatePagerAdapter
?如果我将
片段
s从我的主
layout.xml
移开,我应该将它们放在哪里?在他们自己的XML文件中?在您的情况下,
FragmentStatePagerAdapter
FragmentPagerAdapter
应该没有多大区别
FragmentPagerAdapter
应该将片段保存在内存中,这样您就不必担心数据了。另外,如果您的数据在数据库中,并且您使用的是ContentProvider和Loader,那么重新加载也不是什么大问题。如果我将片段从main layout.xml中移出,我应该将它们放在哪里?在他们自己的XML文件中?也不只需将其实例化为运行时。ViewPager负责为您处理事务是的,您是对的,数据仍然存在。我只是对
new
的意思感到困惑,好吧,创建一个新的
片段。我没有意识到它在本例中重用了旧的
片段。我理解对了吗?也就是说,当这样做时,
片段
被重用?这是内存使用和CPU之间的折衷,对吗?是的,
片段
s会一直保存在片段管理器中,只要用户可以返回页面。