Android — отложенные клики в ListView

У меня есть следующая структура в моем приложении:

FragmentActivity с ViewPager, содержащим несколько фрагментов, управляемых FragmentStatePagerAdapter с использованием пакета совместимости с Android 2.1

Каждый фрагмент содержит ListView. Каждый элемент в ListView имеет LinearLayout с двумя TextViews и Button. LinearLayout и кнопка имеют onClickListeners (отдельно). Нажатие на LinearLayout запускает другое Activity. Я заметил, что поведение кликов очень непоследовательно: иногда действие выполняется немедленно, но очень часто с задержкой, а иногда просто игнорируется, сколько бы раз я ни нажимал. Это становится еще более странным, потому что я могу нажать, и действие будет выполнено только тогда, когда я начну прокручивать список. Я пробовал различные комбинации setFocusable(false) и setSelectable(true), но, похоже, это не имеет никакого значения. Любые идеи? Я буду рад предоставить более подробную информацию.


person Bostone    schedule 11.01.2012    source источник


Ответы (6)


У меня была аналогичная проблема, и мне потребовалось 2 дня, чтобы отладить и решить ее. У меня есть ListAdapter, который создает несколько TextView в 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. Когда Android обновляет представление, есть два важных шага onMeasure и onLayout. Метод getView объекта ListAdapter вызывается не только для отрисовки представления, но и ранее во время onMeasure. В этом более позднем случае представление создается, но еще не зарегистрировано в цепочке событий для обработки событий кликов.

Когда представление, созданное для onMeasure, повторно используется позже для точного отображения на экране, оно должно быть зарегистрировано в системе Android для обработки событий кликов. Для этого особого случая разработчики Android сделали что-то, что можно считать грязным взломом. Специальный флаг в LayoutParams используется, чтобы решить, что представление должно быть зарегистрировано в изменении события.

Теперь моя проблема: при сбросе LayoutParams также при повторном использовании представления этот флаг всегда сбрасывался. Поэтому система Android не будет регистрировать представление, и события не будут проходить.

Подводя итог: при повторном использовании представления в getView ListAdapter не перезаписывайте LayoutParams, потому что они сохраняют внутреннюю информацию системы Android.

person user3599248    schedule 03.05.2014
comment
У меня такая же проблема. В моем случае размер макета должен был измениться в зависимости от источника данных для списка, поэтому при первом раздувании представления я изменил там свой layout.params, и клики по элементам строк снова работали нормально. - person James andresakis; 16.07.2014
comment
ОМ*Г, ни за что, это было для меня! Это оооочень отстало, неясно и совершенно мертво для мозга. API — это контракт, и он не соблюдается. Я потратил на это часы, не позволяйте этому разработчику Google приближаться ко мне. Спасибо, что не дал мне сойти с ума по этому поводу. - person Guy Moreillon; 16.09.2016

Я столкнулся с той же проблемой, но в моем случае решение заключалось в том, чтобы не сохранять ссылки на представления, что вызывало проблемы с кэшированием представлений ListView. После правильной реализации метода getView() с использованием converView все странное поведение с потерянными/неожиданными вызовами кликов исчезло.

person Fenix Voltres    schedule 23.03.2012
comment
Я всегда использую шаблон обработчика в своем коде при работе со списками. - person Bostone; 24.03.2012
comment
+1 у меня работает. Я сохранял ссылку на одно из своих представлений (которое затем использовалось для одной строки ListView) по причинам, которые я не буду вдаваться здесь - по какой-то причине это привело к задержке всех кликов по этому элементу до тех пор, пока активность не была перезапущен , где все они будут выполняться и вызывать множество исключений. создание представления внутри адаптера решает эту проблему - очень странно - person Dori; 21.03.2013

В случае, если кто-то задавался вопросом, как я решил это. В основном мне пришлось упростить мои макеты. Кажется, что когда у вас есть сложные вложенные структуры, события могут занять слишком много времени, и если вы начнете прокручивать список в то же время, событие может вызвать неправильное действие. Я обрезал макеты, максимально переключившись на RelativeLayout, и это, похоже, очень помогло.

person Bostone    schedule 07.02.2012

Что сработало для меня, так это назначение OnItemClickListener для ListView до setOnItemClickListener, а не OnClickListener для отдельных элементов списка. Очевидно, что кнопке по-прежнему нужен собственный OnClickListener, но я не проверял этот сценарий.

person Zeus    schedule 11.08.2012

Не уверен, что это кому-нибудь поможет, но у меня была аналогичная проблема с TableLayout. Приведенные выше решения не решили мою проблему.

Для меня проблема была: android:animateLayoutChanges="true"

Удаление этого позволило моим щелчкам кнопок в моих строках TableLayout работать правильно. Затем мне пришлось анимировать свои представления вручную вместо того, чтобы полагаться на вышеуказанное свойство.

person triad    schedule 17.04.2015

Кажется, вы выполняете какой-то блокирующий процесс (например, вызов веб-сервисов или открытие файлов) в потоке событий, поэтому ваш поток событий заблокирован. В этом случае обработайте код блокировки в другом потоке, а не в Event THread.

person jeet    schedule 11.01.2012
comment
Я использую загрузчики, в частности AsyncLoader, для выполнения всех моих фоновых процессов. Но я переоценю код, спасибо за подсказку! - person Bostone; 11.01.2012
comment
В конце концов, это не было причиной. Смотри мой ответ - person Bostone; 07.02.2012