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