1、每个Item一个计时器,条目多的话,性能损耗太大;
2、单个计时器,然后遍历数据 刷新条目;
计时器两种实现方式:1、Handler轮询; 2、子线程睡眠(时间到后 移除列表中的条目会有问题);
源码地址:https://github.com/CuiChenbo/CountdownList
代码很简单,没有任何难度,列表使用 RecyclerView+BaseRecyclerViewAdapterHelper实现;
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.1'
上代码:
public class MainActivity extends AppCompatActivity {
private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rv = findViewById(R.id.rv);
initView();
initData();
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
quickAdapter.addData(0,new TimeBean("附加商品、离活动结束还剩:" , 99));
rv.scrollToPosition(0);
}
});
}
private QuickAdapter quickAdapter;
private void initView() {
quickAdapter = new QuickAdapter(R.layout.item);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setAdapter(quickAdapter);
rv.setItemAnimator(null);
Countdown();
}
private void initData() {
List<TimeBean> datas = new ArrayList<>();
for (int i = 1; i < 10; i++) {
datas.add(new TimeBean("商品" + i + "、离活动结束还剩:", (i + 5) * i));
}
quickAdapter.setNewData(datas);
}
private class QuickAdapter extends BaseQuickAdapter<TimeBean, BaseViewHolder> {
public QuickAdapter(int layoutResId) {
super(layoutResId);
}
@Override
protected void convert(BaseViewHolder vh, TimeBean datas) {
vh.setText(R.id.tv, datas.getStr() + "");
vh.setText(R.id.tv2, datas.getTime() + "s");
}
}
private Handler mHandler = new Handler();
private Runnable runnable;
private void Countdown() {
runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < quickAdapter.getData().size(); i++) {
TimeBean bean = quickAdapter.getData().get(i);
if (bean.getTime() > 0) {
bean.setTime(bean.getTime() - 1);
quickAdapter.setData(i, bean);
} else {
quickAdapter.remove(i);
}
}
mHandler.postDelayed(runnable, 1000L);
}
};
mHandler.postDelayed(runnable, 1000L);
}
private void Countdown2() {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
SystemClock.sleep(1000L);
for (int i = 0; i < quickAdapter.getData().size(); i++) {
final TimeBean bean = quickAdapter.getData().get(i);
final int finalI = i;
if (bean.getTime() > 0) {
bean.setTime(bean.getTime() - 1);
runOnUiThread(new Runnable() {
@Override
public void run() {
quickAdapter.setData(finalI, bean);
}
});
} else {
// 当时间是0时 移除条目(子线程加睡眠模式移除条目有问题,原因时数据源未更新)
runOnUiThread(new Runnable() {
@Override
public void run() {
quickAdapter.remove(finalI);
}
});
}
}
}
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(runnable);
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
public class TimeBean {
public TimeBean(String str, int time) {
this.str = str;
this.time = time;
}
private String str;
private int time;
}
性能优化
如果看了上面的代码可以发现,功能是实现了但是没有动画效果,因为rv.setItemAnimator(null) 屏蔽了RecyclerView的动画;不屏蔽动画的话,每次刷新条目都会闪一下;闪一下的原因是条目重新绘制了UI;如果条目中的控件特别多或者有图片的情况下,用户体验会更加不好;
还想要动画、还想刷新时不闪动;那么它来了!!!根据id刷新单个控件
刷新RecyclerView条目的指定控件:
看一波源码:payload 参数的解释是 传 null 代表刷新整个条目;
/*
* @param position Position of the item that has changed
* @param payload Optional parameter, use null to identify a "full" update
*
* @see #notifyItemRangeChanged(int, int)
*/
public final void notifyItemChanged(int position, @Nullable Object payload) {
mObservable.notifyItemRangeChanged(position, 1, payload);
}
/*
* @param positionStart Position of the first item that has changed
* @param itemCount Number of items that have changed
* @param payload Optional parameter, use null to identify a "full" update
*
* @see #notifyItemChanged(int)
*/
public final void notifyItemRangeChanged(int positionStart, int itemCount,
@Nullable Object payload) {
mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
}
可以传递控件的id来刷新单个控件,例:
adapter.notifyItemChanged(position); //刷新单个条目
替换为下面的 ↓
adapter.notifyItemChanged(position, R.id.***); //刷新单个条目中的指定id的控件
adapter.notifyItemRangeChanged(position, ItemCount , R.id.***); //刷新多条目中的指定id的控件
好,上代码:
public class GoodListActivity extends AppCompatActivity {
private RecyclerView rv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_good_list);
rv = findViewById(R.id.rv);
initView();
initData();
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mAdapter.addData(0,new TimeBean("附加商品、离活动结束还剩:" , 99));
rv.scrollToPosition(0);
}
});
findViewById(R.id.btnGood).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
private ListAdapter mAdapter;
private void initView() {
mAdapter = new ListAdapter(this,R.layout.item);
rv.setLayoutManager(new LinearLayoutManager(this));
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(mAdapter);
Countdown();
}
private void initData() {
List<TimeBean> datas = new ArrayList<>();
for (int i = 1; i < 10; i++) {
datas.add(new TimeBean("商品" + i + "、离活动结束还剩:", (i + 5) * i));
}
mAdapter.setNewData(datas);
}
private class ListAdapter extends BaseRecyclerAdapter<TimeBean>{
public ListAdapter(Context context, int layoutRes) {
super(context, layoutRes);
}
@Override
public void convert(BaseRecyclerHolder holder, List<TimeBean> items, int position) {
((TextView)holder.getView(R.id.tv)).setText(items.get(position).getStr());
((TextView)holder.getView(R.id.tvTime)).setText(items.get(position).getTime()+"s");
}
}
private Handler mHandler = new Handler();
private Runnable runnable;
private void Countdown() {
runnable = new Runnable() {
@Override
public void run() {
List<TimeBean> deleteDatas = null; //记录需要删除的条目
for (int i = 0; i < mAdapter.getDatas().size(); i++) {
TimeBean bean = mAdapter.getDatas().get(i);
if (bean.getTime() > 0) {
bean.setTime(bean.getTime() - 1);
mAdapter.getDatas().set(i, bean); //时间未到的 ,只改变数据,暂不刷新条目
} else {
if (deleteDatas == null)
deleteDatas = new ArrayList<>();
deleteDatas.add(bean); //把需要删除的条目暂存起来,避免边遍历边操作集合;
}
}
if (deleteDatas != null && deleteDatas.size() != 0) { //删除条目
for (int i = 0; i < deleteDatas.size(); i++) {
mAdapter.remove(deleteDatas.get(i));
}
}
mAdapter.notifyItemRangeChanged(0, mAdapter.getItemCount(), R.id.tvTime); //刷新条目中的指定控件
mHandler.postDelayed(runnable, 1000L);
}
};
mHandler.postDelayed(runnable, 1000L);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacks(runnable);
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
}
重点就是这一段:
List<TimeBean> deleteDatas = null; //记录需要删除的条目
for (int i = 0; i < mAdapter.getDatas().size(); i++) {
TimeBean bean = mAdapter.getDatas().get(i);
if (bean.getTime() > 0) {
bean.setTime(bean.getTime() - 1);
mAdapter.getDatas().set(i, bean); //时间未到的 ,只改变数据,暂不刷新条目
} else {
if (deleteDatas == null)
deleteDatas = new ArrayList<>();
deleteDatas.add(bean); //把需要删除的条目暂存起来,避免边遍历边操作集合;
}
}
if (deleteDatas != null && deleteDatas.size() != 0) { //删除条目
for (int i = 0; i < deleteDatas.size(); i++) {
mAdapter.remove(deleteDatas.get(i));
}
}
mAdapter.notifyItemRangeChanged(0, mAdapter.getItemCount(), R.id.tvTime); //刷新条目中的指定控件
好啦!BaseRecyclerAdapter的地址:https://blog.csdn.net/qq_35605213/article/details/80176558