Recently, I received a request. I need to set the countdown effect when the expiration time of the coupon in the list remains two days. I think it shouldn't be a problem when I get the demand.
There are two main methods to realize the countdown: 1. Set a timer for each item that starts the countdown, and then update the item; 2. Only start a timer, then traverse the data, and then update the item.
Since the previous countdown function has been encapsulated and used the CountDownTimer class, I choose the first method to implement it. It starts directly, and the countdown effect of the list is realized after a wave of operation. The following figure is a demo of the simulation effect, an informal project, as shown in the figure:

The implementation process is relatively smooth. The use of CountDownTimer class also perfectly solves the problem of time confusion of different items caused by item reuse in RecyclerView. I thought it was implemented in this way. In terms of function, it is indeed implemented. However, after exiting the page, it is found that the printed log is still running, which indicates that we did not cancel when exiting, This is the memory problem. Let's see how to solve it!
Here is a middle page. Click the button and jump to the countdown page. It is mainly to simulate whether you are still running in the background without canceling after exiting the page. Let's take a look at the main code.
Code implementation steps:
1. Analog data
The simulation data uses the data of the last 20 days of the current time, and the structure type is also datetime
//Analog data, structure type 2021-12-11 15:28:23 List<String> dataList = new ArrayList<>(); //Gets the month and day of the current time Calendar now = Calendar.getInstance(); int currentYear = now.get(Calendar.YEAR); int currentMonth = now.get(Calendar.MONTH) + 1; int currentDay = now.get(Calendar.DAY_OF_MONTH); for (int i = 0; i < 20; i++) { if ((currentDay + i) < CommonUtils.getCurrentMonthLastDay()) { dataList.add(currentYear + "-" + currentMonth + "-" + (currentDay + i) + " 23:59:" + i); } else { dataList.add(currentYear + "-" + (currentMonth + 1) + "-" + (currentDay + i - CommonUtils.getCurrentMonthLastDay()) + " 23:59:" + i); } } recycler_view.setAdapter(new TimeOutAdapter(this, dataList));
2. TimeOutAdapter class implemented by countdown function
class TimeOutAdapter extends RecyclerView.Adapter<TimeOutAdapter.ViewHolder> { private Context mContext; private List<String> dataList; private SparseArray<CountDownTimerUtils> countDownMap = new SparseArray<>(); public TimeOutAdapter(Context context, List<String> dataList) { this.mContext = context; this.dataList = dataList; } /** * Empty resources */ public void cancelAllTimers() { if (countDownMap == null) { return; } for (int i = 0; i < countDownMap.size(); i++) { CountDownTimerUtils cdt = countDownMap.get(countDownMap.keyAt(i)); if (cdt != null) { cdt.cancel(); } } } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.time_out_view, parent, false); ViewHolder viewHolder = new ViewHolder(view); return viewHolder; } @Override public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { if (dataList != null && dataList.size() != 0) { holder.text_content.setText(dataList.get(position)); try { //Converts the data to milliseconds, where CommonUtils is the tool class long residueTime = CommonUtils.residueTimeout(dataList.get(position)); //Set when time is greater than 0 if (residueTime > 0) { //Where CountDownTimerUtils is the tool class for countdown holder.countDownTimer = CountDownTimerUtils.getCountDownTimer() .setMillisInFuture(residueTime) .setCountDownInterval(1000) .setTickDelegate(new CountDownTimerUtils.TickDelegate() { @Override public void onTick(long pMillisUntilFinished) { Log.i("TAG==", "==Output data update==" + pMillisUntilFinished); //Update data holder.text_content.setText(CommonUtils.stampToDate(pMillisUntilFinished)); } }) .setFinishDelegate(new CountDownTimerUtils.FinishDelegate() { @Override public void onFinish() { //Countdown complete } }); holder.countDownTimer.start(); //Set the hashcode of item as the key in SparseArray countDownMap.put(holder.text_content.hashCode(), holder.countDownTimer); } } catch (Exception e) { e.printStackTrace(); } } } @Override public int getItemCount() { return dataList.size(); } public class ViewHolder extends RecyclerView.ViewHolder { private final TextView text_content; CountDownTimerUtils countDownTimer; public ViewHolder(@NonNull View itemView) { super(itemView); text_content = itemView.findViewById(R.id.text_content); } } }
Here, we can notice the cancelAllTimer() method, which is used to solve the memory problem.
Through the following line of code, set the hashcode in the item as the key into SparseArray, so that you can traverse it in the cancelAllTimer method to cancel the countdown.
countDownMap.put(holder.text_content.hashCode(), holder.countDownTimer);
3. Call the cancelAllTimer() method to cancel when exiting the page
//Cancel processing if (timeOutAdapter != null) { timeOutAdapter.cancelAllTimers(); }
This will solve the problem and end the work.