本文微信公众号「AndroidTraveler」首发。java
在 Android 列表开发过程当中,有时候咱们的 Item 会有一些组件,好比倒计时。这类组件要求不断刷新,这个时候因为列表复用的机制,所以会有一些坑。那么咱们本篇文章就给你们讲两个主题。git
第一个是列表复用是否必定有问题。 第二个是出现问题有哪些解决方案可供咱们选择。github
因为咱们的主题重点是为了解决不断刷新问题,所以关于 RecyclerView 的基本使用就再也不赘述,不清楚的小伙伴能够看下我以前的文章:
RecyclerView基本使用apache
首先咱们看下效果图:微信
很简单,就是一个 RecyclerView 列表,列表项有两个组件。分别表明第几项和剩余秒数。ide
这里就是经过倒计时来演示刷新可能存在的问题。函数
重点代码是 Adapter 里面的显示逻辑,初始为:post
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
private void updateTime(final RecyclerViewViewHolder holder, final long time) {
String content;
long remainTime = time - System.currentTimeMillis();
remainTime /= 1000;
if (remainTime <= 0) {
content = "Time up";
holder.mTxtTitle.setText(content);
return;
}
content = "剩下"+remainTime+"秒";
holder.mTxtTitle.setText(content);
}
复制代码
所有代码见:github.com/nesger/Recy…优化
接下来咱们增长刷新方法,有不少种,咱们一一说明。ui
修改显示代码,以下:
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
private void updateTime(final RecyclerViewViewHolder holder, final long time) {
String content;
long remainTime = time - System.currentTimeMillis();
remainTime /= 1000;
if (remainTime <= 0) {
content = "Time up";
holder.mTxtTitle.setText(content);
return;
}
content = "剩下"+remainTime+"秒";
holder.mTxtTitle.setText(content);
holder.mTxtTitle.postDelayed(new Runnable() {
@Override
public void run() {
updateTime(holder, time);
}
}, 1000);
}
复制代码
能够看到经过 handler 延时一秒,而后每次更新时间也是减小一秒。
咱们看下效果图:
能够看到没滚动以前还好,滚动以后会发现,倒计时都乱了。
固然有时候可能不会暴露出来,好比滚动数目少,或者只有部分组件有倒计时,不像咱们这个例子,全部项目都有倒计时,可是这也间接留下了可能的坑。
出现这个问题的缘由在于组件的复用,若是你用 ListView 演示,而且不用复用,那么是不会错乱的。
固然列表不复用这个确定是不推荐的。
所以,该方式不推荐。
所有代码见:github.com/nesger/Recy…
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
private void updateTime(final RecyclerViewViewHolder holder, final long time) {
String content;
long remainTime = time - System.currentTimeMillis();
remainTime /= 1000;
if (remainTime <= 0) {
content = "Time up";
holder.mTxtTitle.setText(content);
return;
}
content = "剩下"+remainTime+"秒";
holder.mTxtTitle.setText(content);
}
复制代码
同样不行,不推荐。
所有代码见:github.com/nesger/Recy…
其实咱们简单分析一下就知道,出现上面错乱状况的缘由大体是两个:一个是复用,一个是代码屡次调用。
因此若是可以解决这两个问题,那么这个问题就解决了。
由于咱们这里的业务是倒计时监听,全部 View 都是同样的,就是一秒更新一次。
因此咱们的定时器不须要 N 个,只须要一个,在构造函数初始化便可。
另外为了不复用和代码屡次调用问题,咱们将 View 经过一个集合保存起来。
最后修改的代码以下:
private Timer mTimer;
private Set<RecyclerViewViewHolder> mHolders;
public RecyclerViewAdapter(Activity activity, List<Long> itemList) {
if (activity == null || itemList == null) {
throw new IllegalArgumentException("params can't be null");
}
this.activity = activity;
this.itemList = itemList;
mHolders = new HashSet<>();
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
for (RecyclerViewViewHolder holder : mHolders) {
updateTime(holder, holder.getTime());
}
}
}, 0, 1000);
}
@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
holder.setTime(itemList.get(position));
mHolders.add(holder);
holder.mTvNum.setText(String.valueOf(position + 1));
updateTime(holder, itemList.get(position));
}
复制代码
效果图以下:
能够看到没问题了。
固然这里有些优化还没处理,由于本篇主要是思路分析,这里就不添加了。
待优化点:定时器的启动和关闭跟生命周期关联,无数据源不启用定时器等。
所有代码见:github.com/nesger/Recy…
该方法来自与一名朋友的分享。
这边 AndroidStudio 有安装阿里巴巴提供的一个代码检测插件,连接为:plugins.jetbrains.com/plugin/1004…
在 AndroidStudio 输入插件名字 Alibaba Java Coding Guidelines 查找安装便可。
在方法 3 使用 Timer 时提示下面信息:
Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions.
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//do something
}
},initialDelay,period, TimeUnit.HOURS);
复制代码
因此咱们这里修改 Timer 为 ScheduledExecutorService:
private ScheduledExecutorService mExecutorService;
public RecyclerViewAdapter(Activity activity, List<Long> itemList) {
if (activity == null || itemList == null) {
throw new IllegalArgumentException("params can't be null");
}
this.activity = activity;
this.itemList = itemList;
mHolders = new HashSet<>();
mExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread thread = new Thread(r);
thread.setName("countdown");
return thread;
}
});
mExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
for (RecyclerViewViewHolder holder : mHolders) {
updateTime(holder, holder.getTime());
}
}
}, 0, 1000, TimeUnit.MILLISECONDS);
}
复制代码
所有代码见:github.com/nesger/Recy…
有更多方法欢迎到上面的 GitHub 连接提 PR,能够基于 feature/refresh 分支新建分支。
有另一位朋友提出了自定义 View 的处理方式,将倒计时的功能放到 View 里面去处理,这个感兴趣的小伙伴能够实现而后提 PR 哈,这里提供额外一种思路。