Android - ListView中的延迟点击

18

我在应用程序中有以下结构:

ViewPager包含多个由FragmentStatePagerAdapter管理的片段,使用Android 2.1兼容性包。

每个片段包含一个ListView。每个ListView元素都有一个包含两个TextViews和一个ButtonLinearLayoutLinearLayout和按钮分别有onClickListeners。点击LinearLayout将启动另一个Activity。 我发现点击行为非常不一致:有时会立即执行操作,但经常会延迟,有时无论我点击多少次都会被忽略。更奇怪的是,我可以轻点,但只有当我开始滚动列表时才会执行操作。 我尝试了各种setFocusable(false)setSelectable(true)的组合,但似乎没有任何区别。 有什么想法吗? 我很乐意提供更多细节。

6个回答

9
我有一个ListAdapter,为每个列表项在LinearLayout中创建几个TextViews。每个TextView都有自己的OnClickListener,因为我需要处理每个项目上的点击事件。当我更改实现方式以便重用视图时,OnClickListener就停止正常工作了。在4.4.2上,大多数点击都有效,但有时直到我在列表中滚动才会有反应。在2.3上,第一次点击将不起作用,然后所有点击都会突然被处理。在我的特殊情况下,我是通过Java代码创建所有视图而不是通过膨胀资源来完成的。而且关键点是,即使视图被重用,我也设置了LinearLayout的LayoutParams(这似乎比假定重用的视图具有正确的布局参数更安全)。当我在重用时不设置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。ListAdapter 的 getView 方法不仅被调用来绘制视图,而且还会在 onMeasure 期间提前调用。在这种情况下,视图已经创建,但尚未在事件链中注册以处理单击事件。
当为 onMeasure 创建的视图稍后被重用以实际绘制到屏幕上时,它必须从 Android 系统中注册以处理单击事件。对于这种特殊情况,Android 开发者做了一些被认为是肮脏的黑科技。LayoutParams 中的一个特殊标志用于确定视图必须在事件中心注册。
现在我的问题是:当重用视图时重置 LayoutParams 时,该标志总是被重置。因此,Android 系统将不会注册视图,事件也不会传递。
总之,在 ListAdapter 的 getView 中重用视图时,请勿覆盖 LayoutParams,因为它们保留了 Android 系统的内部信息。

我曾经遇到过同样的问题。在我的情况下,布局必须根据列表的数据源进行调整大小,因此第一次填充视图时,我在那里进行了布局参数更改,然后行项目上的点击功能再次正常工作。 - James andresakis
天啊,不可能吧,这就是我的全部!这太过时、晦涩难懂,简直是脑残。API是一份合同,但这个API并没有被遵守。我已经浪费了好几个小时在这上面,别让那个Google开发者靠近我。感谢你让我不至于因此而疯狂。 - Guy Moreillon

4
我遇到了同样的问题,但在我的情况下,解决方案不是保留对视图的引用,这会导致ListView的视图缓存出现问题。通过正确实现使用converView的getView()方法,所有关于丢失/意外点击调用的奇怪行为都消失了。

当我处理列表时,我总是在我的代码中使用处理程序模式。 - Bostone
+1 对我来说可行。出于某些原因,我一直保留着对我的一个视图的引用(然后该视图用于单个 ListView 行),这里不再赘述 - 这样做的结果是将所有点击操作延迟到活动被 重新启动 时,它们才会全部流过并导致大量异常。在适配器内创建视图可以解决此问题 - 非常奇怪。 - Dori

3

如果有人想知道我是如何解决这个问题的,基本上我必须简化我的布局。当您有复杂的嵌套结构时,事件可能需要太长时间才能冒泡,如果您同时开始滚动列表,事件可能会触发错误的操作。我通过尽可能地切换到RelativeLayout来修剪布局,并且似乎有了很大的帮助。


1

不确定这是否对任何人有帮助,但我遇到了一个类似的问题,是关于TableLayout的。上面的解决方案没有解决我的问题。

对我来说,问题在于: android:animateLayoutChanges="true"

删除此属性后,我的TableLayout行内的按钮点击才正常工作。然后我必须手动动画化我的视图,而不依赖于上述属性。


1
我的解决方法是通过setOnItemClickListenerOnItemClickListener分配给ListView,而不是将OnClickListener分配给单个列表项。显然,按钮仍然需要自己的OnClickListener,但我还没有测试过这种情况。

-1

看起来你正在事件线程中执行某些阻塞进程(例如调用Web服务或打开文件),因此你的事件线程被阻塞了。如果是这种情况,请将你的阻塞代码处理到另一个线程而不是事件线程。


我使用Loader,特别是AsyncLoader来执行所有后台进程。但我会重新评估代码,感谢这个提示! - Bostone
毕竟不是那个原因。看看我的回答。 - Bostone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接