1. 程式人生 > 程式設計 >解決Android-RecyclerView列表倒計時錯亂問題

解決Android-RecyclerView列表倒計時錯亂問題

前言

轉眼間距離上次寫部落格已是過了一個年輪,期間發生了不少事;經歷了離職、找工作,新公司的第一版專案上線。現在總算是有時間可以將遇到的問題梳理下了,後期有時間也會分享更多的東西~~

場景

今天分享的問題是當在列表裡面顯示倒計時,這時候滑動列表會出現時間顯示不正常的問題。首先關於倒計時我們需要注意的問題有以下幾方面:

在RecyclerView中ViewHolder的複用導致的時間亂跳的問題。

滑動列表時倒計時會重置的問題。

在退出頁面後定時器的資源釋放問題,這裡我使用的是用系統自帶的CountDownTimer

ps:這裡我們討論的是對倒計時要求不是很嚴格的場景,對於使用者手動修改系統時間這種操作沒法預計;對於淘寶秒殺這種業務場景建議是實時不斷請求後臺拿取正確時間,對應的介面儘量設計簡單,響應資料更快。

接下來通過程式碼具體瞭解:

程式碼

// 介面卡
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
  //伺服器返回資料
  private List<TimeBean> mDatas;
  //退出activity時關閉所有定時器,避免造成資源洩漏。
  private SparseArray<CountDownTimer> countDownMap;

  //記錄每次重新整理時的時間
  private long tempTime;

  public MyAdapter(Context context,List<TimeBean> datas) {
    mDatas = datas;
    countDownMap = new SparseArray<>();
  }

  @Override
  public ViewHolder onCreateViewHolder(ViewGroup parent,int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_common,parent,false);
    return new ViewHolder(view);
  }

  @Override
  public void onBindViewHolder(final ViewHolder holder,int position) {
    final TimeBean data = mDatas.get(position);
    //記錄時間點
    long timeStamp = System.currentTimeMillis() - tempTime;

    long time = data.getLeftTime() - timeStamp;

    //將前一個快取清除
    if (holder.countDownTimer != null) {
      holder.countDownTimer.cancel();
    }
    if (time > 0) { //判斷倒計時是否結束
      holder.countDownTimer = new CountDownTimer(time,1000) {
        public void onTick(long millisUntilFinished) {
          holder.timeTv.setText(getMinuteSecond(millisUntilFinished));
        }
        public void onFinish() {
          //倒計時結束
          holder.timeTv.setText("00:00");
        }
      }.start();

      countDownMap.put(holder.timeTv.hashCode(),holder.countDownTimer);
    } else {
      holder.timeTv.setText("00:00");
    }
  }

  @Override
  public int getItemCount() {
    if (mDatas != null && !mDatas.isEmpty()) {
      return mDatas.size();
    }
    return 0;
  }

  public class ViewHolder extends RecyclerView.ViewHolder {
    public TextView timeTv;
    public CountDownTimer countDownTimer;

    public ViewHolder(View itemView) {
      super(itemView);
      timeTv = (TextView) itemView.findViewById(R.id.tv_time);
    }
  }

  public void setGetTime(long tempTime) {
    this.tempTime = tempTime;
  }

  /**
   * 將毫秒數換算成 00:00 形式
   */
  public static String getMinuteSecond(long time) {
    int ss = 1000;
    int mi = ss * 60;
    int hh = mi * 60;
    int dd = hh * 24;

    long day = time / dd;
    long hour = (time - day * dd) / hh;
    long minute = (time - day * dd - hour * hh) / mi;
    long second = (time - day * dd - hour * hh - minute * mi) / ss;

    String strMinute = minute < 10 ? "0" + minute : "" + minute;
    String strSecond = second < 10 ? "0" + second : "" + second;
    return strMinute + ":" + strSecond;
  }

  /**
   * 清空資源
   */
  public void cancelAllTimers() {
    if (countDownMap == null) {
      return;
    }
    for (int i = 0,length = countDownMap.size(); i < length; i++) {
      CountDownTimer cdt = countDownMap.get(countDownMap.keyAt(i));
      if (cdt != null) {
        cdt.cancel();
      }
    }
  }
}

以上算是整個問題的核心程式碼了;其中SparseArray<CountDownTimer> 用來儲存列表裡面的定時器,用於退出頁面時回收定時器。SparseArray是安卓特有的資料結構,建議多使用;data.getLeftTime() 是伺服器返回的需要倒計時的時間,毫秒為單位。

問題一:ViewHolder的複用導致的資料錯亂

if (holder.countDownTimer != null) {
    holder.countDownTimer.cancel();
  }

每次設定倒計時之前重置下倒計時即可解決。

問題二:滑動列表時倒計時會重置的問題

這個問題是由於解決問題一而導致的,因為列表滑動時離開螢幕的會被複用,這個時候我們會重新設定定時器,之前我是在倒計時裡面記錄倒計時剩餘的時間然後重新設值,但是還是會有問題;這裡借用了系統時間來解決,也就是tempTime 這個值。

首先在伺服器請求成功後回撥裡面設定這個值,如:

  private MyAdapter adapter;

  @Override
  public void onHttpRequestSuccess(String url,HttpContext httpContext)   {
    if (伺服器返回資料) {
      adapter.setGetTime(System.currentTimeMillis());
  }

相當於每次做重新整理操作時獲取的都是系統當時的時間戳。

然後在adapter裡面計算

long timeStamp = System.currentTimeMillis() - tempTime;

long time = data.getLeftTime() - timeStamp;

其中tempTime就是我們儲存的系統當前時間戳,然後每次滑動列表時都會呼叫onBindViewHolder,所以timeStamp就是記錄的距離上次重新整理經過了多少秒,然後用伺服器返回的需要倒計時的時間減去經過的秒數就是還剩下的倒計時秒數。最後給定時器設定上就好了。

問題三:資源的釋放

在當前的activity中呼叫以下方法。

@Override
protected void onDestroy() {
  super.onDestroy();
  if (adapter != null) {
    adapter.cancelAllTimers();
  }
}

好了,今天的分享就到這了,因為程式碼比較簡單,佈局都是一個Textview,所以沒有貼出來,需要程式碼的可以留言~~

補充知識:Android 自定義倒計時,支援listview多item一起倒計時

專案中用到的兩種倒計時,一種是用CountDownTimer,但是這種方式在listview中就不是那麼好用了,當listview 裡面多個item都需要倒計時,就不可以用這種了,我這裡想到用Thread 加handler來一起實現。如果大家還有好的倒計時方法,可以留言一起討論哦,由於程式碼都是在專案中的,我就擷取幾段程式碼。

第一種 CountDownTimer:

主要自定義一個類繼承CountDownTimer,在啟動的時候呼叫start(),倒計時完畢呼叫canel()方法。

time = new TimeCount(remainingTime,1000);//構造CountDownTimer物件
time.start();//開始計時

class TimeCount extends CountDownTimer {
    public TimeCount(long millisInFuture,long countDownInterval) {
      super(millisInFuture,countDownInterval);
    }

    @Override
    public void onFinish() {//計時完畢時觸發
      if (isDead) {
        remainingTime = 90000;
        ColorStateList colorStateList = getResources().getColorStateList(R.color.button_send_code_text2_selector);
        getCode.setTextColor(colorStateList);
        getCode.setText(R.string.register_tip7);
        getCode.setEnabled(true);
      }
    }

    @Override
    public void onTick(long millisUntilFinished) {//計時過程顯示
      if (isDead) {
        getCode.setEnabled(false);
        getCode.setTextColor(getResources().getColor(R.color.grey5));
        remainingTime = millisUntilFinished;
        getCode.setText(millisUntilFinished / 1000 + "秒後重發");
      }
    }
  }

第二種 Thread 加handler

建立一個新的執行緒,每秒中減一次時間,然後在handler中每秒中重新整理一次介面就可以看到倒計時的效果。

 private Thread thread;

  //條目倒計時
  public void start() {
    thread = new Thread() {
      public void run() {
        while (true) {
          try {
            if (list != null) {
              for (InvestProjectVo item : list) {

                if(item.remainOpenTime == 0){
                  item.status = 0;
                }

                if(item.remainOpenTime > 0){
                  item.remainOpenTime = item.remainOpenTime - 1;
                }
              }
            }
            sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    };
    thread.start();
  }

在adapter的getview()方法中,判斷倒計時時間是否大於0,如果大於零可以繼續顯示倒計時時間

          if (vo.remainOpenTime != 0 && vo.remainOpenTime > 0) {

            viewCache.showProjectFullIcon.setVisibility(View.GONE);
            viewCache.projectProgress.setVisibility(View.GONE);
            viewCache.showTimer.setVisibility(View.VISIBLE);

            long tempTime = vo.remainOpenTime;
            long day = tempTime / 60 / 60 / 24;
            long hours = (tempTime - day * 24 * 60 * 60) / 60 / 60;
            long minutes = (tempTime - day * 24 * 60 * 60 - hours * 60 * 60) / 60;
            long seconds = (tempTime - day * 24 * 60 * 60 - hours * 60 * 60 - minutes * 60);
            if (minutes > 0) {
              viewCache.timer.setText(minutes + "分" + seconds + "秒");
            } else {
              viewCache.timer.setText(seconds + "秒");
            }
          }else{
            viewCache.showProjectFullIcon.setVisibility(View.GONE);
            viewCache.projectProgress.setVisibility(View.VISIBLE);
            viewCache.showTimer.setVisibility(View.GONE);
          }

在handler中每秒鐘重新整理一次介面

mHandler.sendEmptyMessageDelayed(2586221,1000);

adapter.notifyDataSetChanged();
//每隔1毫秒更新一次介面,如果只需要精確到秒的倒計時此處改成1000即可
mHandler.sendEmptyMessageDelayed(2586221,1000);

以上這篇解決Android-RecyclerView列表倒計時錯亂問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。