replaceViewPager中的碎片
我试图使用FragmentPagerAdapter与ViewPager使用片段。 我正在寻找的是将位于ViewPager第一页的片段replace为另一个片段。
寻呼机由两页组成。 第一个是FirstPagerFragment,第二个是SecondPagerFragment。 点击第一页的button。 我想用NextFragmentreplaceFirstPagerFragment。
下面是我的代码。
public class FragmentPagerActivity extends FragmentActivity { static final int NUM_ITEMS = 2; MyAdapter mAdapter; ViewPager mPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_pager); mAdapter = new MyAdapter(getSupportFragmentManager()); mPager = (ViewPager) findViewById(R.id.pager); mPager.setAdapter(mAdapter); } /** * Pager Adapter */ public static class MyAdapter extends FragmentPagerAdapter { public MyAdapter(FragmentManager fm) { super(fm); } @Override public int getCount() { return NUM_ITEMS; } @Override public Fragment getItem(int position) { if(position == 0) { return FirstPageFragment.newInstance(); } else { return SecondPageFragment.newInstance(); } } } /** * Second Page FRAGMENT */ public static class SecondPageFragment extends Fragment { public static SecondPageFragment newInstance() { SecondPageFragment f = new SecondPageFragment(); return f; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //Log.d("DEBUG", "onCreateView"); return inflater.inflate(R.layout.second, container, false); } } /** * FIRST PAGE FRAGMENT */ public static class FirstPageFragment extends Fragment { Button button; public static FirstPageFragment newInstance() { FirstPageFragment f = new FirstPageFragment(); return f; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //Log.d("DEBUG", "onCreateView"); View root = inflater.inflate(R.layout.first, container, false); button = (Button) root.findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { FragmentTransaction trans = getFragmentManager().beginTransaction(); trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance()); trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); trans.addToBackStack(null); trans.commit(); } }); return root; } /** * Next Page FRAGMENT in the First Page */ public static class NextFragment extends Fragment { public static NextFragment newInstance() { NextFragment f = new NextFragment(); return f; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { //Log.d("DEBUG", "onCreateView"); return inflater.inflate(R.layout.next, container, false); } } }
…和这里的XML文件
fragment_pager.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:padding="4dip" android:gravity="center_horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> </android.support.v4.view.ViewPager> </LinearLayout>
first.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/first_fragment_root_id" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="to next"/> </LinearLayout>
现在的问题…我应该使用哪个ID
trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
?
如果使用R.id.first_fragment_root_id,replace工作,但层次结构查看器显示一个奇怪的行为,如下所示。
一开始的情况是
更换后的情况是
正如你所看到的那样,有一些错误,我希望在replace片段之后find与第一张图片相同的状态。
还有另一种解决scheme,不需要修改ViewPager
和FragmentStatePagerAdapter
源代码,它可以和作者使用的FragmentPagerAdapter
基类一起工作。
我想首先回答作者关于他应该使用哪个ID的问题; 它是容器的ID,即视图寻呼机本身的ID。 然而,正如你可能注意到你自己,在你的代码中使用该ID不会导致任何事情发生。 我将解释为什么:
首先,要使ViewPager
重新填充页面,您需要调用驻留在适配器的基类中的notifyDataSetChanged()
。
其次, ViewPager
使用getItemPosition()
抽象方法来检查哪些页面应该被销毁,哪些应该被保留。 这个函数的默认实现总是返回POSITION_UNCHANGED
,这会导致ViewPager
保留所有当前页面,因此不会附加新的页面。 因此,要使片段replace工作, getItemPosition()
需要在您的适配器中被覆盖,并且在使用旧的被隐藏片段作为参数调用时必须返回POSITION_NONE
。
这也意味着你的适配器总是需要知道应该在位置0, FirstPageFragment
或NextFragment
显示哪个片段。 这样做的一个方法是在创buildFirstPageFragment
时提供一个监听器,当切换片段时会调用它。 我认为这是一件好事,让您的片段适配器处理所有的片段切换和调用ViewPager
和FragmentManager
。
第三, FragmentPagerAdapter
通过从位置派生的名称来caching已使用的片段,所以如果在位置0有一个片段,即使该类是新的,它也不会被replace。 有两种解决scheme,但最简单的方法是使用FragmentTransaction
的remove()
函数,它也会删除它的标签。
这是很多的文字,这里是应该在你的情况下工作的代码:
public class MyAdapter extends FragmentPagerAdapter { static final int NUM_ITEMS = 2; private final FragmentManager mFragmentManager; private Fragment mFragmentAtPos0; public MyAdapter(FragmentManager fm) { super(fm); mFragmentManager = fm; } @Override public Fragment getItem(int position) { if (position == 0) { if (mFragmentAtPos0 == null) { mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener() { public void onSwitchToNextFragment() { mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit(); mFragmentAtPos0 = NextFragment.newInstance(); notifyDataSetChanged(); } }); } return mFragmentAtPos0; } else return SecondPageFragment.newInstance(); } @Override public int getCount() { return NUM_ITEMS; } @Override public int getItemPosition(Object object) { if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment) return POSITION_NONE; return POSITION_UNCHANGED; } } public interface FirstPageFragmentListener { void onSwitchToNextFragment(); }
希望这有助于任何人!
截至2012年11月13日,在ViewPager中修复碎片似乎变得更容易了。 Google发布了支持嵌套片段的Android 4.2,并且在新的Android支持库v11中也支持它,所以这一切都可以回到1.6
除了使用getChildFragmentManager之外,它与replace片段的正常方式非常类似。 它似乎工作,除了当用户单击后退button时, 不会popup嵌套的片段backstack 。 根据该链接问题的解决scheme,您需要手动调用片段的子pipe理器上的popBackStackImmediate()。 因此,您需要重写ViewPager活动的onBackPressed(),您将获得ViewPager的当前片段,并调用getChildFragmentManager()。popBackStackImmediate()。
获取当前显示的片段也有点冒险 ,我使用了这个肮脏的“android:switcher:VIEWPAGER_ID:INDEX”解决scheme,但是您也可以自己跟踪ViewPager的所有片段,如本页第二个解决scheme中所述。
所以这里是我的ViewPager的代码,带有4个ListView,当用户点击一行,并且后退button工作时,在ViewPager中显示一个详细视图。 为了简洁起见,我试图只包含相关的代码,所以如果你想把完整的应用程序上传到GitHub,留下评论。
HomeActivity.java
public class HomeActivity extends SherlockFragmentActivity { FragmentAdapter mAdapter; ViewPager mPager; TabPageIndicator mIndicator; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mAdapter = new FragmentAdapter(getSupportFragmentManager()); mPager = (ViewPager)findViewById(R.id.pager); mPager.setAdapter(mAdapter); mIndicator = (TabPageIndicator)findViewById(R.id.indicator); mIndicator.setViewPager(mPager); } // This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update. @Override public void onBackPressed() { Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem()); if (fragment != null) // could be null if not instantiated yet { if (fragment.getView() != null) { // Pop the backstack on the ChildManager if there is any. If not, close this activity as normal. if (!fragment.getChildFragmentManager().popBackStackImmediate()) { finish(); } } } } class FragmentAdapter extends FragmentPagerAdapter { public FragmentAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { switch (position) { case 0: return ListProductsFragment.newInstance(); case 1: return ListActiveSubstancesFragment.newInstance(); case 2: return ListProductFunctionsFragment.newInstance(); case 3: return ListCropsFragment.newInstance(); default: return null; } } @Override public int getCount() { return 4; } } }
ListProductsFragment.java
public class ListProductsFragment extends SherlockFragment { private ListView list; public static ListProductsFragment newInstance() { ListProductsFragment f = new ListProductsFragment(); return f; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View V = inflater.inflate(R.layout.list, container, false); list = (ListView)V.findViewById(android.R.id.list); list.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // This is important bit Fragment productDetailFragment = FragmentProductDetail.newInstance(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); transaction.addToBackStack(null); transaction.replace(R.id.products_list_linear, productDetailFragment).commit(); } }); return V; } }
基于@wize的答案,我发现这个答案很有帮助和优雅,我可以实现我想要的一部分,因为我希望能够回到第一个片段。 我实现了一点他的代码修改。
这将是FragmentPagerAdapter:
public static class MyAdapter extends FragmentPagerAdapter { private final class CalendarPageListener implements CalendarPageFragmentListener { public void onSwitchToNextFragment() { mFragmentManager.beginTransaction().remove(mFragmentAtPos0) .commit(); if (mFragmentAtPos0 instanceof FirstFragment){ mFragmentAtPos0 = NextFragment.newInstance(listener); }else{ // Instance of NextFragment mFragmentAtPos0 = FirstFragment.newInstance(listener); } notifyDataSetChanged(); } } CalendarPageListener listener = new CalendarPageListener();; private Fragment mFragmentAtPos0; private FragmentManager mFragmentManager; public MyAdapter(FragmentManager fm) { super(fm); mFragmentManager = fm; } @Override public int getCount() { return NUM_ITEMS; } @Override public int getItemPosition(Object object) { if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment) return POSITION_NONE; if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment) return POSITION_NONE; return POSITION_UNCHANGED; } @Override public Fragment getItem(int position) { if (position == 0) return Portada.newInstance(); if (position == 1) { // Position where you want to replace fragments if (mFragmentAtPos0 == null) { mFragmentAtPos0 = FirstFragment.newInstance(listener); } return mFragmentAtPos0; } if (position == 2) return Clasificacion.newInstance(); if (position == 3) return Informacion.newInstance(); return null; } } public interface CalendarPageFragmentListener { void onSwitchToNextFragment(); }
要执行replace,只需定义CalendarPageFragmentListener
types的静态字段,并通过相应片段的newInstance
方法初始化,然后调用FirstFragment.pageListener.onSwitchToNextFragment()
或NextFragment.pageListener.onSwitchToNextFragment()
。
希望这是明确的和有益的。
最好的祝福。
我已经实施了一个解决scheme:
- 在选项卡内dynamic碎片replace
- 维护每个选项卡的历史logging
- 处理方向更改
实现这一目的的技巧如下:
- 使用notifyDataSetChanged()方法来应用片段replace
- 使用片段pipe理器仅用于后台,不用于replace
- 使用纪念图案(堆叠)保持历史logging
适配器代码如下:
public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener { /** The sherlock fragment activity. */ private final SherlockFragmentActivity mActivity; /** The action bar. */ private final ActionBar mActionBar; /** The pager. */ private final ViewPager mPager; /** The tabs. */ private List<TabInfo> mTabs = new LinkedList<TabInfo>(); /** The total number of tabs. */ private int TOTAL_TABS; private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>(); /** * Creates a new instance. * * @param activity the activity * @param pager the pager */ public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) { super(activity.getSupportFragmentManager()); activity.getSupportFragmentManager(); this.mActivity = activity; this.mActionBar = activity.getSupportActionBar(); this.mPager = pager; mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); } /** * Adds the tab. * * @param image the image * @param fragmentClass the class * @param args the arguments */ public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) { final TabInfo tabInfo = new TabInfo(fragmentClass, args); final ActionBar.Tab tab = mActionBar.newTab(); tab.setTabListener(this); tab.setTag(tabInfo); tab.setIcon(image); mTabs.add(tabInfo); mActionBar.addTab(tab); notifyDataSetChanged(); } @Override public Fragment getItem(final int position) { final TabInfo tabInfo = mTabs.get(position); return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args); } @Override public int getItemPosition(final Object object) { /* Get the current position. */ int position = mActionBar.getSelectedTab().getPosition(); /* The default value. */ int pos = POSITION_NONE; if (history.get(position).isEmpty()) { return POSITION_NONE; } /* Checks if the object exists in current history. */ for (Stack<TabInfo> stack : history.values()) { TabInfo c = stack.peek(); if (c.fragmentClass.getName().equals(object.getClass().getName())) { pos = POSITION_UNCHANGED; break; } } return pos; } @Override public int getCount() { return mTabs.size(); } @Override public void onPageScrollStateChanged(int arg0) { } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int position) { mActionBar.setSelectedNavigationItem(position); } @Override public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) { TabInfo tabInfo = (TabInfo) tab.getTag(); for (int i = 0; i < mTabs.size(); i++) { if (mTabs.get(i).equals(tabInfo)) { mPager.setCurrentItem(i); } } } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { } public void replace(final int position, final Class fragmentClass, final Bundle args) { /* Save the fragment to the history. */ mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit(); /* Update the tabs. */ updateTabs(new TabInfo(fragmentClass, args), position); /* Updates the history. */ history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args)); notifyDataSetChanged(); } /** * Updates the tabs. * * @param tabInfo * the new tab info * @param position * the position */ private void updateTabs(final TabInfo tabInfo, final int position) { mTabs.remove(position); mTabs.add(position, tabInfo); mActionBar.getTabAt(position).setTag(tabInfo); } /** * Creates the history using the current state. */ public void createHistory() { int position = 0; TOTAL_TABS = mTabs.size(); for (TabInfo mTab : mTabs) { if (history.get(position) == null) { history.put(position, new Stack<TabInfo>()); } history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args)); position++; } } /** * Called on back */ public void back() { int position = mActionBar.getSelectedTab().getPosition(); if (!historyIsEmpty(position)) { /* In case there is not any other item in the history, then finalize the activity. */ if (isLastItemInHistory(position)) { mActivity.finish(); } final TabInfo currentTabInfo = getPrevious(position); mTabs.clear(); for (int i = 0; i < TOTAL_TABS; i++) { if (i == position) { mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args)); } else { TabInfo otherTabInfo = history.get(i).peek(); mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args)); } } } mActionBar.selectTab(mActionBar.getTabAt(position)); notifyDataSetChanged(); } /** * Returns if the history is empty. * * @param position * the position * @return the flag if empty */ private boolean historyIsEmpty(final int position) { return history == null || history.isEmpty() || history.get(position).isEmpty(); } private boolean isLastItemInHistory(final int position) { return history.get(position).size() == 1; } /** * Returns the previous state by the position provided. * * @param position * the position * @return the tab info */ private TabInfo getPrevious(final int position) { TabInfo currentTabInfo = history.get(position).pop(); if (!history.get(position).isEmpty()) { currentTabInfo = history.get(position).peek(); } return currentTabInfo; } /** The tab info class */ private static class TabInfo { /** The fragment class. */ public Class fragmentClass; /** The args.*/ public Bundle args; /** * Creates a new instance. * * @param fragmentClass * the fragment class * @param args * the args */ public TabInfo(Class fragmentClass, Bundle args) { this.fragmentClass = fragmentClass; this.args = args; } @Override public boolean equals(final Object o) { return this.fragmentClass.getName().equals(o.getClass().getName()); } @Override public int hashCode() { return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0; } @Override public String toString() { return "TabInfo{" + "fragmentClass=" + fragmentClass + '}'; } }
首次添加所有选项卡时,我们需要调用createHistory()方法来创build初始历史logging
public void createHistory() { int position = 0; TOTAL_TABS = mTabs.size(); for (TabInfo mTab : mTabs) { if (history.get(position) == null) { history.put(position, new Stack<TabInfo>()); } history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args)); position++; } }
每次你想要replace一个片段到一个特定的选项卡,你可以调用:replace(final int position,final Class fragmentClass,final Bundle args)
/* Save the fragment to the history. */ mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit(); /* Update the tabs. */ updateTabs(new TabInfo(fragmentClass, args), position); /* Updates the history. */ history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args)); notifyDataSetChanged();
按回来,你需要调用back()方法:
public void back() { int position = mActionBar.getSelectedTab().getPosition(); if (!historyIsEmpty(position)) { /* In case there is not any other item in the history, then finalize the activity. */ if (isLastItemInHistory(position)) { mActivity.finish(); } final TabInfo currentTabInfo = getPrevious(position); mTabs.clear(); for (int i = 0; i < TOTAL_TABS; i++) { if (i == position) { mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args)); } else { TabInfo otherTabInfo = history.get(i).peek(); mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args)); } } } mActionBar.selectTab(mActionBar.getTabAt(position)); notifyDataSetChanged(); }
该解决scheme适用于sherlock操作栏和轻扫手势。
我已经为索引2和3创build了一个具有3个元素和2个子元素的ViewPager,这里是我想要做的。
我已经在StackOverFlow的以前的问题和答案的帮助下实现了这一点,这里是链接。
ViewPagerChildFragments
要replaceViewPager
的片段,可以将ViewPager
, PagerAdapter
和FragmentStatePagerAdapter
类的源代码移动到您的项目中,并添加以下代码。
到ViewPager
:
public void notifyItemChanged(Object oldItem, Object newItem) { if (mItems != null) { for (ItemInfo itemInfo : mItems) { if (itemInfo.object.equals(oldItem)) { itemInfo.object = newItem; } } } invalidate(); }
进入FragmentStatePagerAdapter:
public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) { startUpdate(container); // remove old fragment if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } int position = getFragmentPosition(oldFragment); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, null); mFragments.set(position, null); mCurTransaction.remove(oldFragment); // add new fragment while (mFragments.size() <= position) { mFragments.add(null); } mFragments.set(position, newFragment); mCurTransaction.add(container.getId(), newFragment); finishUpdate(container); // ensure getItem returns newFragemtn after calling handleGetItemInbalidated() handleGetItemInbalidated(container, oldFragment, newFragment); container.notifyItemChanged(oldFragment, newFragment); } protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment); protected abstract int getFragmentPosition(Fragment fragment);
handleGetItemInbalidated()
确保在下一次调用getItem()
它返回newFragment getFragmentPosition()
返回适配器中片段的位置。
现在,要更换片段调用
mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);
如果您对示例项目感兴趣,请向我索取源代码。
一些提出的解决scheme帮助我部分解决了这个问题,但是仍然有一个重要的东西在某些情况下产生了意想不到的exception和黑色页面内容而不是片段内容。
问题是FragmentPagerAdapter类正在使用项目ID将caching片段存储到FragmentManager 。 由于这个原因,你还需要重写getItemId(int position)方法,以便返回例如顶层页面的位置和详细页面的100 +位置 。 否则,先前创build的顶级片段将从caching而不是详细级片段返回。
此外,我在这里分享一个完整的例子,如何使用ViewPager和使用RadioGroup的选项卡button实现带有片段页面的标签式活动,允许用详细页面replace顶层页面,并且还支持后退button。 该实现仅支持一个级别的后台堆叠(项目列表 – 项目详细信息),但是多级后台堆叠实施非常简单。 这个例子在正常情况下工作得很好,除了当你切换到例如第二页,改变第一页的片段(不可见)并返回到第一页时,抛出NullPointerException 。 我会解决这个问题,一旦我弄明白了:
public class TabsActivity extends FragmentActivity { public static final int PAGE_COUNT = 3; public static final int FIRST_PAGE = 0; public static final int SECOND_PAGE = 1; public static final int THIRD_PAGE = 2; /** * Opens a new inferior page at specified tab position and adds the current page into back * stack. */ public void startPage(int position, Fragment content) { // Replace page adapter fragment at position. mPagerAdapter.start(position, content); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize basic layout. this.setContentView(R.layout.tabs_activity); // Add tab fragments to view pager. { // Create fragments adapter. mPagerAdapter = new PagerAdapter(pager); ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager); pager.setAdapter(mPagerAdapter); // Update active tab in tab bar when page changes. pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int index, float value, int nextIndex) { // Not used. } @Override public void onPageSelected(int index) { RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById( R.id.tabs_radio_group); switch (index) { case 0: { tabs_radio_group.check(R.id.first_radio_button); } break; case 1: { tabs_radio_group.check(R.id.second_radio_button); } break; case 2: { tabs_radio_group.check(R.id.third_radio_button); } break; } } @Override public void onPageScrollStateChanged(int index) { // Not used. } }); } // Set "tabs" radio group on checked change listener that changes the displayed page. RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group); radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup radioGroup, int id) { // Get view pager representing tabs. ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager); if (view_pager == null) { return; } // Change the active page. switch (id) { case R.id.first_radio_button: { view_pager.setCurrentItem(FIRST_PAGE); } break; case R.id.second_radio_button: { view_pager.setCurrentItem(SECOND_PAGE); } break; case R.id.third_radio_button: { view_pager.setCurrentItem(THIRD_PAGE); } break; } }); } } @Override public void onBackPressed() { if (!mPagerAdapter.back()) { super.onBackPressed(); } } /** * Serves the fragments when paging. */ private class PagerAdapter extends FragmentPagerAdapter { public PagerAdapter(ViewPager container) { super(TabsActivity.this.getSupportFragmentManager()); mContainer = container; mFragmentManager = TabsActivity.this.getSupportFragmentManager(); // Prepare "empty" list of fragments. mFragments = new ArrayList<Fragment>(){}; mBackFragments = new ArrayList<Fragment>(){}; for (int i = 0; i < PAGE_COUNT; i++) { mFragments.add(null); mBackFragments.add(null); } } /** * Replaces the view pager fragment at specified position. */ public void replace(int position, Fragment fragment) { // Get currently active fragment. Fragment old_fragment = mFragments.get(position); if (old_fragment == null) { return; } // Replace the fragment using transaction and in underlaying array list. // NOTE .addToBackStack(null) doesn't work this.startUpdate(mContainer); mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .remove(old_fragment).add(mContainer.getId(), fragment) .commit(); mFragments.set(position, fragment); this.notifyDataSetChanged(); this.finishUpdate(mContainer); } /** * Replaces the fragment at specified position and stores the current fragment to back stack * so it can be restored by #back(). */ public void start(int position, Fragment fragment) { // Remember current fragment. mBackFragments.set(position, mFragments.get(position)); // Replace the displayed fragment. this.replace(position, fragment); } /** * Replaces the current fragment by fragment stored in back stack. Does nothing and returns * false if no fragment is back-stacked. */ public boolean back() { int position = mContainer.getCurrentItem(); Fragment fragment = mBackFragments.get(position); if (fragment == null) { // Nothing to go back. return false; } // Restore the remembered fragment and remove it from back fragments. this.replace(position, fragment); mBackFragments.set(position, null); return true; } /** * Returns fragment of a page at specified position. */ @Override public Fragment getItem(int position) { // If fragment not yet initialized, create its instance. if (mFragments.get(position) == null) { switch (position) { case FIRST_PAGE: { mFragments.set(FIRST_PAGE, new DefaultFirstFragment()); } break; case SECOND_PAGE: { mFragments.set(SECOND_PAGE, new DefaultSecondFragment()); } break; case THIRD_PAGE: { mFragments.set(THIRD_PAGE, new DefaultThirdFragment()); } break; } } // Return fragment instance at requested position. return mFragments.get(position); } /** * Custom item ID resolution. Needed for proper page fragment caching. * @see FragmentPagerAdapter#getItemId(int). */ @Override public long getItemId(int position) { // Fragments from second level page hierarchy have their ID raised above 100. This is // important to FragmentPagerAdapter because it is caching fragments to FragmentManager with // this item ID key. Fragment item = mFragments.get(position); if (item != null) { if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) || (item instanceof NewThirdFragment)) { return 100 + position; } } return position; } /** * Returns number of pages. */ @Override public int getCount() { return mFragments.size(); } @Override public int getItemPosition(Object object) { int position = POSITION_UNCHANGED; if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) { if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) { position = POSITION_NONE; } } if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) { if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) { position = POSITION_NONE; } } if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) { if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) { position = POSITION_NONE; } } return position; } private ViewPager mContainer; private FragmentManager mFragmentManager; /** * List of page fragments. */ private List<Fragment> mFragments; /** * List of page fragments to return to in onBack(); */ private List<Fragment> mBackFragments; } /** * Tab fragments adapter. */ private PagerAdapter mPagerAdapter; }
tl;dr: Use a host fragment that is responsible for replacing its hosted content and keeps track of a back navigation history (like in a browser).
As your use case consists of a fixed amount of tabs my solution works well: The idea is to fill the ViewPager with instances of a custom class HostFragment
, that is able to replace its hosted content and keeps its own back navigation history. To replace the hosted fragment you make a call to the method hostfragment.replaceFragment()
:
public void replaceFragment(Fragment fragment, boolean addToBackstack) { if (addToBackstack) { getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit(); } else { getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit(); } }
All that method does is to replace the frame layout with the id R.id.hosted_fragment
with the fragment provided to the method.
Check my tutorial on this topic for further details and a complete working example on GitHub!
Works Great with AndroidTeam's solution, however I found that I needed the ability to go back much like FrgmentTransaction.addToBackStack(null)
But merely adding this will only cause the Fragment to be replaced without notifying the ViewPager. Combining the provided solution with this minor enhancement will allow you to return to the previous state by merely overriding the activity's onBackPressed()
method. The biggest drawback is that it will only go back one at a time which may result in multiple back clicks
private ArrayList<Fragment> bFragments = new ArrayList<Fragment>(); private ArrayList<Integer> bPosition = new ArrayList<Integer>(); public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) { startUpdate(container); // remove old fragment if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } int position = getFragmentPosition(oldFragment); while (mSavedState.size() <= position) { mSavedState.add(null); } //Add Fragment to Back List bFragments.add(oldFragment); //Add Pager Position to Back List bPosition.add(position); mSavedState.set(position, null); mFragments.set(position, null); mCurTransaction.remove(oldFragment); // add new fragment while (mFragments.size() <= position) { mFragments.add(null); } mFragments.set(position, newFragment); mCurTransaction.add(container.getId(), newFragment); finishUpdate(container); // ensure getItem returns newFragemtn after calling handleGetItemInbalidated() handleGetItemInvalidated(container, oldFragment, newFragment); container.notifyItemChanged(oldFragment, newFragment); } public boolean popBackImmediate(ViewPager container){ int bFragSize = bFragments.size(); int bPosSize = bPosition.size(); if(bFragSize>0 && bPosSize>0){ if(bFragSize==bPosSize){ int last = bFragSize-1; int position = bPosition.get(last); //Returns Fragment Currently at this position Fragment replacedFragment = mFragments.get(position); Fragment originalFragment = bFragments.get(last); this.replaceFragments(container, replacedFragment, originalFragment); bPosition.remove(last); bFragments.remove(last); return true; } } return false; }
希望这有助于某人。
Also as far as getFragmentPosition()
goes it's pretty much getItem()
in reverse. You know which fragments go where, just make sure you return the correct position it will be in. Here's an example:
@Override protected int getFragmentPosition(Fragment fragment) { if(fragment.equals(originalFragment1)){ return 0; } if(fragment.equals(replacementFragment1)){ return 0; } if(fragment.equals(Fragment2)){ return 1; } return -1; }
I also made a solution, which is working with Stacks . It's a more modular approach so u don't have to specify each Fragment and Detail Fragment in your FragmentPagerAdapter
. It's build on top of the Example from ActionbarSherlock which derives if I'm right from the Google Demo App.
/** * This is a helper class that implements the management of tabs and all * details of connecting a ViewPager with associated TabHost. It relies on a * trick. Normally a tab host has a simple API for supplying a View or * Intent that each tab will show. This is not sufficient for switching * between pages. So instead we make the content part of the tab host * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy * view to show as the tab content. It listens to changes in tabs, and takes * care of switch to the correct paged in the ViewPager whenever the selected * tab changes. * * Changed to support more Layers of fragments on each Tab. * by sebnapi (2012) * */ public class TabsAdapter extends FragmentPagerAdapter implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener { private final Context mContext; private final TabHost mTabHost; private final ViewPager mViewPager; private ArrayList<String> mTabTags = new ArrayList<String>(); private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>(); static final class TabInfo { public final String tag; public final Class<?> clss; public Bundle args; TabInfo(String _tag, Class<?> _class, Bundle _args) { tag = _tag; clss = _class; args = _args; } } static class DummyTabFactory implements TabHost.TabContentFactory { private final Context mContext; public DummyTabFactory(Context context) { mContext = context; } @Override public View createTabContent(String tag) { View v = new View(mContext); v.setMinimumWidth(0); v.setMinimumHeight(0); return v; } } public interface SaveStateBundle{ public Bundle onRemoveFragment(Bundle outState); } public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) { super(activity.getSupportFragmentManager()); mContext = activity; mTabHost = tabHost; mViewPager = pager; mTabHost.setOnTabChangedListener(this); mViewPager.setAdapter(this); mViewPager.setOnPageChangeListener(this); } /** * Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using * addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args) * The Stack will hold always the default Fragment u add here. * * DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected * beahvior. * * @param tabSpec * @param clss * @param args */ public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){ Stack<TabInfo> tabStack = new Stack<TabInfo>(); tabSpec.setContent(new DummyTabFactory(mContext)); mTabHost.addTab(tabSpec); String tag = tabSpec.getTag(); TabInfo info = new TabInfo(tag, clss, args); mTabTags.add(tag); // to know the position of the tab tag tabStack.add(info); mTabStackMap.put(tag, tabStack); notifyDataSetChanged(); } /** * Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment * it will be instantiated by this object. Proivde _args for your Fragment. * * @param fm * @param _tag * @param _class * @param _args */ public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){ TabInfo info = new TabInfo(_tag, _class, _args); Stack<TabInfo> tabStack = mTabStackMap.get(_tag); Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag)); if(frag instanceof SaveStateBundle){ Bundle b = new Bundle(); ((SaveStateBundle) frag).onRemoveFragment(b); tabStack.peek().args = b; } tabStack.add(info); FragmentTransaction ft = fm.beginTransaction(); ft.remove(frag).commit(); notifyDataSetChanged(); } /** * Will pop the Fragment added to the Tab with the Tag _tag * * @param fm * @param _tag * @return */ public boolean popFragment(FragmentManager fm, String _tag){ Stack<TabInfo> tabStack = mTabStackMap.get(_tag); if(tabStack.size()>1){ tabStack.pop(); Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag)); FragmentTransaction ft = fm.beginTransaction(); ft.remove(frag).commit(); notifyDataSetChanged(); return true; } return false; } public boolean back(FragmentManager fm) { int position = mViewPager.getCurrentItem(); return popFragment(fm, mTabTags.get(position)); } @Override public int getCount() { return mTabStackMap.size(); } @Override public int getItemPosition(Object object) { ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>(); for(Stack<TabInfo> tabStack: mTabStackMap.values()){ positionNoneHack.add(tabStack.peek().clss); } // if the object class lies on top of our stacks, we return default if(positionNoneHack.contains(object.getClass())){ return POSITION_UNCHANGED; } return POSITION_NONE; } @Override public Fragment getItem(int position) { Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position)); TabInfo info = tabStack.peek(); return Fragment.instantiate(mContext, info.clss.getName(), info.args); } @Override public void onTabChanged(String tabId) { int position = mTabHost.getCurrentTab(); mViewPager.setCurrentItem(position); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { // Unfortunately when TabHost changes the current tab, it kindly // also takes care of putting focus on it when not in touch mode. // The jerk. // This hack tries to prevent this from pulling focus out of our // ViewPager. TabWidget widget = mTabHost.getTabWidget(); int oldFocusability = widget.getDescendantFocusability(); widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); mTabHost.setCurrentTab(position); widget.setDescendantFocusability(oldFocusability); } @Override public void onPageScrollStateChanged(int state) { } }
Add this for back button functionality in your MainActivity:
@Override public void onBackPressed() { if (!mTabsAdapter.back(getSupportFragmentManager())) { super.onBackPressed(); } }
If u like to save the Fragment State when it get's removed. Let your Fragment implement the interface SaveStateBundle
return in the function a bundle with your save state. Get the bundle after instantiation by this.getArguments()
.
You can instantiate a tab like this:
mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"), FirstFragmentActivity.FirstFragmentFragment.class, null);
works similiar if u want to add a Fragment on top of a Tab Stack. Important : I think, it won't work if u want to have 2 instances of same class on top of two Tabs. I did this solution quick together, so I can only share it without providing any experience with it.
Replacing fragments in a viewpager is quite involved but is very possible and can look super slick. First, you need to let the viewpager itself handle the removing and adding of the fragments. What is happening is when you replace the fragment inside of SearchFragment, your viewpager retains its fragment views. So you end up with a blank page because the SearchFragment gets removed when you try to replace it.
The solution is to create a listener inside of your viewpager that will handle changes made outside of it so first add this code to the bottom of your adapter.
public interface nextFragmentListener { public void fragment0Changed(String newFragmentIdentification); }
Then you need to create a private class in your viewpager that becomes a listener for when you want to change your fragment. For example you could add something like this. Notice that it implements the interface that was just created. So whenever you call this method, it will run the code inside of the class below.
private final class fragmentChangeListener implements nextFragmentListener { @Override public void fragment0Changed(String fragment) { //I will explain the purpose of fragment0 in a moment fragment0 = fragment; manager.beginTransaction().remove(fragAt0).commit(); switch (fragment){ case "searchFragment": fragAt0 = SearchFragment.newInstance(listener); break; case "searchResultFragment": fragAt0 = Fragment_Table.newInstance(listener); break; } notifyDataSetChanged(); }
There are two main things to point out here:
- fragAt0 is a "flexible" fragment. It can take on whatever fragment type you give it. This allows it to become your best friend in changing the fragment at position 0 to the fragment you desire.
-
Notice the listeners that are placed in the 'newInstance(listener)constructor. These are how you will callfragment0Changed(String newFragmentIdentification)`. The following code shows how you create the listener inside of your fragment.
static nextFragmentListener listenerSearch;
public static Fragment_Journals newInstance(nextFragmentListener listener){ listenerSearch = listener; return new Fragment_Journals(); }
You could call the change inside of your onPostExecute
private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{ protected Void doInBackground(Void... params){ . .//some more operation . } protected void onPostExecute(Void param){ listenerSearch.fragment0Changed("searchResultFragment"); } }
This would trigger the code inside of your viewpager to switch your fragment at position zero fragAt0 to become a new searchResultFragment. There are two more small pieces you would need to add to the viewpager before it became functional.
One would be in the getItem override method of the viewpager.
@Override public Fragment getItem(int index) { switch (index) { case 0: //this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected. if(fragAt0 == null){ switch(fragment0){ case "searchFragment": fragAt0 = FragmentSearch.newInstance(listener); break; case "searchResultsFragment": fragAt0 = FragmentSearchResults.newInstance(listener); break; } } return fragAt0; case 1: // Games fragment activity return new CreateFragment(); }
Now without this final piece you would still get a blank page. Kind of lame, but it is an essential part of the viewPager. You must override the getItemPosition method of the viewpager. Ordinarily this method will return POSITION_UNCHANGED which tells the viewpager to keep everything the same and so getItem will never get called to place the new fragment on the page. Here's an example of something you could do
public int getItemPosition(Object object) { //object is the current fragment displayed at position 0. if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){ return POSITION_NONE; //this condition is for when you press back }else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){ return POSITION_NONE; } return POSITION_UNCHANGED }
Like I said, the code gets very involved, but you basically have to create a custom adapter for your situation. The things I mentioned will make it possible to change the fragment. It will likely take a long time to soak everything in so I would be patient, but it will all make sense. It is totally worth taking the time because it can make a really slick looking application.
Here's the nugget for handling the back button. You put this inside your MainActivity
public void onBackPressed() { if(mViewPager.getCurrentItem() == 0) { if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){ ((Fragment_Table) pagerAdapter.getItem(0)).backPressed(); }else if (pagerAdapter.getItem(0) instanceof FragmentSearch) { finish(); } }
You will need to create a method called backPressed() inside of FragmentSearchResults that calls fragment0changed. This in tandem with the code I showed before will handle pressing the back button. Good luck with your code to change the viewpager. It takes a lot of work, and as far as I have found, there aren't any quick adaptations. Like I said, you are basically creating a custom viewpager adapter, and letting it handle all of the necessary changes using listeners
In your onCreateView
method, container
is actually a ViewPager
instance.
So, just calling
ViewPager vpViewPager = (ViewPager) container; vpViewPager.setCurrentItem(1);
will change current fragment in your ViewPager
.
Here's my relatively simple solution to this problem. The keys to this solution are to use FragmentStatePagerAdapter
instead of FragmentPagerAdapter
as the former will remove unused fragments for you while the later still retains their instances. The second is the use of POSITION_NONE
in getItem(). I've used a simple List to keep track of my fragments. My requirement was to replace the entire list of fragments at once with a new list, but the below could be easily modified to replace individual fragments:
public class MyFragmentAdapter extends FragmentStatePagerAdapter { private List<Fragment> fragmentList = new ArrayList<Fragment>(); private List<String> tabTitleList = new ArrayList<String>(); public MyFragmentAdapter(FragmentManager fm) { super(fm); } public void addFragments(List<Fragment> fragments, List<String> titles) { fragmentList.clear(); tabTitleList.clear(); fragmentList.addAll(fragments); tabTitleList.addAll(titles); notifyDataSetChanged(); } @Override public int getItemPosition(Object object) { if (fragmentList.contains(object)) { return POSITION_UNCHANGED; } return POSITION_NONE; } @Override public Fragment getItem(int item) { if (item >= fragmentList.size()) { return null; } return fragmentList.get(item); } @Override public int getCount() { return fragmentList.size(); } @Override public CharSequence getPageTitle(int position) { return tabTitleList.get(position); } }
I doing something to similar to wize but in my answer yo can change between the two fragments whenever you want. And with the wize answer I have some problems when changing the orientation of the screen an things like that. This is the PagerAdapter looks like:
public class MyAdapter extends FragmentPagerAdapter { static final int NUM_ITEMS = 2; private final FragmentManager mFragmentManager; private Fragment mFragmentAtPos0; private Map<Integer, String> mFragmentTags; private boolean isNextFragment=false; public MyAdapter(FragmentManager fm) { super(fm); mFragmentManager = fm; mFragmentTags = new HashMap<Integer, String>(); } @Override public Fragment getItem(int position) { if (position == 0) { if (isPager) { mFragmentAtPos0 = new FirstPageFragment(); } else { mFragmentAtPos0 = new NextFragment(); } return mFragmentAtPos0; } else return SecondPageFragment.newInstance(); } @Override public int getCount() { return NUM_ITEMS; } @Override public Object instantiateItem(ViewGroup container, int position) { Object obj = super.instantiateItem(container, position); if (obj instanceof Fragment) { // record the fragment tag here. Fragment f = (Fragment) obj; String tag = f.getTag(); mFragmentTags.put(position, tag); } return obj; } public void onChange(boolean isNextFragment) { if (mFragmentAtPos0 == null) mFragmentAtPos0 = getFragment(0); if (mFragmentAtPos0 != null) mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit(); if (!isNextFragment) { mFragmentAtFlashcards = new FirstPageFragment(); } else { mFragmentAtFlashcards = new NextFragment(); } notifyDataSetChanged(); } @Override public int getItemPosition(Object object) { if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment) return POSITION_NONE; if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment) return POSITION_NONE; return POSITION_UNCHANGED; } public Fragment getFragment(int position) { String tag = mFragmentTags.get(position); if (tag == null) return null; return mFragmentManager.findFragmentByTag(tag); } }
The listener I implemented in the adapter container activity to put it to the fragment when attaching it, this is the activity:
public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener { //... @Override public void onChange(boolean isNextFragment) { if (pagerAdapter != null) pagerAdapter.onChange(isNextFragment); } //... }
Then in the fragment putting the listener when attach an calling it:
public class FirstPageFragment extends Fragment{ private ChangeFragmentListener changeFragmentListener; //... @Override public void onAttach(Activity activity) { super.onAttach(activity); changeFragmentListener = ((PagerContainerActivity) activity); } @Override public void onDetach() { super.onDetach(); changeFragmentListener = null; } //... //in the on click to change the fragment changeFragmentListener.onChange(true); //... }
And finally the listener:
public interface changeFragmentListener { void onChange(boolean isNextFragment); }
I followed the answers by @wize and @mdelolmo and I got the solution. Thanks Tons. But, I tuned these solutions a little bit to improve the memory consumption.
Problems I observed:
They save the instance of Fragment
which is replaced. In my case, it is a Fragment which holds MapView
and I thought its costly. So, I am maintaining the FragmentPagerPositionChanged (POSITION_NONE or POSITION_UNCHANGED)
instead of Fragment
itself.
Here is my implementation.
public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter { private SwitchFragListener mSwitchFragListener; private Switch mToggle; private int pagerAdapterPosChanged = POSITION_UNCHANGED; private static final int TOGGLE_ENABLE_POS = 2; public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) { super(fm); mToggle = toggle; mSwitchFragListener = new SwitchFragListener(); mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mSwitchFragListener.onSwitchToNextFragment(); } }); } @Override public Fragment getItem(int i) { switch (i) { case TOGGLE_ENABLE_POS: if(mToggle.isChecked()) { return TabReplaceFragment.getInstance(); }else { return DemoTab2Fragment.getInstance(i); } default: return DemoTabFragment.getInstance(i); } } @Override public int getCount() { return 5; } @Override public CharSequence getPageTitle(int position) { return "Tab " + (position + 1); } @Override public int getItemPosition(Object object) { // This check make sures getItem() is called only for the required Fragment if (object instanceof TabReplaceFragment || object instanceof DemoTab2Fragment) return pagerAdapterPosChanged; return POSITION_UNCHANGED; } /** * Switch fragments Interface implementation */ private final class SwitchFragListener implements SwitchFragInterface { SwitchFragListener() {} public void onSwitchToNextFragment() { pagerAdapterPosChanged = POSITION_NONE; notifyDataSetChanged(); } } /** * Interface to switch frags */ private interface SwitchFragInterface{ void onSwitchToNextFragment(); } }
Demo link here.. https://youtu.be/l_62uhKkLyM
For demo purpose, used 2 fragments TabReplaceFragment
and DemoTab2Fragment
at position two. In all the other cases I'm using DemoTabFragment
instances.
说明:
I'm passing Switch
from Activity to the DemoCollectionPagerAdapter
. Based on the state of this switch we will display correct fragment. When the switch check is changed, I'm calling the SwitchFragListener
's onSwitchToNextFragment
method, where I'm changing the value of pagerAdapterPosChanged
variable to POSITION_NONE
. Check out more about POSITION_NONE . This will invalidate the getItem and I have logics to instantiate the right fragment over there. Sorry, if the explanation is a bit messy.
Once again big thanks to @wize and @mdelolmo for the original idea.
希望这是有帮助的。 🙂
Let me know if this implementation has any flaws. That will be greatly helpful for my project.
I found simple solution, which works fine even if you want add new fragments in the middle or replace current fragment. In my solution you should override getItemId()
which should return unique id for each fragment. Not position as by default.
There is it:
public class DynamicPagerAdapter extends FragmentPagerAdapter { private ArrayList<Page> mPages = new ArrayList<Page>(); private ArrayList<Fragment> mFragments = new ArrayList<Fragment>(); public DynamicPagerAdapter(FragmentManager fm) { super(fm); } public void replacePage(int position, Page page) { mPages.set(position, page); notifyDataSetChanged(); } public void setPages(ArrayList<Page> pages) { mPages = pages; notifyDataSetChanged(); } @Override public Fragment getItem(int position) { if (mPages.get(position).mPageType == PageType.FIRST) { return FirstFragment.newInstance(mPages.get(position)); } else { return SecondFragment.newInstance(mPages.get(position)); } } @Override public int getCount() { return mPages.size(); } @Override public long getItemId(int position) { // return unique id return mPages.get(position).getId(); } @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = (Fragment) super.instantiateItem(container, position); while (mFragments.size() <= position) { mFragments.add(null); } mFragments.set(position, fragment); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container, position, object); mFragments.set(position, null); } @Override public int getItemPosition(Object object) { PagerFragment pagerFragment = (PagerFragment) object; Page page = pagerFragment.getPage(); int position = mFragments.indexOf(pagerFragment); if (page.equals(mPages.get(position))) { return POSITION_UNCHANGED; } else { return POSITION_NONE; } } }
Notice: In this example FirstFragment
and SecondFragment
extends abstract class PageFragment, which has method getPage()
.
after research i found solution with short code. first of all create a public instance on fragment and just remove your fragment on onSaveInstanceState if fragment not recreating on orientation change.
@Override public void onSaveInstanceState(Bundle outState) { if (null != mCalFragment) { FragmentTransaction bt = getChildFragmentManager().beginTransaction(); bt.remove(mFragment); bt.commit(); } super.onSaveInstanceState(outState); }