Android列表刷新过快点击无响应处理

2,740 阅读2分钟

1:现象

ListView刷新频率高的时候(刷新频率1秒十多次),点击列表,没有触发OnItemClick事件。尝试了降低频率到5秒一次,就可以正常响应。

2.原因

以Android API 26源码为例,AbsListView.class 手指抬起事件如下图

有个延时任务mTouchModeReset,延时时间是固定的64毫秒。64毫秒之后执行了performClick.run(),如下图

正常情况下,performClick.run()就会执行performItemClick方法,回调到外面响应OnItemClick。

那么此时没有响应OnItemClick,是因为什么呢? 要执行performClick.run(),是有条件的。

if (!mDataChanged && !mIsDetaching && isAttachedToWindow) {
    performClick.run()
}

此时listview已经是正常展示在屏幕上了,后面两个参数不用考虑,就只有这个mDataChanged了,这个值为false的情况下,不会执行performClick.run()。那么这个值是什么时候变化的呢?

mDataChanged=false,这个只有两个地方,一个是resetList(),比如ListView setAdapter的时候就会调用;另一个地方就是ListView的layoutChildren(),在所有的子项全部完成布局展示之后。置为true的地方有onAttachedToWindow,invalidateViews,onFocusChanged等等,最主要的还是adapter.notifyDataSetChanged()方法,如下图。

adapter.notifyDataSetChanged()调用时,这个mDataChanged会被置为true,然后是ListView的layoutChildren()执行完成时,才会被置为false,如果你手指按下的时候,列表更新了,此时就不会响应OnItemClick事件了。而在列表刷新频率很高时,几乎就不会响应点击事件!体现在操作上就是:刷新频率越快,越难响应点击事件

3.解决方案

尝试过不设置OnItemClickListener,使用View的OnClick行不行?结果还是不行,OnClick事件,在手指按下到抬起的过程中如果更新了,抬起事件会变成cancel。

最后处理方案是:

  1. 自定义Adapter增加一个开关,用于开启和禁止刷新。
abstract class CanRefreshAdapter extends BaseAdapter {
    // 是否可刷新
    private boolean isCanRefresh = true;
    
    // 重写方法,改动小
    public void notifyDataSetChanged() {
        if (isCanRefresh) {
            super.notifyDataSetChanged();
        }
    }
    
    public void setCanRefresh(boolean canRefresh) {
        this.isCanRefresh = canRefresh;
    }
}
  1. 重写ListView的onTouch事件,控制刷新的开启和关闭
abstract class CanRefreshListView extends ListView {
    public void onTouch(MotionEvent v) {
        witch(ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (canRefreshAdapter != null) {
                    canRefreshAdapter.setCanRefresh(false);
                    resetListRefreshState();
                }
                break;
            case MotionEvent.ACTION_UP:
                resetListRefreshState();
                break;
        }
    }
    
    // 恢复列表可刷新状态,加延迟是因为按下到抬起需要至少64毫秒来响应OnItemClick时间,延迟时间可以自己调。
    private void resetListRefreshState() {
        postDelayed(() -> {
            if (canRefreshAdapter != null) {
                canRefreshAdapter.setCanRefresh(true);
                // 手动刷新一下列表,防止数据最后一次更新没有生效
                canRefreshAdapter.notifyDataSetChanged();
            }
        }, 500);
    }
}

4.小结

至此问题就解决了,目前上线之后也没有用户返回这个问题了。原理很简单,就只是限制了notifyDataSetChanged()的调用频率。

好记性不如烂笔头,谨以记之~