Удаление фрагментов из 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.