2015-06-07 25 views
1

第一:對於文本/代碼的牆很抱歉,但我認爲大部分是需要了解問題。FragmentPagerAdapter:IllegalStateException無法更改分段的容器ID

我正在使用FragmentsViewPagerTabHost中創建應用程序。 ViewPager有一個自定義FragmentPagerAdapter,它將提供ViewPager中的各個頁面。我遇到了一個問題,自定義FragmentPagerAdapter開始將各種Fragments添加到BackStack,但它在檢查容器ID(在這種情況下爲ViewPager的ID)與要添加的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,因爲這是它的ID通過在instantiateItem(ViewGroup, int)調用中傳遞。在我的案例中,MyViewPager實例ID爲213129631 和的ID是213129631 ,因此它失敗。

我在哪裏轉錯了?我對整個ViewPager/FragmentPagerAdapter/Fragment概念有什麼誤解?

回答

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 s的新實例,而不是現有的,它已經父,因此,assinged的容器。刪除您在佈局中聲明的Fragment 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; 
} 
+0

我首先這樣做的原因是,我需要保持在每個'Fragment's的狀態。它們都有一些數組包含其中的'ListFragments'數據,這些數據是從數據庫更新的,所以當我滑動或更改制表符時需要數據持久化。最佳做法是什麼? 'FragmentStatePagerAdapter'?如果我將'Fragment'從我的main'layout.xml'中移開,我應該在哪裏放置它們?在他們自己的XML文件中? –

+0

你的情況'FragmentStatePagerAdapter'或'FragmentPagerAdapter'應該沒有太大的區別。 'FragmentPagerAdapter'應該將這些片段保存在內存中,所以你不必擔心數據。此外,如果您的數據在DataBase上,並且您使用的是ContentProvider和Loader,則重新加載並不是什麼大問題。 *如果我將碎片從我的主layout.xml移開,我應該在哪裏放置它們?在他們自己的XML文件中?*都沒有。只是實例化一個運行時。 ViewPager負責爲你處理交易 – Blackbelt

+0

是的,你是對的,數據依然存在。我剛剛對「新」含義感到困惑,好吧,創建一個*新的*'片段'。我沒有意識到它在這種情況下重用了舊的「片段」。我對此有正確的理解嗎?也就是說,當這樣做時,Fragment被重用了嗎?這是內存使用和CPU之間的折衷,對吧? –

相關問題