ViewPager and fragments — what's the right way to store fragment's state?

后端 未结 11 1345
遇见更好的自我
遇见更好的自我 2020-11-22 02:21

Fragments seem to be very nice for separation of UI logic into some modules. But along with ViewPager its lifecycle is still misty to me. So Guru thoughts are b

相关标签:
11条回答
  • 2020-11-22 02:41

    What is that BasePagerAdapter? You should use one of the standard pager adapters -- either FragmentPagerAdapter or FragmentStatePagerAdapter, depending on whether you want Fragments that are no longer needed by the ViewPager to either be kept around (the former) or have their state saved (the latter) and re-created if needed again.

    Sample code for using ViewPager can be found here

    It is true that the management of fragments in a view pager across activity instances is a little complicated, because the FragmentManager in the framework takes care of saving the state and restoring any active fragments that the pager has made. All this really means is that the adapter when initializing needs to make sure it re-connects with whatever restored fragments there are. You can look at the code for FragmentPagerAdapter or FragmentStatePagerAdapter to see how this is done.

    0 讨论(0)
  • 2020-11-22 02:43

    I came up with this simple and elegant solution. It assumes that the activity is responsible for creating the Fragments, and the Adapter just serves them.

    This is the adapter's code (nothing weird here, except for the fact that mFragments is a list of fragments maintained by the Activity)

    class MyFragmentPagerAdapter extends FragmentStatePagerAdapter {
    
        public MyFragmentPagerAdapter(FragmentManager fm) {
            super(fm);
        }
    
        @Override
        public Fragment getItem(int position) {
            return mFragments.get(position);
        }
    
        @Override
        public int getCount() {
            return mFragments.size();
        }
    
        @Override
        public int getItemPosition(Object object) {
            return POSITION_NONE;
        }
    
        @Override
        public CharSequence getPageTitle(int position) {
            TabFragment fragment = (TabFragment)mFragments.get(position);
            return fragment.getTitle();
        }
    } 
    

    The whole problem of this thread is getting a reference of the "old" fragments, so I use this code in the Activity's onCreate.

        if (savedInstanceState!=null) {
            if (getSupportFragmentManager().getFragments()!=null) {
                for (Fragment fragment : getSupportFragmentManager().getFragments()) {
                    mFragments.add(fragment);
                }
            }
        }
    

    Of course you can further fine tune this code if needed, for example making sure the fragments are instances of a particular class.

    0 讨论(0)
  • 2020-11-22 02:44

    To get the fragments after orientation change you have to use the .getTag().

        getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewPagerId + ":" + positionOfItemInViewPager)
    

    For a bit more handling i wrote my own ArrayList for my PageAdapter to get the fragment by viewPagerId and the FragmentClass at any Position:

    public class MyPageAdapter extends FragmentPagerAdapter implements Serializable {
    private final String logTAG = MyPageAdapter.class.getName() + ".";
    
    private ArrayList<MyPageBuilder> fragmentPages;
    
    public MyPageAdapter(FragmentManager fm, ArrayList<MyPageBuilder> fragments) {
        super(fm);
        fragmentPages = fragments;
    }
    
    @Override
    public Fragment getItem(int position) {
        return this.fragmentPages.get(position).getFragment();
    }
    
    @Override
    public CharSequence getPageTitle(int position) {
        return this.fragmentPages.get(position).getPageTitle();
    }
    
    @Override
    public int getCount() {
        return this.fragmentPages.size();
    }
    
    
    public int getItemPosition(Object object) {
        //benötigt, damit bei notifyDataSetChanged alle Fragemnts refrehsed werden
    
        Log.d(logTAG, object.getClass().getName());
        return POSITION_NONE;
    }
    
    public Fragment getFragment(int position) {
        return getItem(position);
    }
    
    public String getTag(int position, int viewPagerId) {
        //getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.shares_detail_activity_viewpager + ":" + myViewPager.getCurrentItem())
    
        return "android:switcher:" + viewPagerId + ":" + position;
    }
    
    public MyPageBuilder getPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
        return new MyPageBuilder(pageTitle, icon, selectedIcon, frag);
    }
    
    
    public static class MyPageBuilder {
    
        private Fragment fragment;
    
        public Fragment getFragment() {
            return fragment;
        }
    
        public void setFragment(Fragment fragment) {
            this.fragment = fragment;
        }
    
        private String pageTitle;
    
        public String getPageTitle() {
            return pageTitle;
        }
    
        public void setPageTitle(String pageTitle) {
            this.pageTitle = pageTitle;
        }
    
        private int icon;
    
        public int getIconUnselected() {
            return icon;
        }
    
        public void setIconUnselected(int iconUnselected) {
            this.icon = iconUnselected;
        }
    
        private int iconSelected;
    
        public int getIconSelected() {
            return iconSelected;
        }
    
        public void setIconSelected(int iconSelected) {
            this.iconSelected = iconSelected;
        }
    
        public MyPageBuilder(String pageTitle, int icon, int selectedIcon, Fragment frag) {
            this.pageTitle = pageTitle;
            this.icon = icon;
            this.iconSelected = selectedIcon;
            this.fragment = frag;
        }
    }
    
    public static class MyPageArrayList extends ArrayList<MyPageBuilder> {
        private final String logTAG = MyPageArrayList.class.getName() + ".";
    
        public MyPageBuilder get(Class cls) {
            // Fragment über FragmentClass holen
            for (MyPageBuilder item : this) {
                if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                    return super.get(indexOf(item));
                }
            }
            return null;
        }
    
        public String getTag(int viewPagerId, Class cls) {
            // Tag des Fragment unabhängig vom State z.B. nach bei Orientation change
            for (MyPageBuilder item : this) {
                if (item.fragment.getClass().getName().equalsIgnoreCase(cls.getName())) {
                    return "android:switcher:" + viewPagerId + ":" + indexOf(item);
                }
            }
            return null;
        }
    }
    

    So just create a MyPageArrayList with the fragments:

        myFragPages = new MyPageAdapter.MyPageArrayList();
    
        myFragPages.add(new MyPageAdapter.MyPageBuilder(
                getString(R.string.widget_config_data_frag),
                R.drawable.ic_sd_storage_24dp,
                R.drawable.ic_sd_storage_selected_24dp,
                new WidgetDataFrag()));
    
        myFragPages.add(new MyPageAdapter.MyPageBuilder(
                getString(R.string.widget_config_color_frag),
                R.drawable.ic_color_24dp,
                R.drawable.ic_color_selected_24dp,
                new WidgetColorFrag()));
    
        myFragPages.add(new MyPageAdapter.MyPageBuilder(
                getString(R.string.widget_config_textsize_frag),
                R.drawable.ic_settings_widget_24dp,
                R.drawable.ic_settings_selected_24dp,
                new WidgetTextSizeFrag()));
    

    and add them to the viewPager:

        mAdapter = new MyPageAdapter(getSupportFragmentManager(), myFragPages);
        myViewPager.setAdapter(mAdapter);
    

    after this you can get after orientation change the correct fragment by using its class:

            WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
                .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
    
    0 讨论(0)
  • 2020-11-22 02:46

    If anyone is having issues with their FragmentStatePagerAdapter not properly restoring the state of its fragments...ie...new Fragments are being created by the FragmentStatePagerAdapter instead of it restoring them from state...

    Make sure you call ViewPager.setOffscreenPageLimit() BEFORE you call ViewPager.setAdapter(fragmentStatePagerAdapter)

    Upon calling ViewPager.setOffscreenPageLimit()...the ViewPager will immediately look to its adapter and try to get its fragments. This could happen before the ViewPager has a chance to restore the Fragments from savedInstanceState(thus creating new Fragments that can't be re-initialized from SavedInstanceState because they're new).

    0 讨论(0)
  • 2020-11-22 02:51

    A bit different opinion instead of storing the Fragments yourself just leave it to the FragmentManager and when you need to do something with the fragments look for them in the FragmentManager:

    //make sure you have the right FragmentManager 
    //getSupportFragmentManager or getChildFragmentManager depending on what you are using to manage this stack of fragments
    List<Fragment> fragments = fragmentManager.getFragments();
    if(fragments != null) {
       int count = fragments.size();
       for (int x = 0; x < count; x++) {
           Fragment fragment = fragments.get(x);
           //check if this is the fragment we want, 
           //it may be some other inspection, tag etc.
           if (fragment instanceof MyFragment) {
               //do whatever we need to do with it
           }
       }
    }
    

    If you have a lot of Fragments and the cost of instanceof check may be not what you want, but it is good thing to have in mind that the FragmentManager already keeps account of Fragments.

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