Я создаю интерфейс, похожий на интерфейс чата Google Hangouts. Новые сообщения добавляются в нижней части списка. Прокрутка вверх до списка приведет к загрузке предыдущей истории сообщений. Когда история поступает из сети, эти сообщения добавляются в верхнюю часть списка и не должны вызывать какой-либо прокрутки из положения, которое пользователь остановил при загрузке нагрузки. Другими словами, в верхней части списка отображается "индикатор загрузки":
который затем заменяется in-situ любой загруженной историей.
У меня все это работает... за исключением того, что мне приходилось прибегать к размышлениям. Существует множество вопросов и ответов, связанных с сохранением и восстановлением позиции прокрутки при добавлении элементов в адаптер, подключенный к ListView. Моя проблема в том, что когда я делаю что-то вроде следующего (упрощенный, но должен быть понятным):
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
Тогда то, что пользователь увидит, - это быстрая вспышка в верхней части ListView, а затем быстрый флэш обратно в нужное место. Проблема довольно очевидна и обнаруживается для многих людей: setSelection()
несчастлив только после notifyDataSetChanged()
и перерисовки ListView
. Поэтому нам нужно post()
видеть, чтобы дать ему шанс нарисовать. Но это выглядит ужасно.
Я "исправил" его, используя отражение. Я ненавижу это. По своей сути я хочу выполнить reset первую позицию ListView
, не пройдя через римарол цикла вытягивания, пока я не установил положение. Для этого есть полезное поле ListView: mFirstPosition
. По gawd, это именно то, что мне нужно настроить! К сожалению, это пакет-частный. К сожалению, похоже, что нет никакого способа установить его программным путем или повлиять на него каким-либо образом, который не включает цикл invalidate... уступает уродливое поведение.
Итак, отражение с отказом при отказе:
try {
Field field = AdapterView.class.getDeclaredField("mFirstPosition");
field.setAccessible(true);
field.setInt(listView, positionToSave);
}
catch (Exception e) { // CATCH ALL THE EXCEPTIONS </meme>
e.printStackTrace();
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
}
Это работает? Да. Это отвратительно? Да. Будет ли это работать в будущем? Кто знает? Есть ли способ лучше? Что мой вопрос.
Как это сделать без отражения?
Ответ может быть "написать свой собственный ListView
, который может справиться с этим". Я просто спрошу, есть ли у вас код для ListView
.
EDIT: рабочее решение без отражения на основе комментария/ответа Luksprog.
Luksprog рекомендовал OnPreDrawListener()
. Захватывающий! Раньше я сталкивался с ViewTreeObservers, но никогда не был одним из них. После некоторых беспорядков, следующий тип вещи, кажется, работает совершенно отлично.
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(listView.getFirstVisiblePosition() == positionToSave) {
listView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
else {
return false;
}
}
});
}
Очень круто.