FragmentPagerAdapter is not removing items (Fragments) correctly

前端 未结 9 1198
别跟我提以往
别跟我提以往 2021-01-30 01:30

I have implemented the FragmentPagerAdapter and and using a List to hold all fragments for my ViewPager to display. On

相关标签:
9条回答
  • 2021-01-30 02:24

    Updated this post and included my solution (if someone can improve let me know)

    Ok i've now solved my problem in a hackish way, but yeah it's working ;). If someone can improve my solution please let me know. For my new solution i now use a CustomFragmentStatePagerAdapter but it doesn't save the state like it should and stores all the Fragments in a list. This can cause a memory problem if the user has more than 50 fragments, like the normal FragmentPagerAdapter does. It would be great if someone can add the State-thing back to my solution without removing my fixes. Thanks.

    So here's my CustomFragmentStatePagerAdapter.java

    package com.tundem.webLab.Adapter;
    
    import java.util.ArrayList;
    
    import android.os.Bundle;
    import android.os.Parcelable;
    import android.support.v4.app.Fragment;
    import android.support.v4.app.FragmentManager;
    import android.support.v4.app.FragmentTransaction;
    import android.support.v4.view.PagerAdapter;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    
    public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter {
        private static final String TAG = "FragmentStatePagerAdapter";
        private static final boolean DEBUG = false;
    
        private final FragmentManager mFragmentManager;
        private FragmentTransaction mCurTransaction = null;
    
        public ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
        public ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
        private Fragment mCurrentPrimaryItem = null;
    
        public CustomFragmentStatePagerAdapter(FragmentManager fm) {
            mFragmentManager = fm;
        }
    
        /**
         * Return the Fragment associated with a specified position.
         */
        public abstract Fragment getItem(int position);
    
        @Override
        public void startUpdate(ViewGroup container) {}
    
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            // If we already have this item instantiated, there is nothing
            // to do. This can happen when we are restoring the entire pager
            // from its saved state, where the fragment manager has already
            // taken care of restoring the fragments we previously had instantiated.
    
            // DONE Remove of the add process of the old stuff
            /* if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } */
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
    
            Fragment fragment = getItem(position);
            if (DEBUG)
                Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
            if (mSavedState.size() > position) {
                Fragment.SavedState fss = mSavedState.get(position);
                if (fss != null) {
                    try // DONE: Try Catch
                    {
                        fragment.setInitialSavedState(fss);
                    } catch (Exception ex) {
                        // Schon aktiv (kA was das heißt xD)
                    }
                }
            }
            while (mFragments.size() <= position) {
                mFragments.add(null);
            }
            fragment.setMenuVisibility(false);
            mFragments.set(position, fragment);
            mCurTransaction.add(container.getId(), fragment);
    
            return fragment;
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            Fragment fragment = (Fragment) object;
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.remove(fragment);
    
            /*if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)
             * object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
             * mFragments.set(position, null); mCurTransaction.remove(fragment); */
        }
    
        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            Fragment fragment = (Fragment) object;
            if (fragment != mCurrentPrimaryItem) {
                if (mCurrentPrimaryItem != null) {
                    mCurrentPrimaryItem.setMenuVisibility(false);
                }
                if (fragment != null) {
                    fragment.setMenuVisibility(true);
                }
                mCurrentPrimaryItem = fragment;
            }
        }
    
        @Override
        public void finishUpdate(ViewGroup container) {
            if (mCurTransaction != null) {
                mCurTransaction.commitAllowingStateLoss();
                mCurTransaction = null;
                mFragmentManager.executePendingTransactions();
            }
        }
    
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return ((Fragment) object).getView() == view;
        }
    
        @Override
        public Parcelable saveState() {
            Bundle state = null;
            if (mSavedState.size() > 0) {
                state = new Bundle();
                Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
                mSavedState.toArray(fss);
                state.putParcelableArray("states", fss);
            }
            for (int i = 0; i < mFragments.size(); i++) {
                Fragment f = mFragments.get(i);
                if (f != null) {
                    if (state == null) {
                        state = new Bundle();
                    }
                    String key = "f" + i;
                    mFragmentManager.putFragment(state, key, f);
                }
            }
            return state;
        }
    
        @Override
        public void restoreState(Parcelable state, ClassLoader loader) {
            if (state != null) {
                Bundle bundle = (Bundle) state;
                bundle.setClassLoader(loader);
                Parcelable[] fss = bundle.getParcelableArray("states");
                mSavedState.clear();
                mFragments.clear();
                if (fss != null) {
                    for (int i = 0; i < fss.length; i++) {
                        mSavedState.add((Fragment.SavedState) fss[i]);
                    }
                }
                Iterable<String> keys = bundle.keySet();
                for (String key : keys) {
                    if (key.startsWith("f")) {
                        int index = Integer.parseInt(key.substring(1));
                        Fragment f = mFragmentManager.getFragment(bundle, key);
                        if (f != null) {
                            while (mFragments.size() <= index) {
                                mFragments.add(null);
                            }
                            f.setMenuVisibility(false);
                            mFragments.set(index, f);
                        } else {
                            Log.w(TAG, "Bad fragment at key " + key);
                        }
                    }
                }
            }
        }
    }
    

    Here's my normal FragmentAdapter.java

    package com.tundem.webLab.Adapter;
    
    import java.util.LinkedList;
    import java.util.List;
    
    import android.support.v4.app.FragmentManager;
    
    import com.tundem.webLab.fragments.BaseFragment;
    import com.viewpagerindicator.TitleProvider;
    
    public class FragmentAdapter extends CustomFragmentStatePagerAdapter implements TitleProvider {
        public List<BaseFragment> fragments = new LinkedList<BaseFragment>();
    
        private int actPage;
    
        public FragmentAdapter(FragmentManager fm) {
            super(fm);
        }
    
        public void setActPage(int actPage) {
            this.actPage = actPage;
        }
    
        public void addItem(BaseFragment fragment) {
            // TODO if exists don't open / change to that tab
            fragments.add(fragment);
        }
    
        public BaseFragment getActFragment() {
            return getItem(getActPage());
        }
    
        public int getActPage() {
            return actPage;
        }
    
        @Override
        public BaseFragment getItem(int position) {
            if (position < getCount()) {
                return fragments.get(position);
            } else
                return null;
        }
    
        @Override
        public int getCount() {
            return fragments.size();
        }
    
        @Override
        public String getTitle(int position) {
            return fragments.get(position).getTitle();
        }
    
        @Override
        public int getItemPosition(Object object) {
            return POSITION_NONE;
        }
    }
    

    And this is the way i delete a Fragment. (I know it's a bit more than only .remove() ). Be free to improve my solution, you can also add this code somewhere in the adapter so yeah. It's up to the user who tries to implement this. I use this in my TabHelper.java (A class which handles all tab operations like delete, add, ...)

        int act = Cfg.mPager.getCurrentItem();
        Cfg.mPager.removeAllViews();
        Cfg.mAdapter.mFragments.remove(act);
        try {
            Cfg.mAdapter.mSavedState.remove(act);
        } catch (Exception ex) {/* Already removed */}
        try {
            Cfg.mAdapter.fragments.remove(act);
        } catch (Exception ex) {/* Already removed */}
    
        Cfg.mAdapter.notifyDataSetChanged();
        Cfg.mIndicator.notifyDataSetChanged();
    

    Description of the Cfg. thing. I save the reference to those objects in a cfg, class so i can always use them without the need of a special Factory.java ...

    Yeah. i hope i was able to help. Feel free to improve this, but let me know so i can improve my code too.

    Thanks.

    If i missed any code let me know.


    My old answer also works but only if you have different Fragments. FileFragment, WebFragment, ... Not if you use one of those fragmenttypes twice.

    I got it pseudo working for now. It's a really dirty solution and i'm still searching for a better one. Please help.

    I changed the code, where i delete a tab to this one:

       public static void deleteActTab()
            {   
                //We set this on the indicator, NOT the pager
                int act = Cfg.mPager.getCurrentItem();
                Cfg.mAdapter.removeItem(act);
                List<BaseFragment> frags = new LinkedList<BaseFragment>();
                frags = Cfg.mAdapter.fragments;
    
                Cfg.mPager = (ViewPager)Cfg.act.findViewById(R.id.pager);
                Cfg.mPager.setAdapter(Cfg.mAdapter);
                Cfg.mIndicator.setViewPager(Cfg.mPager);
    
                Cfg.mAdapter.fragments = frags;
    
                if(act > 0)
                {
                    Cfg.mPager.setCurrentItem(act-1);
                    Cfg.mIndicator.setCurrentItem(act-1);
                }
    
                Cfg.mIndicator.notifyDataSetChanged();
            }
    

    If someone can improve this code let me know. If someone can tell us the real answer for that problem. please add it here. There are many many people who experience this issue. I added a reputation of 50 for the one who solve it. I can also give a donation for the one who solve it.

    Thanks

    0 讨论(0)
  • 2021-01-30 02:26

    Maybe this answer helps you.

    Use FragmentStatePagerAdapter instead of FragmentPagerAdapter.

    Because FragmentPagerAdapter does not destory the views. For more information read this answer.

    0 讨论(0)
  • 2021-01-30 02:28

    I have been having this same issue until I dawned on me, I was creating my PagerView from within another Fragment and not the main activity.

    My solution was to pass the ChildFragment Manager into the constructor of the Fragment(State)PagerAdapter and not the Fragment Manager of the parent Fragment.

    Using the ChildFragmentManager all the Fragments created by the ViewPagerAdapter are cleaned up automatically when the parent Fragment is destroyed.

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