ViewPager и фрагменты - как правильно сохранить состояние фрагмента?

492

Фрагменты, кажется, очень хороши для разделения логики пользовательского интерфейса на некоторые модули. Но вместе с тем ViewPagerего жизненный цикл все еще остается для меня туманным. Поэтому мысли Гуру крайне необходимы!

редактировать

Смотрите тупое решение ниже ;-)

Объем

Основная деятельность имеет ViewPagerфрагменты. Эти фрагменты могут реализовывать несколько иную логику для других (подосновных) действий, поэтому данные фрагментов заполняются через интерфейс обратного вызова внутри действия. И все отлично работает при первом запуске, но! ...

проблема

Когда действие воссоздается (например, при изменении ориентации), делайте ViewPagerфрагменты. Код (вы найдете ниже) говорит, что каждый раз, когда создается действие, я пытаюсь создать новый ViewPagerадаптер фрагментов так же, как фрагменты (возможно, это проблема), но FragmentManager уже хранит все эти фрагменты где-то (где?) И запускает механизм отдыха для тех. Таким образом, механизм восстановления вызывает «старый» фрагмент onAttach, onCreateView и т. Д. С моим вызовом интерфейса обратного вызова для инициирования данных через реализованный метод Activity. Но этот метод указывает на вновь созданный фрагмент, который создается с помощью метода onCreate в Activity.

вопрос

Может быть, я использую неправильные шаблоны, но даже книга Android 3 Pro не так много об этом. Поэтому, пожалуйста , дайте мне один-два удара и укажите, как это сделать правильно. Большое спасибо!

Код

Основная деятельность

public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {

private MessagesFragment mMessagesFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    setContentView(R.layout.viewpager_container);
    new DefaultToolbar(this);

    // create fragments to use
    mMessagesFragment = new MessagesFragment();
    mStreamsFragment = new StreamsFragment();

    // set titles and fragments for view pager
    Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
    screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
    screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);

    // instantiate view pager via adapter
    mPager = (ViewPager) findViewById(R.id.viewpager_pager);
    mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
    mPager.setAdapter(mPagerAdapter);

    // set title indicator
    TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
    indicator.setViewPager(mPager, 1);

}

/* set of fragments callback interface implementations */

@Override
public void onMessageInitialisation() {

    Logger.d("Dash onMessageInitialisation");
    if (mMessagesFragment != null)
        mMessagesFragment.loadLastMessages();
}

@Override
public void onMessageSelected(Message selectedMessage) {

    Intent intent = new Intent(this, StreamActivity.class);
    intent.putExtra(Message.class.getName(), selectedMessage);
    startActivity(intent);
}

BasePagerActivity ака помощник

public class BasePagerActivity extends FragmentActivity {

BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}

адаптер

public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {

private Map<String, Fragment> mScreens;

public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {

    super(fm);
    this.mScreens = screenMap;
}

@Override
public Fragment getItem(int position) {

    return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}

@Override
public int getCount() {

    return mScreens.size();
}

@Override
public String getTitle(int position) {

    return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}

// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {

    // TODO Auto-generated method stub
}

}

Фрагмент

public class MessagesFragment extends ListFragment {

private boolean mIsLastMessages;

private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;

private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;

// define callback interface
public interface OnMessageListActionListener {
    public void onMessageInitialisation();
    public void onMessageSelected(Message selectedMessage);
}

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    // setting callback
    mListener = (OnMessageListActionListener) activity;
    mIsLastMessages = activity instanceof DashboardActivity;

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    inflater.inflate(R.layout.fragment_listview, container);
    mProgressView = inflater.inflate(R.layout.listrow_progress, null);
    mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
    return super.onCreateView(inflater, container, savedInstanceState);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // instantiate loading task
    mLoadMessagesTask = new LoadMessagesTask();

    // instantiate list of messages
    mMessagesList = new ArrayList<Message>();
    mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
    setListAdapter(mAdapter);
}

@Override
public void onResume() {
    mListener.onMessageInitialisation();
    super.onResume();
}

public void onListItemClick(ListView l, View v, int position, long id) {
    Message selectedMessage = (Message) getListAdapter().getItem(position);
    mListener.onMessageSelected(selectedMessage);
    super.onListItemClick(l, v, position, id);
}

/* public methods to load messages from host acitivity, etc... */
}

Решение

Тупое решение состоит в том, чтобы сохранить фрагменты внутри onSaveInstanceState (хоста Activity) с помощью putFragment и получить их внутри onCreate через getFragment. Но у меня все еще есть странное чувство, что вещи не должны работать так ... Смотрите код ниже:

    @Override
protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);
    getSupportFragmentManager()
            .putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}

protected void onCreate(Bundle savedInstanceState) {
    Logger.d("Dash onCreate");
    super.onCreate(savedInstanceState);

    ...
    // create fragments to use
    if (savedInstanceState != null) {
        mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
                savedInstanceState, MessagesFragment.class.getName());
                StreamsFragment.class.getName());
    }
    if (mMessagesFragment == null)
        mMessagesFragment = new MessagesFragment();
    ...
}
Алексей Малованый
источник
1
Теперь я задаюсь вопросом: должен ли я использовать совершенно другой подход или попытаться сохранить фрагменты основного действия (Dashboard) через onSavedInstancestate, чтобы использовать их в onCreate (). Есть ли правильный способ сохранить эти фрагменты и получить их из пакета в onCreate? Они, кажется, не подлежат продаже ...
Алексей Малованый
2
Работает 2-й подход - см. «Sulution». Но это, кажется, некрасивый кусок кода, не так ли?
Алексей Малованый
1
Ради усилий по очистке тега Android (подробности здесь: meta.stackexchange.com/questions/100529/… ), не могли бы вы опубликовать свое решение в качестве ответа и отметить его как выбранное? Таким образом, это не будет отображаться как вопрос без ответа :)
Александр Лукас
1
да, думаю, что все в порядке. Надеялся на что-то лучше моего ...
Алексей Малованый
1
Дурацкое решение вообще работает? Это дает мне исключение нулевого указателя ..
Жень Лю

Ответы:

451

Когда FragmentPagerAdapterобъект добавляет фрагмент в FragmentManager, он использует специальный тег, основанный на конкретной позиции, в которую будет помещен фрагмент. FragmentPagerAdapter.getItem(int position)вызывается только тогда, когда фрагмент для этой позиции не существует. После поворота Android заметит, что он уже создал / сохранил фрагмент для этой конкретной позиции, и поэтому он просто пытается восстановить соединение с ним FragmentManager.findFragmentByTag(), а не создавать новый. Все это бесплатно при использовании FragmentPagerAdapterи поэтому обычно в вашем getItem(int)методе есть код инициализации вашего фрагмента .

Даже если мы не использовали a FragmentPagerAdapter, не рекомендуется создавать новый фрагмент каждый раз в Activity.onCreate(Bundle). Как вы заметили, когда фрагмент добавляется в FragmentManager, он будет воссоздан для вас после поворота, и нет необходимости добавлять его снова. Это является распространенной причиной ошибок при работе с фрагментами.

Обычный подход при работе с фрагментами такой:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    CustomFragment fragment;
    if (savedInstanceState != null) {
        fragment = (CustomFragment) getSupportFragmentManager().findFragmentByTag("customtag");
    } else {
        fragment = new CustomFragment();
        getSupportFragmentManager().beginTransaction().add(R.id.container, fragment, "customtag").commit(); 
    }

    ...

}

При использовании FragmentPagerAdapterмы отказываемся от управления фрагментами для адаптера, и нам не нужно выполнять вышеуказанные шаги. По умолчанию, он будет предварительно загружать только один фрагмент впереди и позади текущей позиции (хотя он не уничтожит их, если вы не используете FragmentStatePagerAdapter). Это контролируется ViewPager.setOffscreenPageLimit (int) . Из-за этого прямой вызов методов для фрагментов вне адаптера не гарантируется как допустимый, поскольку они могут даже не существовать.

Короче говоря, ваше решение использовать putFragmentего для получения ссылки впоследствии не так уж и безумно, и не так уж отличается от обычного способа использовать фрагменты в любом случае (см. Выше). В противном случае получить ссылку сложно, поскольку этот фрагмент добавляется адаптером, а не вами лично. Просто убедитесь, что offscreenPageLimitон достаточно высок, чтобы всегда загружать нужные фрагменты, поскольку вы полагаетесь на его наличие. Это обходит возможности отложенной загрузки ViewPager, но, похоже, это то, что вы хотите для своего приложения.

Другой подход заключается в переопределении FragmentPageAdapter.instantiateItem(View, int)и сохранении ссылки на фрагмент, возвращенный из супер-вызова, перед его возвратом (он имеет логику для поиска фрагмента, если он уже существует).

Для более полной картины взгляните на некоторые из источников FragmentPagerAdapter (короткий) и ViewPager (длинный).

antonyt
источник
7
Очень понравилась последняя часть. Имел кеш для фрагментов и перемещал вставленную в кеш логику внутри FragmentPageAdapter.instantiateItem(View, int). Наконец исправлена ​​длительная ошибка, которая появляется только при смене ротации / конфигурации и сводит меня с ума ...
Карлос Собриньо
2
Это исправило проблему, возникающую при смене поворота. Может быть, я просто не вижу, чтобы посмотреть, но документировано ли это? т.е. используя тег для восстановления из предыдущего состояния? Это может быть очевидный ответ, но я довольно новичок в разработке Android.
1
@ Industrial-antidepressant Это идентификатор контейнера (например, FrameLayout), к которому должен быть добавлен фрагмент.
антонит
1
Кстати, для меня это было FragmentPageAdapter.instantiateItem(ViewGroup, int)скорее, чем FragmentPageAdapter.instantiateItem(View, int).
Отображаемое имя
1
Кстати, вы знаете, какой (если есть) метод жизненного цикла фрагмента вызывается, когда фрагмент удаляется с экрана? Это onDetach()или что-то еще?
августа
36

Я хочу предложить решение, которое расширяет antonyt «S замечательный ответ и упоминание переопределения , FragmentPageAdapter.instantiateItem(View, int)чтобы сохранить ссылки на созданный Fragmentsтаким образом Вы можете сделать работу на них позже. Это также должно работать с FragmentStatePagerAdapter; см примечания для деталей.


Вот простой пример того, как получить ссылку на Fragmentsвозвращаемое значение, FragmentPagerAdapterкоторое не зависит от внутреннего tagsнабора в Fragments. Ключ переопределитьinstantiateItem() и сохранить ссылки там, а не в getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

        public CustomPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}

или если вы предпочитаете работать с tagsпеременными / ссылками на члены классаFragments вы также можете захватить tagsнабор FragmentPagerAdapterтаким же образом: ПРИМЕЧАНИЕ: это не относится к, FragmentStatePagerAdapterтак как оно не устанавливается tagsпри создании его Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}

Обратите внимание, что этот метод НЕ полагается на имитацию внутреннего tagнабора с помощью FragmentPagerAdapterи вместо этого использует надлежащие API для их получения. Таким образом, даже если tagизменения в будущих версиях SupportLibraryвы все равно будете в безопасности.


Не забывай что в зависимости от вашего дизайна Activity, над которым Fragmentsвы пытаетесь работать, он может существовать, а может и не существовать, поэтому вы должны учитывать это, выполняя nullпроверки перед использованием ваших ссылок.

Кроме того, если вместо этого вы работаете FragmentStatePagerAdapter, то вы не хотите хранить жесткие ссылки на свои, Fragmentsпотому что у вас их может быть много, а жесткие ссылки будут излишне хранить их в памяти. Вместо этого сохраните Fragmentссылки в WeakReferenceпеременных вместо стандартных. Нравится:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
Тони Чан
источник
При нехватке памяти Android может попросить FragmentManager уничтожить неиспользуемые фрагменты, верно? Если я прав, ваша вторая версия будет работать, первая может потерпеть неудачу. (Не знаю, если текущие версии Android делают это, но, похоже, мы должны ожидать этого.)
Moritz Both
1
как насчет создания FragmetPagerAdapterв onCreate of Activity при каждом повороте экрана. Это неправильно, потому что это может обойти повторное использование уже добавленных фрагментовFragmentPagerAdapter
Бахман
Переопределение instantiateItem()- это путь; это помогло мне управлять поворотами экрана и извлекать мои существующие экземпляры Fragment после возобновления Activity и Adapter; Я оставил себе комментарии в коде как напоминание: после поворота getItem()НЕ вызывается; только этот метод instantiateItem()вызывается. Супер реализация для instantiateItem()повторного присоединения фрагментов после ротации (при необходимости), вместо создания новых экземпляров!
HelloImKevo
Я получаю нулевой указатель Fragment createdFragment = (Fragment) super.instantiateItem..в первом решении.
AlexS
После поиска всех повторяющихся и повторяющихся вариантов этой проблемы это было лучшее решение. (Перекрестные ссылки из другого Q с более релевантным названием, так что спасибо вам за это!)
MandisaW
18

Я нашел другое относительно простое решение для вашего вопроса.

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

String tag="android:switcher:" + viewId + ":" + index;

viewIdЭто container.getId(), то containerваш ViewPagerэкземпляр. indexПозиция фрагмента. Следовательно, вы можете сохранить идентификатор объекта в outState:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("viewpagerid" , mViewPager.getId() );
}

@Override
    protected void onCreate(Bundle savedInstanceState) {
    setContentView(R.layout.activity_main);
    if (savedInstanceState != null)
        viewpagerid=savedInstanceState.getInt("viewpagerid", -1 );  

    MyFragmentPagerAdapter titleAdapter = new MyFragmentPagerAdapter (getSupportFragmentManager() , this);        
    mViewPager = (ViewPager) findViewById(R.id.pager);
    if (viewpagerid != -1 ){
        mViewPager.setId(viewpagerid);
    }else{
        viewpagerid=mViewPager.getId();
    }
    mViewPager.setAdapter(titleAdapter);

Если вы хотите связаться с этим фрагментом, вы можете получить, если FragmentManager, например:

getSupportFragmentManager().findFragmentByTag("android:switcher:" + viewpagerid + ":0")
user2110066
источник
21
хм, я не думаю, что это хороший способ, как вы отвечаете на внутреннем соглашении об именовании тегов, которое ни в коем случае не останется прежним.
Дори
1
Для тех, кто ищет решение, которое не зависит от внутреннего tag, попробуйте мой ответ .
Тони Чан
16

Я хочу предложить альтернативное решение для, возможно, немного другого случая, так как многие из моих поисков ответов продолжали приводить меня к этой теме.

Мой случай - я создаю / добавляю страницы динамически и перемещаю их в ViewPager, но при повороте (onConfigurationChange) я получаю новую страницу, потому что, конечно, OnCreate вызывается снова. Но я хочу сохранить ссылку на все страницы, которые были созданы до ротации.

Проблема - у меня нет уникальных идентификаторов для каждого фрагмента, который я создаю, поэтому единственный способ ссылки был каким-то образом хранить ссылки в массиве, который будет восстановлен после изменения ротации / конфигурации.

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

public class MainActivity extends FragmentActivity

Итак, в рамках этого занятия я объявляю частного участника для отслеживания открытых страниц.

private List<Fragment> retainedPages = new ArrayList<Fragment>();

Это обновляется каждый раз, когда onSaveInstanceState вызывается и восстанавливается в onCreate.

@Override
protected void onSaveInstanceState(Bundle outState) {
    retainedPages = _adapter.exportList();
    outState.putSerializable("retainedPages", (Serializable) retainedPages);
    super.onSaveInstanceState(outState);
}

... так что, как только он будет сохранен, его можно получить ...

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (savedInstanceState != null) {
        retainedPages = (List<Fragment>) savedInstanceState.getSerializable("retainedPages");
    }
    _mViewPager = (CustomViewPager) findViewById(R.id.viewPager);
    _adapter = new ViewPagerAdapter(getApplicationContext(), getSupportFragmentManager());
    if (retainedPages.size() > 0) {
        _adapter.importList(retainedPages);
    }
    _mViewPager.setAdapter(_adapter);
    _mViewPager.setCurrentItem(_adapter.getCount()-1);
}

Это были необходимые изменения в основной деятельности, и поэтому мне нужны были члены и методы в моем FragmentPagerAdapter, чтобы это работало, поэтому в

public class ViewPagerAdapter extends FragmentPagerAdapter

идентичная конструкция (как показано выше в MainActivity)

private List<Fragment> _pages = new ArrayList<Fragment>();

и эта синхронизация (как используется выше в onSaveInstanceState) поддерживается специально методами

public List<Fragment> exportList() {
    return _pages;
}

public void importList(List<Fragment> savedPages) {
    _pages = savedPages;
}

И, наконец, в классе фрагмента

public class CustomFragment extends Fragment

для того, чтобы все это заработало, было сделано два изменения:

public class CustomFragment extends Fragment implements Serializable

а затем добавить это в onCreate, чтобы фрагменты не были уничтожены

setRetainInstance(true);

Я все еще нахожусь в процессе обдумывания фрагментов и жизненного цикла Android, поэтому предостережение здесь может заключаться в избыточности / неэффективности этого метода. Но это работает для меня, и я надеюсь, что может быть полезным для других в случаях, подобных моему.

Фрэнк Инь
источник
2
+1 для setRetainInstance (true) - черная магия, которую я искал!
склонение
Это было еще проще, используя FragmentStatePagerAdapter (v13). Которые занимаются для вас восстановлением и выпуском государства.
Мясник
Четкое описание и очень хорошее решение. Спасибо!
Бруно Биери
8

Мое решение очень грубое, но работает: так как мои фрагменты динамически создаются из сохраненных данных, я просто удаляю все фрагменты из PageAdapterперед вызовом super.onSaveInstanceState()и затем воссоздаю их при создании активности:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putInt("viewpagerpos", mViewPager.getCurrentItem() );
    mSectionsPagerAdapter.removeAllfragments();
    super.onSaveInstanceState(outState);
}

Вы не можете удалить их onDestroy(), иначе вы получите это исключение:

java.lang.IllegalStateException: Не могу выполнить это действие после onSaveInstanceState

Вот код на странице адаптера:

public void removeAllfragments()
{
    if ( mFragmentList != null ) {
        for ( Fragment fragment : mFragmentList ) {
            mFm.beginTransaction().remove(fragment).commit();
        }
        mFragmentList.clear();
        notifyDataSetChanged();
    }
}

Я сохраняю только текущую страницу и восстанавливаю ее onCreate()после создания фрагментов.

if (savedInstanceState != null)
    mViewPager.setCurrentItem( savedInstanceState.getInt("viewpagerpos", 0 ) );  
Сэр джон
источник
Я не знаю почему, но notifyDataSetChanged (); вызывает сбой приложения
Малахия
4

Что это BasePagerAdapter? Вы должны использовать один из стандартных адаптеров пейджера - либо, FragmentPagerAdapterлибо FragmentStatePagerAdapter, в зависимости от того, хотите ли вы, чтобы фрагменты, которые больше не нужны, ViewPagerлибо сохранялись (первый), либо сохраняли свое состояние (последний) и создавались заново, если снова нужно

Пример кода для использования ViewPagerможно найти здесь

Это правда, что управление фрагментами в пейджере представлений между экземплярами операций немного сложнее, потому что FragmentManagerв среде заботится о сохранении состояния и восстановлении любых активных фрагментов, которые создал пейджер. Все это действительно означает, что адаптер при инициализации должен убедиться, что он повторно соединяется с любыми восстановленными фрагментами. Вы можете посмотреть на код FragmentPagerAdapterили FragmentStatePagerAdapterпосмотреть, как это делается.

hackbod
источник
1
Код BasePagerAdapter доступен в моем вопросе. Как можно видеть, он просто расширяет FragmentPagerAdapter с целью реализации TitleProvider . Так что все уже работает с Android предложенный подход.
Алексей Малованый
Я не знал о «FragmentStatePagerAdapter». Ты спас меня буквально часами. Спасибо.
Кносс,
2

Если у кого-то возникают проблемы с его FragmentStatePagerAdapter, неправильно восстанавливающего состояние его фрагментов ... то есть ... новые фрагменты создаются FragmentStatePagerAdapter вместо того, чтобы восстанавливать их из состояния ...

Убедитесь, что вы звоните, ViewPager.setOffscreenPageLimit()прежде чем позвонитьViewPager.setAdapter(fragmentStatePagerAdapter)

После вызова ViewPager.setOffscreenPageLimit()... ViewPager сразу же посмотрит на свой адаптер и попытается получить его фрагменты. Это может произойти до того, как ViewPager сможет восстановить фрагменты из saveInstanceState (создавая новые фрагменты, которые нельзя повторно инициализировать из SavedInstanceState, поскольку они новые).

dell116
источник
0

Я придумал это простое и элегантное решение. Предполагается, что действие отвечает за создание фрагментов, а адаптер просто обслуживает их.

Это код адаптера (здесь нет ничего странного, за исключением того факта, что mFragmentsэто список фрагментов, поддерживаемый 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();
    }
} 

Вся проблема этого потока заключается в получении ссылки на «старые» фрагменты, поэтому я использую этот код в onCreate Activity.

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

Конечно, вы можете дополнительно настроить этот код при необходимости, например, убедившись, что фрагменты являются экземплярами определенного класса.

Merlevede
источник
0

Чтобы получить фрагменты после изменения ориентации, вы должны использовать .getTag ().

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

Для большей обработки я написал свой собственный ArrayList для моего PageAdapter, чтобы получить фрагмент по viewPagerId и FragmentClass в любой позиции:

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

Так что просто создайте MyPageArrayList с фрагментами:

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

и добавьте их в viewPager:

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

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

        WidgetDataFrag dataFragment = (WidgetDataFrag) getSupportFragmentManager()
            .findFragmentByTag(myFragPages.getTag(myViewPager.getId(), WidgetDataFrag.class));
попрошайка
источник
-30

Добавить:

   @SuppressLint("ValidFragment")

перед вашим классом.

это не работает, сделайте что-то вроде этого:

@SuppressLint({ "ValidFragment", "HandlerLeak" })
andro_stackoverflow
источник