How to properly handle screen rotation with a ViewPager and nested fragments?

前端 未结 6 678
执念已碎
执念已碎 2021-01-31 03:42

I\'ve got this activity, which holds a fragment. This fragment layout consists of a view pager with several fragments (two, actually).

When the view pager is created, it

相关标签:
6条回答
  • 2021-01-31 03:58

    My answer is kind of similar to Joshua Hunt's one, but by commiting the transaction in finishUpdate method you get much better performance. One transaction instead of two per update. Here is the code:

    private class SuchPagerAdapter extends PagerAdapter{
    
        private final FragmentManager mFragmentManager;
        private SparseArray<Fragment> mFragments;
        private FragmentTransaction mCurTransaction;
    
        private SuchPagerAdapter(FragmentManager fragmentManager) {
            mFragmentManager = fragmentManager;
            mFragments = new SparseArray<>();
        }
    
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment fragment = getItem(position);
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.add(container.getId(),fragment,"fragment:"+position);
            return fragment;
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.detach(mFragments.get(position));
            mFragments.remove(position);
        }
    
        @Override
        public boolean isViewFromObject(View view, Object fragment) {
            return ((Fragment) fragment).getView() == view;
        }
    
        public Fragment getItem(int position) {         
            return YoursVeryFragment.instantiate();
        }
    
        @Override
        public void finishUpdate(ViewGroup container) {
            if (mCurTransaction != null) {
                mCurTransaction.commitAllowingStateLoss();
                mCurTransaction = null;
                mFragmentManager.executePendingTransactions();
            }
        }
    
    
        @Override
        public int getCount() {
            return countOfPages;
        }
    
    }
    
    0 讨论(0)
  • 2021-01-31 04:03

    Why solutions above so complex? Looks like overkill. I solve it just with replacing old reference to new in class extended from FragmentPagerAdapter

     @Override
    public Object instantiateItem(ViewGroup container, int position) {
        frags[position] = (Fragment) super.instantiateItem(container, position);
        return frags[position];
    }
    

    All adapter's code looks like this

    public class RelationsFragmentsAdapter extends FragmentPagerAdapter {
    
    private final String titles[] = new String[3];
    private final Fragment frags[] = new Fragment[titles.length];
    
    public RelationsFragmentsAdapter(FragmentManager fm) {
        super(fm);
        frags[0] = new FriendsFragment();
        frags[1] = new FriendsRequestFragment();
        frags[2] = new FriendsDeclinedFragment();
    
        Resources resources = AppController.getAppContext().getResources();
    
        titles[0] = resources.getString(R.string.my_friends);
        titles[1] = resources.getString(R.string.my_new_friends);
        titles[2] = resources.getString(R.string.followers);
    }
    
    @Override
    public CharSequence getPageTitle(int position) {
        return titles[position];
    }
    
    @Override
    public Fragment getItem(int position) {
        return frags[position];
    }
    
    @Override
    public int getCount() {
        return frags.length;
    }
    
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        frags[position] = (Fragment) super.instantiateItem(container, position);
        return frags[position];
    }
    

    }

    0 讨论(0)
  • 2021-01-31 04:06

    I had the same issue - I assume you're subclassing FragmentPagerAdapter for your pager adapter (as getItem() is specific to FragmentPagerAdapter).

    My solution was to instead subclass PagerAdapter and handle the fragment creation/deletion yourself (reimplementing some of the FragmentPagerAdapter code):

    public class ListPagerAdapter extends PagerAdapter {
        FragmentManager fragmentManager;
        Fragment[] fragments;
    
        public ListPagerAdapter(FragmentManager fm){
            fragmentManager = fm;
            fragments = new Fragment[5];
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            assert(0 <= position && position < fragments.length);
            FragmentTransaction trans = fragmentManager.beginTransaction();
            trans.remove(fragments[position]);
            trans.commit();
            fragments[position] = null;
    }
    
        @Override
        public Fragment instantiateItem(ViewGroup container, int position){
            Fragment fragment = getItem(position);
            FragmentTransaction trans = fragmentManager.beginTransaction();
            trans.add(container.getId(),fragment,"fragment:"+position);
            trans.commit();
            return fragment;
        }
    
        @Override
        public int getCount() {
            return fragments.length;
        }
    
        @Override
        public boolean isViewFromObject(View view, Object fragment) {
            return ((Fragment) fragment).getView() == view;
        }
    
        public Fragment getItem(int position){
            assert(0 <= position && position < fragments.length);
            if(fragments[position] == null){
                fragments[position] = ; //make your fragment here
            }
            return fragments[position];
        }
    }
    

    Hope this helps.

    0 讨论(0)
  • 2021-01-31 04:15

    With reference to simekadam's solution, mFragments isn't being populated in instantiateItem and needs mFragments.put(position, fragment); inside or you'll end up with this error: Trying to remove fragment from view gives me NullPointerException on mNextAnim.

    0 讨论(0)
  • 2021-01-31 04:20

    My code :

        public class SampleAdapter extends FragmentStatePagerAdapter {
    
        private Fragment mFragmentAtPos2;
        private FragmentManager mFragmentManager;
        private Fragment[] mFragments = new Fragment[3];
    
    
        public SampleAdapter(FragmentManager mgr) {
            super(mgr);
            mFragmentManager = mgr;
            Bundle hack = new Bundle();
            try {
                for (int i = 0; i < mFragments.length; i++) {
                    hack.putInt("hack", i);
                    mFragments[i] = mFragmentManager.getFragment(hack, "hack");
                }
            } catch (Exception e) {
                // No need to fail here, likely because it's the first creation and mActive is empty
            }
    
        }
    
        public void switchFrag(Fragment frag) {
    
            if (frag == null) {
                Dbg.e(TAG, "- switch(frag) frag is NULL");
                return;
            } else Dbg.v(TAG, "- switch(frag) - frag is " + frag.getClass());
            // We have to check for mFragmentAtPos2 null in case of first time (only Mytrips fragment being instatiante).
            if (mFragmentAtPos2!= null) 
                mFragmentManager.beginTransaction()  
                    .remove(mFragmentAtPos2)
                    .commit();
            mFragmentAtPos2 = frag;
            notifyDataSetChanged();
        }
    
        @Override
        public int getCount() {
            return(3);
        }
    
        @Override
        public int getItemPosition(Object object) {
            Dbg.v(TAG,"getItemPosition : "+object.getClass());
            if (object instanceof MyTripsFragment
                    || object instanceof FindingDriverFragment
                    || object instanceof BookingAcceptedFragment
                    || object instanceof RideStartedFragment
                    || object instanceof RideEndedFragment
                    || object instanceof ContactUsFragment
                    )
                return POSITION_NONE;
            else return POSITION_UNCHANGED;
    
        }
    
        @Override
        public Fragment getItem(int position) {
            Dbg.v("SampleAdapter", "getItem called on: "+position);
            switch (position) {
            case 0: 
                if (snapbookFrag==null) {
                    snapbookFrag = new SnapBookingFragment();
                    Dbg.e(TAG, "snapbookFrag created");
                }
                return snapbookFrag;
            case 1: 
                if(bookingFormFrag==null) {
                    bookingFormFrag = new BookingFormFragment();
                    Dbg.e(TAG, "bookingFormFrag created");
                }
                return bookingFormFrag;
            case 2: 
                if (mFragmentAtPos2 == null) {
                    myTripsFrag = new MyTripsFragment();
                    mFragmentAtPos2 = myTripsFrag;
                    return mFragmentAtPos2;
                }
                return mFragmentAtPos2;
            default:
                return(new SnapBookingFragment());
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-31 04:24

    The problem is that getItem() in FragmentPageAdapter has a wrong name. It should have been named createItem(). Because the way it works the getItem() is for create fragments and it's not safe to call it to query/find a fragment.

    My recomendation is to make a copy of current FragmentPagerAdapter and change it that way:

    Add:

        public abstract Fragment createFragment(int position);
    

    And change getItem to:

    public Fragment getItem(int position) {
        if(containerId!=null) {
            final long itemId = getItemId(position);
            String name = makeFragmentName(containerId, itemId);
            return mFragmentManager.findFragmentByTag(name);
        } else {
            return null;
        }
    }
    

    Finally add that to instantiateItem:

        if(containerId==null)
            containerId = container.getId();
        else if(containerId!=container.getId())
            throw new RuntimeException("Container id not expected to change");
    

    Complete code at this gist

    I think that this implementation is safer and easier to use and also has same performance from the original adapter from Google engineers.

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