第一:對於文本/代碼的牆很抱歉,但我認爲大部分是需要了解問題。FragmentPagerAdapter:IllegalStateException無法更改分段的容器ID
我正在使用Fragments
在ViewPager
和TabHost
中創建應用程序。 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
概念有什麼誤解?
我首先這樣做的原因是,我需要保持在每個'Fragment's的狀態。它們都有一些數組包含其中的'ListFragments'數據,這些數據是從數據庫更新的,所以當我滑動或更改制表符時需要數據持久化。最佳做法是什麼? 'FragmentStatePagerAdapter'?如果我將'Fragment'從我的main'layout.xml'中移開,我應該在哪裏放置它們?在他們自己的XML文件中? –
你的情況'FragmentStatePagerAdapter'或'FragmentPagerAdapter'應該沒有太大的區別。 'FragmentPagerAdapter'應該將這些片段保存在內存中,所以你不必擔心數據。此外,如果您的數據在DataBase上,並且您使用的是ContentProvider和Loader,則重新加載並不是什麼大問題。 *如果我將碎片從我的主layout.xml移開,我應該在哪裏放置它們?在他們自己的XML文件中?*都沒有。只是實例化一個運行時。 ViewPager負責爲你處理交易 – Blackbelt
是的,你是對的,數據依然存在。我剛剛對「新」含義感到困惑,好吧,創建一個*新的*'片段'。我沒有意識到它在這種情況下重用了舊的「片段」。我對此有正確的理解嗎?也就是說,當這樣做時,Fragment被重用了嗎?這是內存使用和CPU之間的折衷,對吧? –