Android - отложенные клики в ListView
У меня есть следующая структура в моем приложении:
FragmentActivity с ViewPager
, содержащим несколько фрагментов, управляемых FragmentStatePagerAdapter
с использованием пакета совместимости с Android 2.1
Каждый фрагмент содержит ListView
. Каждый элемент в ListView
имеет LinearLayout
с двумя TextViews
и a Button
. LinearLayout
и кнопка имеют onClickListeners
(отдельно). При нажатии на LinearLayout
начинается еще один Activity
. Я заметил, что поведение кликов очень противоречиво: иногда действие выполняется немедленно, но очень часто оно задерживается, а иногда оно просто игнорируется независимо от того, сколько раз я нажимаю. Он становится еще более странным, потому что я могу нажать, и действие будет выполнено только при запуске прокрутки списка. Я пробовал различные комбинации setFocusable(false)
и setSelectable(true)
, но, похоже, это не имеет никакого значения. Есть идеи? Я буду рад предоставить более подробную информацию.
Ответы
Ответ 1
В случае, если кто-нибудь задается вопросом, как я это решил. В основном мне приходилось упрощать мои макеты. Кажется, что когда у вас сложная вложенная структура, события могут занять слишком много времени, и если вы начнете прокручивать список, то событие может вызвать неправильное действие. Я урезал макеты, переключившись на RelativeLayout как можно больше, и это, казалось, помогло много.
Ответ 2
У меня была аналогичная проблема, и мне потребовалось 2 дня для ее отладки и решения.
У меня есть ListAdapter, который создает несколько TextViews в LinearLayout для каждого элемента списка.
Каждый TextView имеет собственный OnClickListener, потому что мне нужно обрабатывать клики по каждому элементу.
Когда я изменил реализацию, чтобы повторно использовать представления, OnClickListener перестает работать правильно. На 4.4.2 большинство кликов работало, но иногда не было никакой реакции, пока я не прокрутил список. В 2.3 первые клики не будут работать, а затем все клики, которые обрабатываются в пакете.
В моем специальном случае я создал все представление в Java-коде, а не путем раздувания ресурсов.
И решающим моментом было то, что я установил LayoutParams LinearLayout, даже когда представление было повторно использовано (это кажется более безопасным, а затем предполагая, что повторно используемое представление имеет правильные параметры макета).
Когда я не устанавливаю LayoutParams при повторном использовании, все работает отлично!
Вот критический код:
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout tapeLine = null;
if (convertView != null && convertView instanceof LinearLayout && ((LinearLayout)convertView).getChildCount() == 4) tapeLine = (LinearLayout) convertView; // Reuse view
else tapeLine = new LinearLayout(activity);
if (convertView == null) { // Don't set LayoutParams when reusing view
ViewGroup.LayoutParams tapeLineLayoutParams = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tapeLine.setLayoutParams(tapeLineLayoutParams);
}
ScrollingTape scrollingTape = calculatorHolder.getCalculator().getScrollingTape();
int tapeWidthPx = parent.getWidth();
TapeLineTextSizeInfo tapeLineTextSizeInfo = calculatorHolder.getTapeLineTextSizeHelper().createTapeLineTextSizeInfo(tapeWidthPx);
ScrollingTapeLine line = scrollingTape.getLine(position);
tapeLine.setOrientation(LinearLayout.HORIZONTAL);
int tapeBackgroundColor = getBackgroundColor(line);
tapeLine.setBackgroundColor(tapeBackgroundColor);
addColumnViews(tapeLine, line, tapeLineTextSizeInfo);
tapeLine.setTag(R.id.scrollingtapeadapter_viewtag_position, position);
tapeLine.setOnLongClickListener(longClickListener);
tapeLine.setOnClickListener(remainClickListener);
return tapeLine;
}
Какова предыстория этого странного поведения в представлении списка?
Я немного отлаживал и исследовал источники в Android.
Когда андроид обновляет представление, есть два важных шага onMeasure и onLayout.
Метод getView в ListAdapter призван не только рисовать представление, но и раньше во время onMeasure. В этом более позднем случае представление создается, но оно еще не зарегистрировано в цепочке событий для обработки событий щелчка.
Когда представление, которое было создано для onMeasure, повторно используется позже, чтобы быть нарисованным на экране, оно должно быть зарегистрировано в системе Android для обработки событий щелчка.
Для этого особого случая разработчики Android сделали что-то, что можно было бы назвать грязным взломом. Специальный флаг в LayoutParams используется для определения того, что представление должно быть зарегистрировано при изменении события.
Теперь моя проблема: сбросив LayoutParams также при повторном использовании представления, этот флаг всегда был reset. Поэтому система Android не будет регистрировать представление, и события не пройдут.
чтобы подвести итог: при повторном представлении представления в getView в ListAdapter не перезаписывайте LayoutParams, потому что они сохраняют внутреннюю информацию системы Android.
Ответ 3
Я столкнулся с одной и той же проблемой, но в моем случае решение заключалось не в том, чтобы сохранить ссылки на представления, что вызвало проблемы с кешированием ListView. После правильного внедрения метода getView()
с использованием convertView все странное поведение с потерянными/неожиданными щелчками мыши исчезло.
Ответ 4
Что сработало для меня, было назначение OnItemClickListener
ListView
через setOnItemClickListener
, а не OnClickListener
для отдельных элементов списка. Очевидно, что кнопка все еще нуждается в ее OnClickListener
, но я не тестировал этот сценарий.
Ответ 5
Не уверен, что это кому-то помогает, но вместо этого у меня была аналогичная проблема с TableLayout. Вышеупомянутые решения не устранили мою проблему.
Для меня проблема была:
android:animateLayoutChanges="true"
Удаление этого разрешено, чтобы моя кнопка нажала на мои строки TableLayout для правильной работы. Затем мне пришлось анимировать мои взгляды вручную вместо того, чтобы полагаться на вышеприведенное свойство.
Ответ 6
Кажется, что вы выполняете какой-то процесс блокировки (например, вызываете веб-сервисы или открываете файлы) в потоке событий, поэтому поток событий заблокирован. Если это так, пожалуйста, обработайте свой блокирующий код в другой поток, кроме события THread.