Удаление фрагментов из FragmentStatePagerAdapter
UPDATE
После некоторых серьезных боевых действий с этой проблемой и помощи со стороны пользователей SO мне удалось ее решить.
Вот как выглядит мой clearProgressUpTo
.
public void clearProgressUpTo(int progress) {
boolean deleted = false;
for (int i = fragments.size() - 1; i > 0; i--) {
int key = fragments.keyAt(i);
if (key != progress) {
fragments.delete(key);
deleted = true;
} else {
break;
}
}
if (deleted)
notifyDataSetChanged(); //prevents recursive call (and IllegalStateException: Recursive entry to executePendingTransactions)
while (mSavedState.size() > progress) {
mSavedState.remove(mSavedState.size()-1);
}
}
Причина, по которой я делаю это после notifyDataSetChanged
, состоит в том, что initiateItem
снова заполняет мой mSavedState
. И я уверен, что после уведомления моего adapter
не выполняется ни одно из этих Fragments
.
Также, если вы хотите сделать это так, измените ваш mSaveState
на protected
, чтобы вы могли получить его из расширяющегося класса (в моем случае VerticalAdapter
).
Я забыл упомянуть, что я использую FragmentStatePagerAdapter fix от Adam Speakman, чтобы решить мою проблему.
///////////////////////////////////////////////////// ЧАСТЬ ВОПРОСА//////////////////////////////////////////////////////
проблема, с которой я столкнулась, заключается в том, что мне нужно полностью удалить часть Fragments
, содержащуюся в FragmentStatePagerAdapter
.
Я уже сделал исправление POSITION_NONE
, но старые фрагменты по-прежнему остаются неповрежденными.
КОНСТРУКЦИЯ
Поскольку конструкция моего ViewPager
необычна, мне пришлось помещать внутри моего VerticalAdater
расширение FragmentStatePagerAdapter
a SparseArray
, которое содержит все мои фрагменты.
public class VerticalAdapter extends FragmentStatePagerAdapter {
private SparseArray<Fragment> fragments = new SparseArray<Fragment>();
...
@Override
public Fragment getItem(int position) {
return getFragmentForPosition(position);
}
@Override
public int getCount() {
return fragments.size();
}
ПРОБЛЕМА
Я сделал специальный метод, отвечающий за очистку всего Fragments
до некоторой точки в моем FragmentStatePagerAdapter
. Его работа хорошо, но проблема, с которой я не могу избавиться, очищает ArrayList
внутри FragmentStatePagerAdapter
. Даже если я очистил SparseArray
, ArrayList
от FragmentStatePagerAdapter
, что my VerticalAdapter
extends все еще держит Fragments
, который я не хочу иметь там. Из-за этого, когда я создаю новый Fragments
, у них есть состояние старых.
public void clearProgressUpTo(int progress) {
boolean deleted = false;
for (int i = fragments.size() - 1; i > 0; i--) {
int key = fragments.keyAt(i);
if (key != progress) {
fragments.delete(key);
deleted = true;
} else {
break;
}
}
if (deleted)
notifyDataSetChanged(); //prevents recursive call (and IllegalStateException: Recursive entry to executePendingTransactions)
}
Если вам нужна дополнительная информация/код, пожалуйста, скажите мне в комментариях, я постараюсь предоставить как можно больше информации.
Я понятия не имею, как исправить эту проблему, поэтому любой ввод будет оценен.
ИЗМЕНИТЬ
Добавление запрошенного кода.
private Fragment getFragmentForPosition(int position) {
return fragments.get(getKeyForPosition(position));
}
public int getKeyForPosition(int position) {
int key = 0;
for (int i = 0; i < fragments.size(); i++) {
key = fragments.keyAt(i);
if (i == position) {
return key;
}
}
return key;
}
Ответы
Ответ 1
FragmentStatePageAdapter
сохраняет состояние фрагмента при его уничтожении, так что в последнем случае, когда фрагмент воссоздается, к нему будет применено сохраненное состояние.
Состояние фрагмента сохраняется в частной переменной-члене. Следовательно, мы не можем продлить текущую реализацию, чтобы добавить функцию удаления сохраненного состояния.
Я клонировал текущую реализацию FragmentStatePagerAdapter
и включил функцию удаления сохраненного состояния. Вот ссылка gist.
/**
* Clear the saved state for the given fragment position.
*/
public void removeSavedState(int position) {
if(position < mSavedState.size()) {
mSavedState.set(position, null);
}
}
В вашем методе clearProgressUpTo
, всякий раз, когда вы удаляете фрагмент из разреженного массива, вызывайте этот метод, который очищает сохраненное состояние для этого фрагмента.
Ответ 2
Ваша проблема не вызвана использованием SparseArray. Фактически, вы встречаете ошибку FragmentStatePagerAdapter. Я потратил некоторое время на чтение исходного кода FragmentStatePagerAdapter, ViewPager и FragmentManagerImpl, а затем нашел причину, по которой ваши новые фрагменты имеют состояние старых:
FragmentStatePagerAdapter сохраняет исходные тексты ваших старых фрагментов, когда они их удаляют, и если вы добавите новые фрагменты в ваш адаптер позже, сохраненное состояние будет передано вашему новому фрагменту в том же месте.
Я искал эту проблему и нашел решение, вот его ссылка:
http://speakman.net.nz/blog/2014/02/20/a-bug-in-and-a-fix-for-the-way-fragmentstatepageradapter-handles-fragment-restoration/
Исходный код можно найти здесь:
https://github.com/adamsp/FragmentStatePagerIssueExample/blob/master/app/src/main/java/com/example/fragmentstatepagerissueexample/app/FixedFragmentStatePagerAdapter.java
Ключевой идеей этого решения является переписать FragmentStatePagerAdapter и использовать тег String для идентификации каждого фрагмента в адаптере. Поскольку каждый фрагмент имеет другой тег, легко идентифицировать новый фрагмент, а затем не передавать сохраненное состояние в новый фрагмент.
Наконец, скажите спасибо Адаму Спикману, который является автором этого решения.
Ответ 3
Когда вы вызываете notifyDataSetChanged()
адаптер начинает удалять свои фрагменты с помощью destroyItem(ViewGroup container, int position, Object object)
.
Однако каждое состояние экземпляра фрагмента сохраняется внутри FragmentStatePagerAdapter
для последующего использования в instantiateItem(ViewGroup container, int position)
.
Просто сохраните позицию элемента, которую вы хотите удалить, и вызовите notifyDataSetChanged()
. Сразу после уничтожения последнего элемента извлеките желаемое сохраненное состояние экземпляра из FragmentStatePagerAdapter
и замените его на null.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if(position == getCount() - 1 && i >= 0){
Bundle state = (Bundle) saveState();
Fragment.SavedState[] states = (Fragment.SavedState[]) state.getParcelableArray("states");
if (states != null)
states[i] = null;
i = -1;
restoreState(state, ClassLoader.getSystemClassLoader());
}
Примечание: getItemPosition(Object object)
должен возвращать PagerAdapter.POSITION_NONE
.