ViewPager внутри фрагмента, как сохранить состояние?

В моем приложении активность фрагмента содержит два фрагмента: фрагмент A и фрагмент B. Фрагмент B — это пейджер представления, содержащий 3 фрагмента.

введите здесь описание изображения

В моей деятельности, чтобы предотвратить воссоздание фрагмента при изменении конфигурации:

if(getSupportFragmentManager().findFragmentByTag(MAIN_TAB_FRAGMENT) == null) {
    getSupportFragmentManager().beginTransaction().replace(R.id.container, new MainTabFragment(), MAIN_TAB_FRAGMENT).commit();
}

Код фрагмента B:

public class MainTabFragment extends Fragment {

    private PagerSlidingTabStrip mSlidingTabLayout;
    private LfPagerAdapter adapter;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_tab, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {

        this.adapter = new LfPagerAdapter(getChildFragmentManager());

        this.mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
        this.mViewPager.setAdapter(adapter);

        this.mSlidingTabLayout = (PagerSlidingTabStrip) view.findViewById(R.id.sliding_tabs);
        this.mSlidingTabLayout.setViewPager(this.mViewPager);
    }
}

Код адаптера:

public class LfPagerAdapter extends FragmentPagerAdapter {

    private static final int NUM_ITEMS = 3;

    private FragmentManager fragmentManager;

    public LfPagerAdapter(FragmentManager fm) {
        super(fm);
        this.fragmentManager = fm;
    }

    @Override
    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public Fragment getItem(int position) {
        Log.d("TEST","TEST");
        switch (position) {
            case 1:
                return FragmentC.newInstance();
            case 2:
                return FragmentD.newInstance();
            default:
                return FragmentE.newInstance();
        }
    }
}

Моя проблема в том, что я не могу сохранить состояние пейджера представления и его дочерние фрагменты при изменении ориентации.

Очевидно, это вызывается при каждом вращении:

this.adapter = new LfPagerAdapter(getChildFragmentManager());

что приведет к воссозданию всего пейджера, верно? Как результат

getItem(int position)

будет вызываться при каждом вращении, и фрагмент будет создаваться с нуля и терять свое состояние:

return FragmentC.newInstance();

Я попытался решить это с помощью:

if(this.adapter == null)
    this.adapter = new LfPagerAdapter(getChildFragmentManager());

в onViewCreated, но в результате при ротации фрагменты внутри пейджера удалялись.

Любые идеи, как правильно сохранить состояние внутри пейджера?


person Upvote    schedule 18.11.2014    source источник
comment
Я сделал это, сохранив состояние фрагмента (и выбранную позицию пейджера) в instanceState (не знаю точного переопределения, поскольку сейчас у меня нет доступа к Eclipse). Затем в OnCreate (если в действии) или OnCreateView() (если во фрагменте) я проверяю, существуют ли они, а затем воссоздаю их.   -  person kha    schedule 05.12.2014
comment
Fragment автоматически управляются и восстанавливаются FragmentManager, а реализации PagerAdapter предназначены для сохранения постоянной ссылки на них. getItem() будет вызываться только один раз для инициализации каждой страницы.   -  person corsair992    schedule 10.12.2014
comment
@artworkad: кажется, что Fragments уже сохранены. что именно ты хочешь исполнить? Вы можете описать немного больше?   -  person Mehul Joisar    schedule 10.12.2014


Ответы (2)


Вам нужно будет сделать 2 вещи, чтобы решить проблему:

1) Вы должны использовать метод onCreate вместо onViewCreated для создания экземпляра LfPagerAdapter;

i.e.:

    public class MainTabFragment extends Fragment {

    private PagerSlidingTabStrip mSlidingTabLayout;
    private LfPagerAdapter adapter;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        this.adapter = new LfPagerAdapter(getChildFragmentManager());
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_tab, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {


        this.mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
        this.mViewPager.setAdapter(adapter);

        this.mSlidingTabLayout = (PagerSlidingTabStrip) view.findViewById(R.id.sliding_tabs);
        this.mSlidingTabLayout.setViewPager(this.mViewPager);
    }
}

2) Вам нужно будет расширить FragmentStatePagerAdapter вместо FragmentPagerAdapter

person Mehul Joisar    schedule 05.12.2014
comment
FragmentPagerAdapter никогда не отсоединяет Fragment, поэтому они автоматически управляются и сохраняются FragmentManager, как обычно, и оба адаптера сохраняют постоянную ссылку на них. Сохранение экземпляра Fragment не обязательно, и на самом деле может даже некоторым образом мешать семантике сохранения состояний View и/или дочерних Fragment. - person corsair992; 10.12.2014
comment
@corsair992: тогда что нужно сделать, чтобы решить проблему? - person Mehul Joisar; 10.12.2014
comment
Ничего - с самого начала нет проблем. Если экземпляр Fragment не сохраняется, то состояние ViewPager и дочерних Fragment уже должно управляться (хотя вам может потребоваться настроить его правильно, чтобы сохранить текущую страницу). - person corsair992; 10.12.2014
comment
@corsair992: не сохранено или сохранено? Я не понял твоей мысли. - person Mehul Joisar; 10.12.2014
comment
Как я сказал в своем первом комментарии: сохранение экземпляра Fragment не обязательно, и на самом деле может даже каким-то образом мешать семантике сохранения состояний Views и/или дочерних Fragments. Я говорю о вызове setRetainInstance(true); в код инициализации Fragment. - person corsair992; 10.12.2014
comment
@corsair992: так что я думаю, если мы используем setRetainInstance(true) для FragmentC,FragmentD,FragmentE, тогда проблема также может быть решена. правильно? - person Mehul Joisar; 10.12.2014
comment
Лучше бы вообще этого не делать, так как в этом нет необходимости, да и не для UI это делать Fragment ни в коем случае. - person corsair992; 10.12.2014
comment
Спасибо за информацию и комментарии. На самом деле я предполагал, что фрагменты будут сохранены, однако это не так, потому что getItem вызывался несколько раз. Собираюсь исследовать это в ближайшее время, если у меня будет время. Я оставляю этот вопрос открытым, как только я проверил это. - person Upvote; 11.12.2014
comment
@artworkadシ: Как я уже упоминал, вы должны удалить setRetainInstance(true) из своей базы Fragment, и тогда все должно работать как положено. - person corsair992; 13.12.2014
comment
@corsair не работает, как ты сказал. Если setRetainInstance удален из базового фрагмента, все дочерние фрагменты в адаптере создаются заново. - person Ravi; 06.02.2015
comment
@Ravi: я не утверждал, что они не будут воссозданы. Они будут управляться так же, как и любые другие Fragment. - person corsair992; 06.02.2015

Android автоматически воссоздаст вашу активность в конфигурации без этой строки android:configChanges="keyboardHidden|orientation|screenSize" в вашем манифесте, чтобы вы могли самостоятельно обработать onConfiguration изменение.

Единственный способ - использовать onSaveInstanceState() как в вашем ActivityFragement, чтобы сохранить состояние viewPager (например, текущую позицию), так и во фрагментах, где вам нужно сохранить материал

Пример того, как вы можете сохранить текущую позицию вьюпейджера и восстановить ее onConfiguration изменить

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
            int position = mViewPager.getCurrentItem();
            outState.Int("Key", position );
    }

    @Override //then restore in on onCreate();
    public void onCreated(Bundle savedInstanceState) {
        super.onCreated(savedInstanceState);
        // do stuff
        if (savedInstanceState != null) {
              int  position= savedInstanceState.getInt("Key");
            mViewPager.setCurrentItem(position)
        }
    }

Конечно, это очень простой пример.

Ps: для восстановления во фрагменте используйте метод onActivityCreated() вместо метода onCreate().

Вот еще один пример того, как сохранить состояние: Нажми на меня!

person murielK    schedule 11.12.2014
comment
Собственно, ViewPager сам управляет сохранением и восстановлением текущей позиции, если адаптер установлен в нужное время после восстановления View состояний. - person corsair992; 13.12.2014
comment
Я знаю, что это было давно, но @corsair992, не могли бы вы рассказать об этом подробнее? Звучит как раз то, что мне нужно. - person Steven Schoen; 06.04.2015
comment
@ D_Steve595: Взглянув на исходный код ViewPager в последней версии библиотеки поддержки, кажется, что теперь он должен правильно обрабатывать сохранение положения и состояния адаптера во всех случаях. У вас есть какие-либо проблемы с этим? - person corsair992; 06.04.2015
comment
Спасибо за ответ! В итоге я задал вопрос, и здесь был дан ответ: stackoverflow.com/questions/29464522/ - person Steven Schoen; 06.04.2015