Если ваше приложение загружает данные из Интернета, отображает их в ListView и обрабатывает щелчки по ячейкам, вы можете продолжить чтение.
Это история о том, как можно рисовать за 64 мс после нажатия на ячейку списка.
У нас был обычный список, в котором было 2 типа ячеек: некликабельные категории и кликабельные ячейки.
Случайное изображение с подкатегориями
Адаптер, который мы использовали, можно увидеть здесь: github.com/siyusong/foodtruck-master-android/blob/master/src/com/foodtruckmaster/android/adapter/SeparatedListAdapter.java
Данные загружались с сервера, отображались в ListView, а при нажатии на ячейку открывался отдельный экран с подробным описанием.
Для обработки кликов мы использовали AdapterView.OnItemClickListener. Наши адаптеры в getItem возвращали объекты, которые были переданы на подробные экраны.
Обработка кликов производилась так:
Сбои ClassCastException(String -> Описание) начали появляться в Crashlytics. Это означало, что некликабельные подзаголовки в списках все равно кликались и вместо объекта «Описание» мы получали строку.public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Description desc = parent.getItemAtPosition(position); DescriptionActivity.open(context, desc); }
Щелкать по некликабельным ячейкам можно с помощью PerformItemClick, но мы такие методы не использовали и были сбои на всех экранах со списками и подзаголовками, хотя их было всего несколько.
Далее углубимся в исходники 4.2.2
AbsListView, метод onTouchEvent case MotionEvent.ACTION_UP: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
case TOUCH_MODE_TAP:
case TOUCH_MODE_DONE_WAITING:
.
final AbsListView.PerformClick performClick = mPerformClick; .
if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) { .
if (mTouchModeReset != null) { removeCallbacks(mTouchModeReset); } mTouchModeReset = new Runnable() { @Override public void run() { mTouchMode = TOUCH_MODE_REST; child.setPressed(false); setPressed(false); if (!mDataChanged) { performClick.run(); } } }; if (!mDataChanged && mAdapter.isEnabled(motionPosition)) { .
postDelayed(mTouchModeReset, ViewConfiguration.getPressedStateDuration()); } .
return true; } .
}
В исходники Android без пива лучше не лезть, видимо разработчики ОС руководствовались тем же принципом.
Здесь мы видим, что если мы щелкнули по ячейке списка и она включена, то через определенный интервал мы вызываем PefrormClick. В андроиде 4.2.2 этот интервал составляет 64 мс.
Вот как выглядит Runnable PerformClick private class PerformClick extends WindowRunnnable implements Runnable {
int mClickMotionPosition;
public void run() {
// The data has changed since we posted this action in the event queue,
// bail out before bad things happen
if (mDataChanged) return;
final ListAdapter adapter = mAdapter;
final int motionPosition = mClickMotionPosition;
if (adapter != null && mItemCount > 0 &&
motionPosition != INVALID_POSITION &&
motionPosition < adapter.getCount() && sameWindow()) {
final View view = getChildAt(motionPosition - mFirstPosition);
// If there is no view, something bad happened (the view scrolled off the
// screen, etc.) and we should cancel the click
if (view != null) {
performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
}
}
}
}
Этот исполняемый объект вызывает PerformItemClick, где уже вызван наш OnItemClickListener. Видим, что если данные в адаптере изменились, то скачиваем их.
Проверяем границы адаптера и так далее.
Самое интересное, что если вы установите новый адаптер, а не меняете данные в старом, то mDataChanged будет равно false, также стоит отметить отсутствие проверки ячеек isEnabled. Те.
нажимаем на ячейку, меняем адаптер в течение 64 мс, этот исполняемый файл выполняется и в результате клик происходит не по тем данным, которые мы видели на телефоне, а по новым.
Причём, если в новом адаптере у ячейки isEnabled=false, то по ней всё равно будет клик, вызовется onItemClickListener.
Итак, в строке: Description desc = parent.getItemAtPosition(position);
мы чудесным образом получили исключение ClassCastException
Решение: очевидно, это ошибка ОС, и самым простым решением будет установка флага mDataChanged в значение true или очистка очереди сообщений при смене адаптера.
Заключение: Если вы кликнули по ячейке, и в этот момент новые данные были скачаны с сервера и установлены в список, то вы кликнули по новым данным (если вы создали адаптер заново).
Всегда проверяйте результат метода getItemAtPosition на наличие нуля и экземпляра, если у вас есть несколько типов ячеек и объектов элементов.
Теги: #Android #Разработка Android
-
О Компании Веб-Решений
19 Oct, 24 -
Торренты, Skype И Безопасность
19 Oct, 24 -
Как Пройти Собеседование У It-Специалистов
19 Oct, 24