Планирование транзакций с несколькими фрагментами Android

У меня есть HorizontalScrollView, содержащий (горизонтальный) LinearLayout, который я использую в качестве контейнера для добавления нескольких фрагментов. После некоторых изменений мне нужно удалить все фрагменты из этого контейнера и добавить новые. Однако, кажется, проблема с заказом, когда я удаляю старые фрагменты.

Вот сценарии:

  • запуск приложения
    • правильное добавление фрагментов A1, B1, C1, D1 в этом порядке
  • изменить контент
    • если не удалить исходные фрагменты, но добавив A2, B2, C2 (в качестве одной транзакции), он покажет A1, B1, C1, D1, A2, B2, C2
    • при удалении исходных фрагментов (либо отдельно, либо с использованием одной и той же транзакции), затем добавив A2, B2, C2, он покажет C2, B2, A2

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

EDIT: Обходной путь не работает все время.

Я использую android.support.v4.app.Fragment.

Любые идеи о том, что происходит?

Ответ 1

Я включил отладку в FragmentManager, и я нашел проблему.

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

V/FragmentManager? Freeing fragment index TimeTracesChartFragment{42ac4910 #7 id=0x7f080044}
V/FragmentManager? add: RealTimeValuesFragment{42a567b0 id=0x7f080044}
V/FragmentManager? Allocated fragment index RealTimeValuesFragment{42a567b0 #7 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d35c38 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d35c38 #6 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d35e98 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d35e98 #5 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d36220 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d36220 #4 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d39d18 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d39d18 #3 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d3a170 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d3a170 #2 id=0x7f080044}
V/FragmentManager? add: TimeTracesChartFragment{42d3a528 id=0x7f080044}
V/FragmentManager? Allocated fragment index TimeTracesChartFragment{42d3a528 #1 id=0x7f080044}
V/FragmentManager? moveto CREATED: TimeTracesChartFragment{42d3a528 #1 id=0x7f080044}

И вот код преступника:

http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/support/v4/app/FragmentManager.java#FragmentManagerImpl.makeActive%28android.support.v4.app.Fragment%29

 void makeActive(Fragment f) {
        if (f.mIndex >= 0) {
            return;
        }

        if (mAvailIndices == null || mAvailIndices.size() <= 0) {
            if (mActive == null) {
                mActive = new ArrayList<Fragment>();
            }
            f.setIndex(mActive.size(), mParent);
            mActive.add(f);

        } else {
            f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
            mActive.set(f.mIndex, f);
        }
        if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
    }

Обратите внимание, как доступные индексы берутся из задней части списка. Вероятно, он должен выбрать самый низкий доступный индекс, чтобы сохранить порядок.

Теперь подумать об обходном пути...

EDIT:

Здесь обходной путь: Создайте две отдельные транзакции: одну для удаления, а затем одну для дополнений, затем выполните следующее:

removalTxn.commit();
getSupportFragmentManager().executePendingTransactions();
FragmentTransactionBugFixHack.reorderIndices(getSupportFragmentManager());
//create additionTxn
additionTxn.commit();

Где FragmentTransactionBugFixHack выглядит следующим образом:

package android.support.v4.app;

import java.util.Collections;

public class FragmentTransactionBugFixHack {

    public static void reorderIndices(FragmentManager fragmentManager) {
        if (!(fragmentManager instanceof FragmentManagerImpl))
            return;
        FragmentManagerImpl fragmentManagerImpl = (FragmentManagerImpl) fragmentManager;
        if (fragmentManagerImpl.mAvailIndices != null)
            Collections.sort(fragmentManagerImpl.mAvailIndices, Collections.reverseOrder());
    }

}

Это не идеально, потому что две отдельные транзакции будут мерцать до белого (или как бы то ни было, что у вас на заднем плане), но, по крайней мере, он будет правильно их заказывать.

Ответ 2

другой способ устранить эту проблему:

заменить аргументы памяти ArrayList на ReverseOrderArrayList

ReverseOrderArrayList.java

public class ReverseOrderArrayList<T extends Comparable> extends ArrayList<T> {
@Override
public boolean add(T object) {
    boolean value = super.add(object);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public void add(int index, T object) {
    super.add(index, object);
    Collections.sort(this, Collections.reverseOrder());
}

@Override
public boolean addAll(Collection<? extends T> collection) {
    boolean value = super.addAll(collection);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public boolean addAll(int index, Collection<? extends T> collection) {
    boolean value = super.addAll(index, collection);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
protected void removeRange(int fromIndex, int toIndex) {
    super.removeRange(fromIndex, toIndex);
    Collections.sort(this, Collections.reverseOrder());
}

@Override
public boolean remove(Object object) {
    boolean value = super.remove(object);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public boolean removeAll(Collection<?> collection) {
    boolean value = super.removeAll(collection);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

@Override
public T remove(int index) {
    T value = super.remove(index);
    Collections.sort(this, Collections.reverseOrder());
    return value;
}

}

Hack

public class FragmentTransactionBugFixHack {
private static final String TAG = "FragmentTransactionBugFixHack";

public static void injectFragmentTransactionAvailIndicesAutoReverseOrder(FragmentManager fragmentManager) {
    try {
        Log.d(TAG, "injection injectFragmentTransactionAvailIndicesAutoReverseOrder");
        if (fragmentManager==null || !(fragmentManager instanceof FragmentManagerImpl)) return;
        FragmentManagerImpl fragmentManagerImpl = (FragmentManagerImpl) fragmentManager;
        if (fragmentManagerImpl.mAvailIndices!=null && fragmentManagerImpl.mAvailIndices instanceof ReverseOrderArrayList) return;
        ArrayList<Integer> backupList = fragmentManagerImpl.mAvailIndices;
        fragmentManagerImpl.mAvailIndices = new ReverseOrderArrayList<>();
        if (backupList!=null) {
            fragmentManagerImpl.mAvailIndices.addAll(backupList);
        }
        Log.d(TAG, "injection ok");
    } catch (Exception e) {
        Log.e(TAG, e);
    }
}}

Использование: вызовите FragmentTransactionBugFixHack.injectFragmentTransactionAvailIndicesAutoReverseOrder в activity-onCreate.

Ответ 3

В качестве альтернативного решения вы можете попробовать добавить представления в LinearLayout, а затем добавить каждый фрагмент в правильное представление. Это не идеально, но кажется, что вы не можете полагаться на заказ создания Фрагмента. Что-то вроде следующего:

ViewGroup f1 = new ViewGroup(this);
linearLayout.addView(f1);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(f1, new A2(), "mediocreworkaround");
ft.commit();

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

linearlayout.removeAllViews();

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

Edit:

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

//Declare a counter to ensure generated ids are different
idCounter = 1;

Теперь используйте этот счетчик для установки идентификаторов при создании представлений:

//Set unique id on the view. If you really want, you can do a 
//findViewById(idCounter) == null check to ensure it uniqueness. 
//Once you add this id, the system will take care of remembering 
//it state for you across configuration chagne
f1.setId(idCounter++);

Ответ 4

Если бы аналогичная проблема, решение, которое я в конечном итоге использовал, состояло в том, чтобы иметь несколько транзакций. В моем случае это были только A, B, C. И я использовал одну транзакцию для добавления A, один для добавления B, один для добавления C.

Порядок транзакций кажется надежным.

Вероятно, требуется более сложный код, если вы хотите работать с backstack. Но тег backstack в первой транзакции также должен содержать правильную обработку.

Ответ 5

Вот немного измененная версия Radu answer, где я добавил часть рекурсии в конце. Это переупорядочивает индексы данного диспетчера фрагментов и всех его фрагментов childFragmentMangers, а также всех дочерних менеджеров этих фрагментов и т.д.

Это новый класс, который вы добавляете в свой проект (вы можете добавить пакет android.support.v4.app в папку с исходным кодом java и поместить его в этот пакет, и это сработало для меня):

package android.support.v4.app;

public class FragmentTransactionBugFixHack {

    public static void reorderIndices(FragmentManager fragmentManager) {
        if (!(fragmentManager instanceof FragmentManagerImpl))
            return;
        FragmentManagerImpl fragmentManagerImpl = (FragmentManagerImpl) fragmentManager;
        if (fragmentManagerImpl.mAvailIndices != null) {
            Collections.sort(fragmentManagerImpl.mAvailIndices, Collections.reverseOrder());
        }

        //Recursively reorder indices of all child fragments.
        List<Fragment> fragments = fragmentManager.getFragments();
        //The support library FragmentManager returns null if none.
        if(fragments != null) {
            for (Fragment fragment : fragments) {
                //For some reason, the fragments in the list of fragments might be null.
                if(fragment != null) {
                    reorderIndices(fragment.getChildFragmentManager());
                }
            }
        }
    }
}

Для решения проблемы, когда она воссоздает фрагменты из строя, когда устройство вращается, просто поместите это в свой класс Activity, который управляет фрагментом (Кредит принадлежит комментарию Андрея Углева в Раду ответ):

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    FragmentTransactionBugFixHack.reorderIndices(getSupportFragmentManager());
}