Почему Android меняет значение EditTexts с тем же идентификатором?
У меня есть фрагмент, содержащий LinearLayout, где раздуваются различные элементы в зависимости от бизнес-логики. Один из этих элементов содержит EditText. Когда у меня есть несколько из этих элементов с различным содержанием, и я отсоединяю / прикрепляю фрагмент, все EditTexts так или иначе получают все тот же текст. Это происходит только до тех пор, пока EditText имеет идентификатор в файле макета.
Почему это происходит? Есть ли другой способ предотвратить это, кроме удаления идентификатора? Я хотел бы использовать findViewById
на мои раздутые предметы, чтобы получить доступ к представлениям вместо подверженных ошибкам getChildAt
,
Я создал минималистичный пример для демонстрации проблемы по адресу https://github.com/rodja/EditTextValueProblem
2 ответа
Это можно просто исправить, установив android:saveEnabled="false"
в определении макета EditTexts. Конечно, вы должны убедиться, что контент сохранен / восстановлен самостоятельно. Так что это не интуитивный обходной путь - но он работает для моего случая. Тем не менее, все это выглядит как ошибка Android:
Приятной особенностью системы разметки Android является то, что
Идентификатор не обязательно должен быть уникальным по всему дереву [...]
как указано в документации Android. Это значительно упрощает повторное использование кода и макета и активно используется разработчиками. Я думаю, что реализация состояния экземпляра сохранения / восстановления для представлений использует идентификатор представления в качестве ключа для сохранения его состояния, поэтому оно опирается на уникальность во всем дереве. WTF?
Обновить
Я добавил ListView
к примеру на GitHub, который демонстрирует, что ListView
почти наверняка использует подобный обходной путь, чтобы не допустить столкновения EditTexts с этой проблемой. Как видно, текст, который вводится в EditText внутри ListView, не восстанавливается автоматически.
Существует другая возможность, просто измените идентификатор редактируемого текста, например,
mEditText.setId((parentView.getId()+editTextPosition+someFinalNumber));
Или, если это EditText внутри некоторого пользовательского макета:
mEditText.setId((this.getId()+someFinalNumber));
Таким образом, все EditTexts будут иметь разные идентификаторы, и текст будет восстановлен правильно.
Привет из будущего. К сожалению, разработчики фреймворков Android до сих пор любят посмеяться над нашим счетом. Благодаря нашим умным ученым мы пришли к более эффективному решению. См. Ниже (да, мы все еще будем использовать java в будущем). Вы можете найти оригинальную "исследовательскую работу" здесь.
private static final String KEY_SUPER = "superState";
private static final String KEY_CHILD = "childState";
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
// Don't call super, to disable saving child view states
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
// Similar as above, but other way around
dispatchThawSelfOnly(container);
}
@Override
protected @Nullable Parcelable onSaveInstanceState() {
val bundle = new Bundle();
val superState = super.onSaveInstanceState();
bundle.putParcelable(KEY_SUPER, superState);
val childStates = saveChildViewStates();
bundle.putSparseParcelableArray(KEY_CHILD, childStates);
return bundle;
}
private SparseArray<Parcelable> saveChildViewStates() {
val childViewStates = new SparseArray<Parcelable>();
for(val view : getChildViews())
view.saveHierarchyState(childViewStates);
return childViewStates;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
val bundle = (Bundle) state;
val superState = bundle.getParcelable(KEY_SUPER);
super.onRestoreInstanceState(superState);
val childState = bundle.getSparseParcelableArray(KEY_CHILD);
restoreChildViewStates(childState);
}
private void restoreChildViewStates(SparseArray<Parcelable> states) {
for(val view : getChildViews())
view.restoreHierarchyState(states);
}
private Iterable<View> getChildViews() {
int childCount = getChildCount();
return () -> new Iterator<View>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < childCount;
}
@Override
public View next() {
val view = getChildAt(index);
if(view == null) {
throw new RuntimeException("View was null. Index: " + index +
" count: " + childCount + ".");
}
Objects.requireNonNull(view);
index++;
return view;
}
};
}
PS: Обратите внимание, что это решение может плохо себя вести, если вы заказываете ребенка View
s изменения. Дядя Боба говорит, что сейчас все в порядке. Я оставляю будущим ученым развивать это.
PPS: Если вы работаете в Google, не стесняйтесь копировать и вставлять его во фреймворк и строить на нем.
PPPS: возможно, вы захотите использовать лучшую архитектуру приложения, например MVVM.