Планирование транзакций с несколькими фрагментами 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());
}