End

倒计时 总结 Timer Handler CountDownTimer

本文地址


目录

倒计时 总结 Timer Handler CountDownTimer

基础方案

CountDownTimer [推荐]

简介

CountDownTimer 文档

Schedule安排、清单 a countdown until a time in the future, with regular规律的 notifications on intervals间隔 along the way过程.

在文本字段中显示一个30秒倒计时的示例:

new CountDownTimer(60000, 1000) {
    @Override
    public void onTick(long millisUntilFinished) {
        send.setText(millisUntilFinished / 1000 + "秒");
    }
    @Override
    public void onFinish() {
        send.setEnabled(true);
        send.setText("重试");
    }
}.start();

The calls to onTick(long) are synchronized同步 to this object so that one call to onTick(long) won't ever occur before the previous callback is complete.

This is only relevant有意义 when the implementation of onTick(long) takes an amount of time to execute that is significant重大 compared to the countdown interval. 任务执行时间比间隔时间大才有意义

API数量非常少,但各个都极其有用

  • CountDownTimer(long millisInFuture, long countDownInterval)
    • millisInFuture: The number of millis in the future from the call to start() until the countdown is done and onFinish() is called
    • countDownInterval: The interval along the way to receive onTick(long) callbacks
  • final CountDownTimer start()
  • final void cancel()
  • abstract void onFinish():Callback fired 被解雇、触发 when the time is up 时间到了
  • abstract void onTick(long millisUntilFinished):Callback fired on regular interval
    • millisUntilFinished: The amount of time until finished

案例

@BindView(R.id.send) Button send;//发送验证码
private CountDownTimer timer;//使用CountDownTimer

@OnClick({R.id.send, R.id.next})
public void onClickIv(View v) {
    switch (v.getId()) {
        case R.id.send:
            setTimer();//实际是要在点击之后判断如果符合倒计时条件才调用 setTimer 方法,要在失败后调用 destoryTimer
            break;
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    destoryTimer();
}

private void setTimer() {
    send.setEnabled(false);
    timer = new CountDownTimer(60 * 1000, 1000) {
       @Override
       public void onTick(long millisUntilFinished) {
          int time = (int) (millisUntilFinished / 1000);
          send.setText(time + "s");
       }

       @Override
       public void onFinish() {
          destoryTimer();
       }
    };
    timer.start();
}

private void destoryTimer() {
    send.setEnabled(true);
    send.setText("获取验证码");
    if (timer != null) {
        timer.cancel();
        timer = null;
    }
}

RxJava [推荐]

可以使用 intervalRange 很方便的实现这个功能,也可以使用 repeat、repeatUntil、repeatWhen 间接实现类似功能。

@BindView(R.id.send) Button send;//发送验证码
private Disposable disposable;

@OnClick({R.id.send, R.id.next})
public void onClickIv(View v) {
    switch (v.getId()) {
        case R.id.send:
            startCountDown();
            break;
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (disposable != null && !disposable.isDisposed()) {
        disposable.dispose();
    }
}

private void startCountDown() {
    send.setEnabled(false);
    disposable = Observable.intervalRange(0, 10, 0, 1, TimeUnit.SECONDS) //起始值,发送总数量,初始延迟,固定延迟
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(time -> send.setText((10 - time) + "s"),
            Throwable::printStackTrace,
            () -> {
                send.setEnabled(true);
                send.setText("获取验证码");
            }
        );
}

Timer + Handler [麻烦]

TimerTask task = new TimerTask() {
    @Override
    public void run() {
        //do something
    }
};

Timer timer = new Timer();
timer.schedule(task, 0, 1000);//每隔一秒钟执行一次

Timer + 普通 Handler

@BindView(R.id.send) Button send;//发送验证码
private int time = 60;//倒计时时间
private Timer timer;

@OnClick({R.id.send, R.id.next})
public void onClickIv(View v) {
    switch (v.getId()) {
        case R.id.send:
            setTimer();//实际是要在点击之后判断如果符合倒计时条件才调用 setTimer 方法,要在失败后调用 destoryTimer
            break;
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    destoryTimer();
}

@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
    @Override
    public void handleMessage(android.os.Message msg) {
        switch (msg.what) {
            case 1:
                send.setText(time + "s");
                break;
            case 2:
                destoryTimer();
                break;
        }
    }
};

//定时器
private void setTimer() {
    send.setEnabled(false);//不可点击
    timer = new Timer();
    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            time--;
            handler.sendEmptyMessage(time > 0 ?  1: 2);
        }
    };
    timer.schedule(task, 0, 1000);//每隔一秒钟执行一次
}

private void destoryTimer() {
    time = 60;//重新倒计时
    send.setEnabled(true);//重新可点击
    send.setText("重新发送");//重设文字
    if (timer != null) {
        timer.cancel();
        timer = null;
    }
    if (handler!=null) {
        handler.removeCallbacksAndMessages(null);
    }
}

Timer + 静态 Handler

相比示例一,是将Handler定义为了静态内部类,以防止内存泄漏

private Handler handler = new MyHandler(this);

private static class MyHandler extends Handler {
   private SoftReference<ForgetPasswordActivity> mSoftReference;

   MyHandler(ForgetPasswordActivity activity) {
      mSoftReference = new SoftReference<>(activity);
   }

   @Override
   public void handleMessage(Message msg) {
      super.handleMessage(msg);
      ForgetPasswordActivity activity = mSoftReference.get();
      if (activity != null) {
         switch (msg.what) {
            case 1:
               activity.send.setText(activity.time + "s");
               break;
            case 2:
               activity.destoryTimer();
               break;
         }
      }
   }
}

Timer + runOnUiThread

可以不用 Handler 而用其他更精简的API:

private void setTimer() {
   send.setEnabled(false);
   TimerTask task = new TimerTask() {
      @Override
      public void run() {
         runOnUiThread(() -> {
            time--;
            if (time > 0) {
               send.setText(time + "s");
            } else {
               destoryTimer();
            }
         });
      }
   };
   timer = new Timer();
   timer.schedule(task, 0, 1000);//每隔一秒钟执行一次
}

private void destoryTimer() {
    time = 60;
    send.setEnabled(true);
    send.setText("获取验证码");
    if (timer != null) {
        timer.cancel();
        timer = null;
    }
}

Handler + Delay [特麻烦]

倒计时通过用 Handler 发送 Delayed 消息来实现。核心代码为:

handler.sendMessageDelayed(handler.obtainMessage(1), 1000);

final Handler handler = new Handler() {
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case 1:
            time--;
            if (time > 0) {
                send.setText(time + "S");
                handler.sendMessageDelayed(handler.obtainMessage(1), 1000);//循环发送
            } else {
                send.setEnabled(true);
                send.setText("重新发送");
            }
        }
    }
};

倒计时通过用 Handler 发送 Delayed 的 Runnable 来实现,和上面原理是完全一样的。核心代码为:

Handler handler = new Handler();
handler.postDelayed(runnable, 1000);

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        time--;
        if (time > 0) {
            send.setText(time + "S");
            handler.postDelayed(this, 1000); //延迟发送当前 Runnable
        } else {
            send.setEnabled(true);
            send.setText("重新发送");
        }
    }
};

开源框架 CountdownView

GitHub上星星最多的倒计时控件:CountdownView

CountdownView:Android倒计时控件,使用Canvas绘制,支持多种样式

compile 'com.github.iwgang:countdownview:2.1.3'
cn.iwgang.countdownview.CountdownView

基本使用

CountdownView mCountdownView = (CountdownView)findViewById(R.id.countdownView);
mCountdownView.start(995550000); // 毫秒

// 或者自己编写倒计时逻辑,然后调用 updateShow 来更新UI
for (int time=0; time<1000; time++) {
    mCountdownView.updateShow(time);
}

其他用法

  • 动态设置自定义属性:dynamicShow(DynamicConfig)
  • 倒计时结束后的回调:setOnCountdownEndListener(OnCountdownEndListener);
  • 指定间隔时间的回调:setOnCountdownIntervalListener(long, OnCountdownIntervalListener);

在 RecyclerView 中实现倒计时

onViewAttachedToWindow [推荐]

核心思想为:利用System.currentTimeMillis()帮我们计算倒计时,并且在onViewAttachedToWindow时重新开始倒计时,在onViewDetachedFromWindow时关闭倒计时。

public class RecyclerViewActivity extends Activity {
    private List<ItemInfo> mDataList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
        initData();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv);
        recyclerView.setAdapter(new MyAdapter(this, mDataList));
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        recyclerView.setItemAnimator(new DefaultItemAnimator());
    }

    private void initData() {
        mDataList = new ArrayList<>();
        for (int i = 1; i < 20; i++) {
            mDataList.add(new ItemInfo(i * 20 * 1000));
        }
        // 校对倒计时
        long curTime = System.currentTimeMillis();
        for (ItemInfo itemInfo : mDataList) {
            itemInfo.endTime = curTime + itemInfo.countdown;
        }
    }

    static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
        private Context mContext;
        private List<ItemInfo> mDatas;

        public MyAdapter(Context context, List<ItemInfo> datas) {
            this.mContext = context;
            this.mDatas = datas;
        }

        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new MyViewHolder(LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false));
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.bindData(mDatas.get(holder.getAdapterPosition()));
        }

        @Override
        public int getItemCount() {
            return mDatas.size();
        }

        //******************************************** 关键代码 ↓↓ **********************************
        @Override
        public void onViewAttachedToWindow(MyViewHolder holder) {
            super.onViewAttachedToWindow(holder);//父类中为空代码
            holder.refreshTime(mDatas.get(holder.getAdapterPosition()).endTime - System.currentTimeMillis());
        }

        @Override
        public void onViewDetachedFromWindow(MyViewHolder holder) {
            super.onViewDetachedFromWindow(holder);
            holder.countdownView.stop();
        }
        //******************************************** 关键代码 ↑↑ **********************************
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {
        public CountdownView countdownView;

        public MyViewHolder(View itemView) {
            super(itemView);
            countdownView = (CountdownView) itemView.findViewById(R.id.countdownView);
        }

        public void bindData(ItemInfo itemInfo) {
            refreshTime(itemInfo.endTime - System.currentTimeMillis());
        }

        public void refreshTime(long leftTime) {
            if (leftTime > 0) {
                countdownView.start(leftTime);
            } else {
                countdownView.stop();//停止计时器,mCustomCountDownTimer.stop();
                countdownView.allShowZero();//所有计时清零,即mCountdown.setTimes(0, 0, 0, 0, 0);
            }
        }
    }

    static class ItemInfo {
        public long countdown;
        /*
           根据服务器返回的countdown换算成手机对应的开奖时间 (毫秒)
           [正常情况最好由服务器返回countdown字段,然后客户端再校对成该手机对应的时间,不然误差很大]
         */
        public long endTime;

        public ItemInfo(long countdown) {
            this.countdown = countdown;
        }
    }
}

更改数据源 + notify [不可靠]

这种方案在数据量特别小,且刷新 item 及计算倒计时耗费的时间特别短时适用,否则,将会产生巨大的时间延迟

//定时器,用于刷新 GridView 的数据源
private void setQryTimer() {
    cancelQryTimer(); //取消之前的定时器
    timer = new Timer(); //重新设置定时器
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            runOnUiThread(() -> {
                if (fixRpList != null && fixRpList.size() > 0) {
                    for (FixRpBean item : fixRpList) {
                        if (item.diff_time >= 0) item.diff_time = item.diff_time - 1000L; //【核心代码1】更改数据源
                    }
                    if (fixRpDialog != null) fixRpDialog.upDate(fixRpList); //刷新页面
                }
            });
        }
    }, 0, 1000); //以固定的周期刷新
}

public void upDate(List<FixRpBean> redPacketList) {
    list.clear(); //清除旧的数据
    list.addAll(redPacketList); //设置新的数据(如果列表的数据和源数据是同一个集合,也可以直接更新)
    mRecyclerView.getAdapter().notifyDataSetChanged();//【核心代码2】建议使用局部刷新功能
}

2017-6-12

posted @ 2017-06-12 15:07  白乾涛  阅读(1227)  评论(0编辑  收藏  举报