Android uses RecyclerView to achieve list countdown

Keywords: Android

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

Source code


If you have any questions, please contact us in time.

Posted by lovasco on Mon, 09 Dec 2019 16:13:27 -0800