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()方法,如下图。
3.解决方案
尝试过不设置OnItemClickListener,使用View的OnClick行不行?结果还是不行,OnClick事件,在手指按下到抬起的过程中如果更新了,抬起事件会变成cancel。
最后处理方案是:
- 自定义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;
}
}
- 重写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()的调用频率。
好记性不如烂笔头,谨以记之~