Android 关于倒计时功能的实现
关于倒计时的实现,可以说有很多的方法,比较常见的就是Timer+TimerTask+Handler了,或者还可以配合Runnable。例如下面的代码:
- import java.util.Timer;
- import java.util.TimerTask;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.View;
- import android.widget.Button;
- import android.widget.TextView;
- public class MainActivity extends Activity {
- Timer timer;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- final TextView tv = (TextView) findViewById(R.id.textView1);
- Button b = (Button) findViewById(R.id.button1);
- // 定义Handler
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- //handler处理消息
- if(msg.what>0){
- tv1.setText("" + msg.what);
- }else{
- //在handler里可以更改UI组件
- tv.setText("倒时");
- timer.cancel();
- }
- }
- };
- b.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View arg0) {
- // 定义计时器
- timer = new Timer();
- // 定义计划任务,根据参数的不同可以完成以下种类的工作:在固定时间执行某任务,在固定时间开始重复执行某任务,重复时间间隔可控,在延迟多久后执行某任务,在延迟多久后重复执行某任务,重复时间间隔可控
- timer.schedule(new TimerTask() {
- int i = 10;
- // TimerTask 是个抽象类,实现的是Runable类
- @Override
- public void run() {
- //定义一个消息传过去
- Message msg = new Message();
- msg.what = i--;
- handler.sendMessage(msg);
- }
- }, 1000, 200);
- }
- });
- }
- }
基本逻辑就是这样,需要注意一点是 timer.schedule(task,1000,5000),如果设置为 timer.schedule(task,5000)是不会工作的。因为timer.schedule(task,5000) 是表示执行一次的任务。timer.schedule(task,1000,5000)表示1 秒钟后开始 5 秒钟为周期 的重复执行任务。
这个例子北京简单,下面给出一个完整的例子:
- import java.util.Timer;
- import java.util.TimerTask;
- import com.example.jishiqi.SaveRun;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.content.DialogInterface;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.TextView;
- public class MainActivity extends Activity {
- Button btnselecttime, daojishijicubutton;
- TextView tvTime;
- private Timer timer = null;
- private TimerTask task = null;
- private Handler handler = null;
- private Message msg = null;
- float predegree = 0;
- float secondpredegree = 0;
- float hourpredegree = 0;
- int mlCount = -1;
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.main);
- btnselecttime = (Button) findViewById(R.id.daojishistartbutton);
- daojishijicubutton = (Button) findViewById(R.id.daojishijicubutton);
- tvTime = (TextView) findViewById(R.id.daojishitvTime);
- SaveRun.setisdaojishi(false);
- handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case 1:
- mlCount--;
- if (mlCount <= 0) {
- enddaojishi();
- }
- int totalSec = 0;
- int yushu = 0;
- totalSec = (int) (mlCount / 10);
- yushu = (int) (mlCount % 10);
- int min = (totalSec / 60);
- int sec = (totalSec % 60);
- try {
- tvTime.setText(String.format("%1$02d:%2$02d.%3$d", min,
- sec, yushu));
- predegree = (float) (0.6 * mlCount);
- secondpredegree = (float) (36.0 * mlCount);
- hourpredegree = (float) (mlCount / 100);
- } catch (Exception e) {
- tvTime.setText("" + min + ":" + sec + "." + yushu);
- e.printStackTrace();
- }
- break;
- default:
- break;
- }
- super.handleMessage(msg);
- }
- };
- }
- private void enddaojishi() {
- try {
- task.cancel();
- task = null;
- timer.cancel();
- timer.purge();
- timer = null;
- handler.removeMessages(msg.what);
- new AlertDialog.Builder(MainActivity.this)
- .setTitle("提示 ")
- .setMessage("倒计时结束")
- .setPositiveButton("确定",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- dialog.cancel();
- mlCount = 600;
- btnselecttime.setText("开始");
- SaveRun.setisdaojishi(false);
- }
- }).setCancelable(false).create().show();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- @Override
- protected void onStart() {
- daojishijicubutton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- predegree = 0;
- secondpredegree = 0;
- hourpredegree = 0;
- mlCount = -1;
- btnselecttime.setText("开始");
- SaveRun.setisdaojishi(false);
- try {
- if (task != null) {
- task.cancel();
- task = null;
- timer.cancel();
- timer.purge();
- timer = null;
- handler.removeMessages(msg.what);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- });
- btnselecttime.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (null == timer) {
- if (mlCount == -1 || mlCount == 0) {
- mlCount = 600;
- }
- if (mlCount > 0) {
- SaveRun.setisdaojishi(true);
- btnselecttime.setText("暂停");
- if (null == task) {
- task = new TimerTask() {
- @Override
- public void run() {
- if (null == msg) {
- msg = new Message();
- } else {
- msg = Message.obtain();
- }
- msg.what = 1;
- handler.sendMessage(msg);
- }
- };
- }
- timer = new Timer(true);
- timer.schedule(task, 100, 100);
- }
- } else {
- try {
- SaveRun.setisdaojishi(false);
- btnselecttime.setText("继续");
- task.cancel();
- task = null;
- timer.cancel();
- timer.purge();
- timer = null;
- handler.removeMessages(msg.what);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- });
- super.onStart();
- }
- }
布局:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical" >
- <TextView
- android:id="@+id/daojishitvTime"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_above="@+id/daojishibuttonlinear"
- android:layout_centerInParent="true"
- android:text="00:00.0"
- android:textSize="35sp"
- android:textStyle="bold" />
- <LinearLayout
- android:id="@+id/daojishibuttonlinear"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:orientation="vertical" >
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="74sp"
- android:background="@drawable/v5_bottom_bar_bg_light"
- android:orientation="horizontal" >
- <Button
- android:id="@+id/daojishistartbutton"
- android:layout_width="wrap_content"
- android:layout_height="50sp"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="8sp"
- android:layout_marginRight="3sp"
- android:layout_weight="1"
- android:background="@drawable/startbutton"
- android:text="开始" />
- <Button
- android:id="@+id/daojishijicubutton"
- android:layout_width="wrap_content"
- android:layout_height="50sp"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="3sp"
- android:layout_marginRight="8sp"
- android:layout_weight="1"
- android:background="@drawable/startbutton"
- android:text="取消" />
- </LinearLayout>
- </LinearLayout>
- </RelativeLayout>
显然,这个方式比较笨拙,我们可以对此进行一个封装,利用Handler和Eunnable,看下面的代码:
- package com.example.daojishi;
- import android.os.Handler;
- import android.util.Log;
- public class MyCountDownTimer {
- private long millisInFuture;
- private long countDownInterval;
- private boolean status;
- public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
- this.millisInFuture = pMillisInFuture;
- this.countDownInterval = pCountDownInterval;
- status = false;
- Initialize();
- }
- public void Stop() {
- status = false;
- }
- public long getCurrentTime() {
- return millisInFuture;
- }
- public void Start() {
- status = true;
- }
- public void Initialize() {
- final Handler handler = new Handler();
- Log.v("status", "starting");
- final Runnable counter = new Runnable() {
- public void run() {
- long sec = millisInFuture / 1000;
- if (status) {
- if (millisInFuture <= 0) {
- Log.v("status", "done");
- } else {
- Log.v("status", Long.toString(sec) + " seconds remain");
- millisInFuture -= countDownInterval;
- handler.postDelayed(this, countDownInterval);
- }
- } else {
- Log.v("status", Long.toString(sec)
- + " seconds remain and timer has stopped!");
- handler.postDelayed(this, countDownInterval);
- }
- }
- };
- handler.postDelayed(counter, countDownInterval);
- }
- }
这个类就是负责倒计时的类,下面结合Activity,看一下怎么用:
- package com.example.daojishi;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.util.Log;
- import android.view.View;
- import android.widget.Button;
- import android.widget.TextView;
- public class CounterActivity extends Activity {
- /** Called when the activity is first created. */
- TextView timeText;
- Button startBut;
- Button stopBut;
- MyCountDownTimer mycounter;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- timeText = (TextView) findViewById(R.id.time);
- startBut = (Button) findViewById(R.id.start);
- stopBut = (Button) findViewById(R.id.stop);
- mycounter = new MyCountDownTimer(20000, 1000);
- RefreshTimer();
- }
- public void StartTimer(View v) {
- Log.v("startbutton", "开始倒计时");
- mycounter.Start();
- }
- public void StopTimer(View v) {
- Log.v("stopbutton", "暂停倒计时");
- mycounter.Stop();
- }
- public void RefreshTimer() {
- final Handler handler = new Handler();
- final Runnable counter = new Runnable() {
- public void run() {
- timeText.setText(Long.toString(mycounter.getCurrentTime()));
- handler.postDelayed(this, 100);
- }
- };
- handler.postDelayed(counter, 100);
- }
- }
布局文件:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical"
- android:weightSum="1" >
- <TextView
- android:id="@+id/time"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="TextView"
- android:textAppearance="?android:attr/textAppearanceLarge" >
- </TextView>
- <Button
- android:id="@+id/start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="StartTimer"
- android:text="Start" >
- </Button>
- <Button
- android:id="@+id/stop"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:onClick="StopTimer"
- android:text="Stop" >
- </Button>
- </LinearLayout>
这样就可以比较方便地使用倒计时功能了。但是还有一个更简单的方法。
在Android中有一个CountDownTimer类,这个类就是用来实现类似倒计时方面的功能。使用的时候,只需要继承自CountDownTimer并实现它的方法。
- import android.app.Activity;
- import android.os.Bundle;
- import android.content.Intent;
- import android.os.CountDownTimer;
- import android.widget.TextView;
- import android.widget.Toast;
- public class NewActivity extends Activity {
- private MyCount mc;
- private TextView tv;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- tv = (TextView)findViewById(R.id.show);
- mc = new MyCount(30000, 1000);
- mc.start();
- }
- /*定义一个倒计时的内部类*/
- class MyCount extends CountDownTimer {
- public MyCount(long millisInFuture, long countDownInterval) {
- super(millisInFuture, countDownInterval);
- }
- @Override
- public void onFinish() {
- tv.setText("done");
- }
- @Override
- public void onTick(long millisUntilFinished) {
- tv.setText("seconds remaining: " + millisUntilFinished / 1000);
- }
- }
- }
onFinish()方法是本次倒计时结束的时候调用的,onTick是每隔1秒钟执行的,我们就是在这里执行重复的任务,像本例子的显示时间。执行完后会自动取消,如果在期间停止的话,可以调用cancel()方法。看一下它的源码就会发现,它是使用Handler+SystemClock来实现的。
- /*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package android.os;
- import android.util.Log;
- /**
- * Schedule a countdown until a time in the future, with
- * regular notifications on intervals along the way.
- *
- * Example of showing a 30 second countdown in a text field:
- *
- * <pre class="prettyprint">
- * new CountDownTimer(30000, 1000) {
- *
- * public void onTick(long millisUntilFinished) {
- * mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
- * }
- *
- * public void onFinish() {
- * mTextField.setText("done!");
- * }
- * }.start();
- * </pre>
- *
- * The calls to {@link #onTick(long)} are synchronized to this object so that
- * one call to {@link #onTick(long)} won't ever occur before the previous
- * callback is complete. This is only relevant when the implementation of
- * {@link #onTick(long)} takes an amount of time to execute that is significant
- * compared to the countdown interval.
- */
- public abstract class CountDownTimer {
- /**
- * Millis since epoch when alarm should stop.
- */
- private final long mMillisInFuture;
- /**
- * The interval in millis that the user receives callbacks
- */
- private final long mCountdownInterval;
- private long mStopTimeInFuture;
- /**
- * @param millisInFuture The number of millis in the future from the call
- * to {@link #start()} until the countdown is done and {@link #onFinish()}
- * is called.
- * @param countDownInterval The interval along the way to receive
- * {@link #onTick(long)} callbacks.
- */
- public CountDownTimer(long millisInFuture, long countDownInterval) {
- mMillisInFuture = millisInFuture;
- mCountdownInterval = countDownInterval;
- }
- /**
- * Cancel the countdown.
- */
- public final void cancel() {
- mHandler.removeMessages(MSG);
- }
- /**
- * Start the countdown.
- */
- public synchronized final CountDownTimer start() {
- if (mMillisInFuture <= 0) {
- onFinish();
- return this;
- }
- mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
- mHandler.sendMessage(mHandler.obtainMessage(MSG));
- return this;
- }
- /**
- * Callback fired on regular interval.
- * @param millisUntilFinished The amount of time until finished.
- */
- public abstract void onTick(long millisUntilFinished);
- /**
- * Callback fired when the time is up.
- */
- public abstract void onFinish();
- private static final int MSG = 1;
- // handles counting down
- private Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- synchronized (CountDownTimer.this) {
- final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
- if (millisLeft <= 0) {
- onFinish();
- } else if (millisLeft < mCountdownInterval) {
- // no tick, just delay until done
- sendMessageDelayed(obtainMessage(MSG), millisLeft);
- } else {
- long lastTickStart = SystemClock.elapsedRealtime();
- onTick(millisLeft);
- // take into account user's onTick taking time to execute
- long delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();
- // special case: user's onTick took more than interval to
- // complete, skip to next interval
- while (delay < 0) delay += mCountdownInterval;
- sendMessageDelayed(obtainMessage(MSG), delay);
- }
- }
- }
- };
- }
所以,如果你的程序需要执行一些周期性的任务,就可以考虑使用CountDownTimer这个类了。需要注意的是,在上面的这个例子中,最后显示时间是1,也就是说其实上执行了29次。所以这个地方一定要注意,如果你的任务次数是n,那么设置的时候一定要注意设置成n+1的时间。
最后,欢迎大家评论交流,谢谢。