Replace Fragment inside a ViewPager

后端 未结 18 1331
别那么骄傲
别那么骄傲 2020-11-22 00:26

I\'m trying to use Fragment with a ViewPager using the FragmentPagerAdapter. What I\'m looking for to achieve is to replace a fragment, positioned

相关标签:
18条回答
  • 2020-11-22 01:01

    As of November 13th 2012, repacing fragments in a ViewPager seems to have become a lot easier. Google released Android 4.2 with support for nested fragments, and it's also supported in the new Android Support Library v11 so this will work all the way back to 1.6

    It's very similiar to the normal way of replacing a fragment except you use getChildFragmentManager. It seems to work except the nested fragment backstack isn't popped when the user clicks the back button. As per the solution in that linked question, you need to manually call the popBackStackImmediate() on the child manager of the fragment. So you need to override onBackPressed() of the ViewPager activity where you'll get the current fragment of the ViewPager and call getChildFragmentManager().popBackStackImmediate() on it.

    Getting the Fragment currently being displayed is a bit hacky as well, I used this dirty "android:switcher:VIEWPAGER_ID:INDEX" solution but you can also keep track of all fragments of the ViewPager yourself as explained in the second solution on this page.

    So here's my code for a ViewPager with 4 ListViews with a detail view shown in the ViewPager when the user clicks a row, and with the back button working. I tried to include just the relevant code for the sake of brevity so leave a comment if you want the full app uploaded to 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;
    }
    }
    
    0 讨论(0)
  • 2020-11-22 01:01

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

    Hope this helps someone.

    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;
        }
    
    0 讨论(0)
  • 2020-11-22 01:06

    There is another solution that does not need modifying source code of ViewPager and FragmentStatePagerAdapter, and it works with the FragmentPagerAdapter base class used by the author.

    I'd like to start by answering the author's question about which ID he should use; it is ID of the container, i.e. ID of the view pager itself. However, as you probably noticed yourself, using that ID in your code causes nothing to happen. I will explain why:

    First of all, to make ViewPager repopulate the pages, you need to call notifyDataSetChanged() that resides in the base class of your adapter.

    Second, ViewPager uses the getItemPosition() abstract method to check which pages should be destroyed and which should be kept. The default implementation of this function always returns POSITION_UNCHANGED, which causes ViewPager to keep all current pages, and consequently not attaching your new page. Thus, to make fragment replacement work, getItemPosition() needs to be overridden in your adapter and must return POSITION_NONE when called with an old, to be hidden, fragment as argument.

    This also means that your adapter always needs to be aware of which fragment that should be displayed in position 0, FirstPageFragment or NextFragment. One way of doing this is supplying a listener when creating FirstPageFragment, which will be called when it is time to switch fragments. I think this is a good thing though, to let your fragment adapter handle all fragment switches and calls to ViewPager and FragmentManager.

    Third, FragmentPagerAdapter caches the used fragments by a name which is derived from the position, so if there was a fragment at position 0, it will not be replaced even though the class is new. There are two solutions, but the simplest is to use the remove() function of FragmentTransaction, which will remove its tag as well.

    That was a lot of text, here is code that should work in your case:

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

    Hope this helps anyone!

    0 讨论(0)
  • 2020-11-22 01:06

    I have created a ViewPager with 3 elements and 2 sub elements for index 2 and 3 and here what I wanted to do..

    enter image description here

    I have implemented this with the help from previous questions and answers from StackOverFlow and here is the link.

    ViewPagerChildFragments

    0 讨论(0)
  • 2020-11-22 01:07

    I have implemented a solution for:

    • Dynamic fragment replacement inside the tab
    • Maintenance of the history per tab
    • Working with orientation changes

    The tricks to achieve this are the following:

    • Use the notifyDataSetChanged() method to apply the fragment replacement
    • Use the fragment manager only for back stage and no for fragament replacement
    • Maintain the history using the memento pattern (stack)

    The adapter code is the following:

    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 +
                    '}';
        }
    }
    

    The very first time you add all tabs, we need to call the method createHistory(), to create the initial history

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

    Every time you want to replace a fragment to a specific tab you call: 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();
    

    On back pressed you need to call the back() method:

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

    The solution works with sherlock action bar and with swipe gesture.

    0 讨论(0)
  • 2020-11-22 01:09

    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.

    0 讨论(0)
提交回复
热议问题