Android - отложенные клики в ListView - PullRequest
18 голосов
/ 11 января 2012

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

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

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

Ответы [ 6 ]

9 голосов
/ 03 мая 2014

У меня была похожая проблема, и мне потребовалось 2 дня, чтобы отладить и решить ее. У меня есть ListAdapter, который создает несколько TextViews в LinearLayout для каждого элемента списка. Каждый TextView имеет свой собственный OnClickListener, потому что мне нужно обрабатывать клики по каждому элементу.

Когда я изменил реализацию для повторного использования Views, OnClickListener перестал работать правильно. На 4.4.2 большинство нажатий работало, но иногда не было реакции, пока я не прокрутил список. На 2.3 первые клики не будут работать, а затем все клики, где обрабатывается в серии.

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

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

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

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

4 голосов
/ 23 марта 2012

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

3 голосов
/ 07 февраля 2012

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

1 голос
/ 18 апреля 2015

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

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

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

1 голос
/ 11 августа 2012

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

0 голосов
/ 11 января 2012

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

...