Recently, during the interview, the interviewer asked how to achieve the countdown effect of a list, and then his head suddenly went to O(∩∩∩∩∩∩∩∩∩∩∩∩∩∩∩∩∩.
Operation rendering
Implementation ideas
There are two main implementation methods:
1. Start a timer for each count down item, and then update the item;
2. Start only one timer, traverse the data, and then update the item.
After thinking about the performance and implementation, we decided to use the second way.
Implementation process
- Data entity
/** * Total countdown time (end time start time), in milliseconds * Example: the number of milliseconds between 11:00:30 and 11:00:00 on February 23, 2019 */ private long totalTime; /** * Whether the countdown is paused */ private boolean isPause = true;
-
Count down
Timer
mTimer.schedule(mTask, 0, 1000);
TimerTask
class MyTask extends TimerTask { @Override public void run() { if (mList.isEmpty()) { return; } int size = mList.size(); CountDownTimerBean bean; long totalTime; for (int i = 0; i < size; i++) { bean = mList.get(i); if (!bean.isPause()) {//Not paused totalTime = bean.getTotalTime() - 1000; if (totalTime <= 0) { bean.setPause(true); bean.setTotalTime(0); } bean.setTotalTime(totalTime); Message message = mHandler.obtainMessage(1); message.arg1 = i; mHandler.sendMessage(message); } } } }
- Thread interaction update item
mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: notifyItemChanged(msg.arg1, "update-time"); break; } } };
-
Performance optimization
1. After calling the notifyItemChanged() method, do not update the entire item (for example, the item contains pictures and does not need to be changed), so rewrite the onbindviewholder (holder, int, list)
@Override public void onBindViewHolder(@NonNull Holder holder, int position, @NonNull List<Object> payloads) { if (payloads.isEmpty()) { onBindViewHolder(holder, position); return; } //To update a control, for example, you only need to update the time information, and others do not need to be moved CountDownTimerBean bean = mList.get(position); long day = bean.getTotalTime() / (1000 * 60 * 60 * 24); long hour = (bean.getTotalTime() / (1000 * 60 * 60) - day * 24); long min = ((bean.getTotalTime() / (60 * 1000)) - day * 24 * 60 - hour * 60); long s = (bean.getTotalTime() / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60); holder.tvTime.setText("Remaining time: " + day + "day" + hour + "hour" + min + "branch" + s + "second"); holder.btnAction.setText(bean.isPause() ? "start" : "suspend"); holder.btnAction.setEnabled(bean.getTotalTime() != 0); }
2. Destroy resources:
/** * Destruction of resources */ public void destroy() { mHandler.removeMessages(1); if (mTimer != null) { mTimer.cancel(); mTimer.purge(); mTimer = null; } }
- RecyclerView.Adapter part of the source code
public class CountDownTimerAdapter extends RecyclerView.Adapter<CountDownTimerAdapter.Holder> { private static final String TAG = "CountDownTimerAdapter->"; private List<CountDownTimerBean> mList;//data private Handler mHandler;//Thread scheduling for updating lists private Timer mTimer; private MyTask mTask; public CountDownTimerAdapter() { mList = new ArrayList<>(); mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: notifyItemChanged(msg.arg1, "update-time"); break; } } }; mTask = new MyTask(); } public void bindAdapterToRecyclerView(@NonNull RecyclerView view) { view.setAdapter(this); } /** * Set up a new data source * * @param list data */ public void setNewData(@NonNull List<CountDownTimerBean> list) { destroy(); mList.clear(); mList.addAll(list); notifyDataSetChanged(); if (mTimer == null) { mTimer = new Timer(); } mTimer.schedule(mTask, 0, 1000); } /** * Destruction of resources */ public void destroy() { mHandler.removeMessages(1); if (mTimer != null) { mTimer.cancel(); mTimer.purge(); mTimer = null; } } @NonNull @Override public Holder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_count_down_timer, viewGroup, false); return new Holder(view); } @Override public void onBindViewHolder(@NonNull Holder holder, int position, @NonNull List<Object> payloads) { if (payloads.isEmpty()) { onBindViewHolder(holder, position); return; } //To update a control, for example, you only need to update the time information, and others do not need to be moved CountDownTimerBean bean = mList.get(position); long day = bean.getTotalTime() / (1000 * 60 * 60 * 24); long hour = (bean.getTotalTime() / (1000 * 60 * 60) - day * 24); long min = ((bean.getTotalTime() / (60 * 1000)) - day * 24 * 60 - hour * 60); long s = (bean.getTotalTime() / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60); holder.tvTime.setText("Remaining time: " + day + "day" + hour + "hour" + min + "branch" + s + "second"); holder.btnAction.setText(bean.isPause() ? "start" : "suspend"); holder.btnAction.setEnabled(bean.getTotalTime() != 0); } @Override public void onBindViewHolder(@NonNull final Holder holder, int position) { holder.ivIcon.setImageResource(R.mipmap.ic_launcher_round); final CountDownTimerBean bean = mList.get(position); long day = bean.getTotalTime() / (1000 * 60 * 60 * 24); long hour = (bean.getTotalTime() / (1000 * 60 * 60) - day * 24); long min = ((bean.getTotalTime() / (60 * 1000)) - day * 24 * 60 - hour * 60); long s = (bean.getTotalTime() / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60); holder.tvTime.setText("Remaining time: " + day + "day" + hour + "hour" + min + "branch" + s + "second"); holder.btnAction.setText(bean.isPause() ? "start" : "suspend"); holder.btnAction.setEnabled(bean.getTotalTime() != 0); holder.btnAction.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (bean.isPause()) { bean.setPause(false); holder.btnAction.setText("suspend"); } else { bean.setPause(true); holder.btnAction.setText("start"); } } }); } @Override public int getItemCount() { return mList.size(); } class Holder extends RecyclerView.ViewHolder { private ImageView ivIcon; private TextView tvTime; private Button btnAction; Holder(@NonNull View itemView) { super(itemView); ivIcon = itemView.findViewById(R.id.iv_icon); tvTime = itemView.findViewById(R.id.tv_time); btnAction = itemView.findViewById(R.id.btn_action); } } class MyTask extends TimerTask { @Override public void run() { if (mList.isEmpty()) { return; } int size = mList.size(); CountDownTimerBean bean; long totalTime; for (int i = 0; i < size; i++) { bean = mList.get(i); if (!bean.isPause()) {//Not paused totalTime = bean.getTotalTime() - 1000; if (totalTime <= 0) { bean.setPause(true); bean.setTotalTime(0); } bean.setTotalTime(totalTime); Message message = mHandler.obtainMessage(1); message.arg1 = i; mHandler.sendMessage(message); } } } } }
Project address
If you have any questions, please contact us in time.