2012年05月18日

Handlerを使ったカウントアップタイマー(ストップウォッチ)


Androidで定期的に処理を実行する方法にHandlerのsendMessageDelayed()を使う方法があります
これを利用してカウントアップタイマー(ストップウォッチ)を作成します

Androidはシングル・スレッド モデルを基本としています
シングル・スレッド モデルとはアプリケーションが一度に 1 つの仕事しかできないようになっています
つまり例えば画面描写中にほかのプログラムで画面描写を行うことができないという感じです
これを利用してのカウントダウンタイマー作成を行いたいと思います

少々難しいことを書きましたがHandlerを使ってカウントアップタイマーを作ろうと思ったのは
カウントアップには前回Chronometerを使ったものを紹介しました
Chronometerを使ったカウントアップタイマー
これで簡単にstart/stop/clearが使用できますがこれでは1秒以下の表記ができないからです
(0.5秒とか)
そのためにストップウォッチのようなカウントアップを作成するためにHandlerの選択になりました

Handler sendMessageDelayed()を使う
まずはHandlerを拡張した独自クラスLoopEngineを作成します
その中にstart()メソッドstop()メソッドhandleMessageを作成します
class Loopengine
    //一定時間後にupdateを呼ぶためのオブジェクト
    class LoopEngine extends Handler {
        private boolean isUpdate;
        public void start(){
                this.isUpdate = true;
                handleMessage(new Message());
        }
        public void stop(){
                this.isUpdate = false;
        }
        @Override
        public void handleMessage(Message msg) {
                this.removeMessages(0);//既存のメッセージは削除
                if(this.isUpdate){
                	MainActivity.this.update();//自信が発したメッセージを取得してupdateを実行
                	sendMessageDelayed(obtainMessage(0), 100);//100ミリ秒後にメッセージを出力
                }
        } 
    };
まずはstart()を呼び出すとhandleMessageが呼び出されます
HandleMessageではsendMessageDelayedで一定時間ウェイトしてメッセージを出力しています
つまり (1)start()メソッド実行
(2)HandleMessage呼び出す
(3)MainActivity.this.update()を実行
(4)sendMessageDelayedにより一定時間待ってメッセージ発行
(5)自身が発したメッセージを受け取りsendMessageDelayedを実行
以下(3)〜(5)を繰り返し
この繰り返しはActivity内でその他の操作・動作にかかわらず行えるため
Handlerを使ったマルチスレッドが活きてきます

今回はsendMessageDelayed(obtainMessage(0), 100);としているので100ミリ秒ごとに
updateが呼ばれることになります
このupdateにタイマーを更新するプログラムを書いておけばストップウォッチが作成できます

カウントアップタイマー(ストップウォッチ)
startボタンとstopボタンを備えた0.1秒まで測定できるストップウォッチを作成しました
まずはmain.xmlの作成です
ボタン2つに(startとstop)テキストビューを5つ配置しました
(分・秒・1/10秒とそれぞれの区切り)
layout/main.xml
<?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" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="00" android:textSize="30sp"/>


        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text=":" android:textSize="30sp"/>

        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="00" android:textSize="30sp"/>

        <TextView
            android:id="@+id/textView4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="." android:textSize="30sp"/>

        <TextView
            android:id="@+id/textView5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0" android:textSize="30sp"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="start" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="stop" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

    </LinearLayout>
    
</LinearLayout>

なお文字が見やすいように文字サイズを30spにしてあります

次にMainActivity.javaです
こちらは先ほどのLoopEngineと各種ボタン動作
また、updateによるタイムの更新を作成しています
MainActivity.java
package blog.test;
 
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.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends Activity {
	TextView timer_m,timer_s,timer_ms;
	Button start,stop,restart;
	int minute,second,m_second;
	private LoopEngine loopEngine = new LoopEngine();
	private long startDate;
	@Override
	  protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.main); 
	    timer_m = (TextView)findViewById(R.id.textView1);
	    timer_s = (TextView)findViewById(R.id.textView3);
	    timer_ms = (TextView)findViewById(R.id.textView5);
	    start =(Button)findViewById(R.id.button1);
	    stop =(Button)findViewById(R.id.button2);

	    
	    start.setOnClickListener(new View.OnClickListener() {

	        public void onClick(View v) {
        		startDate =System.currentTimeMillis();
	        	loopEngine.start();
	        }
	    });
	    

	    stop.setOnClickListener(new OnClickListener(){
	        public void onClick(View v) {
	        	loopEngine.stop();
	        }
	    });
	    

	}	
	
	
	 public void update(){
		 	minute =(int)((((System.currentTimeMillis()-startDate))/1000)/60);
	       	second =(int)((((System.currentTimeMillis()-startDate))/1000)%60);
	       	m_second =(int)(((System.currentTimeMillis()-startDate)/10)%10);
	       	timer_m.setText(String.format("%1$02d",minute));
	       	timer_s.setText(String.format("%1$02d",second));
	       	timer_ms.setText(String.format("%1$01d",m_second));
	 }
	 
	 
    //一定時間後にupdateを呼ぶためのオブジェクト
    class LoopEngine extends Handler {
        private boolean isUpdate;
        public void start(){
                this.isUpdate = true;
                handleMessage(new Message());
        }
        public void stop(){
                this.isUpdate = false;
        }
        @Override
        public void handleMessage(Message msg) {
                this.removeMessages(0);//既存のメッセージは削除
                if(this.isUpdate){
                	MainActivity.this.update();//自信が発したメッセージを取得してupdateを実行
                	sendMessageDelayed(obtainMessage(0), 100);//100ミリ秒後にメッセージを出力
                }
        } 
    };

}


System.currentTimeMillis();では
現在の時刻をアンドロイドシステムからミリ秒で取得しています
startボタンを押すとstartDateに現在の時刻を記録
update内で再び現在の時刻を取得しstartDateを引いて経過時間を測定しています

またtimer_m.setText(String.format("%1$02d",minute));
ではminuteを2ケタ表示させ、1桁目に何もない時は“0”を表示させる設定にさせています

こちらを実行すると
device113.png
startボタンでタイマーがスタートし、stopボタンで止まります
device114.png


・・・え?動きがカクついてる・・?
それはupdateを呼び出すのが100ミリ秒ごとだからだと思います
ためしにsendMessageDelayed(obtainMessage(0), 1);とかにすると
だいぶスムーズになりますが負荷的にどうなんだろうww

スポンサードリンク

posted by kenken at 17:31 | Comment(0) | TrackBack(0) | タイマー カウントダウン | このブログの読者になる | 更新情報をチェックする
2012年05月17日

スタート・ストップ・リスタートを備えたカウントダウンタイマー


前回カウントダウンのスタートとストップボタンのみ実装しました
ですがこれではstopで止めたらまた最初からスタートしかできません
前回のをベースに途中中断した時用にリスタートボタンを実装したいと思います
カウントダウンタイマーを作成する

まずはボタンを追加するためmain.xmlを変更します
<?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" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0:00" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="restart" />
    
</LinearLayout>

次にMainActivity.javaを変更します
リスタートボタンを押したときの動作に
(1)その時の残り時間を取得する
(2)取得した残り時間を“分”と“秒”に分け秒に統一する
(3)統一した秒をミリ秒に変換しそれを初期値にスタートさせる
の3段階を追記します

MainActivity.java
package blog.test;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends Activity {
	TextView timer;
	Button start,stop,restart;
	private MyCountDownTimer cdt ;
	@Override
	  protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.main); 
	    timer = (TextView)findViewById(R.id.textView1);
	    start =(Button)findViewById(R.id.button1);
	    stop =(Button)findViewById(R.id.button2);
	    restart =(Button)findViewById(R.id.button3);
	    
	    start.setOnClickListener(new View.OnClickListener() {

	        public void onClick(View v) {
	    	    //CountDownの初期値
	        	cdt = new MyCountDownTimer(180000, 1000); 
	            // カウントダウン開始
	            cdt.start();
	        }
	    });
	    

	    stop.setOnClickListener(new OnClickListener(){
	        public void onClick(View v) {
	            // カウントダウン中止
	            cdt.cancel();
	        }
	    });
	    
	    restart.setOnClickListener(new OnClickListener(){
	        public void onClick(View v) {
	            //中止したtimeを取得
	            String time = timer.getText().toString();
	            //timeを分と秒に分ける
	            String[] time2 = time.split(":", 0);
	            //止めたところからリスタートする
	            cdt = new MyCountDownTimer((Integer.parseInt(time2[0])*60+Integer.parseInt(time2[1]))*1000, 1000);
	            cdt.start();
	        }
	    });
	}	
	
	
	
	public class MyCountDownTimer extends CountDownTimer{

	    public MyCountDownTimer(long millisInFuture, long countDownInterval) {
	        super(millisInFuture, countDownInterval);
	 
	    }
	 
	    @Override
	    public void onFinish() {
	        // カウントダウン完了後に呼ばれる
	        timer.setText("0");
	        Toast.makeText(getApplicationContext(), "終了", Toast.LENGTH_LONG).show();
	    }
	 
	    @Override
	    public void onTick(long millisUntilFinished) {
	        // インターバル(countDownInterval)毎に呼ばれる
	        timer.setText(Long.toString(millisUntilFinished/1000/60) + ":" + Long.toString(millisUntilFinished/1000%60));
	    }
	}
}

String time = timer.getText().toString();の部分で残り時間を取得
String[] time2 = time.split(":", 0);の部分で“:”を基準に文字を分割
これでtime[0]に分が、time[1]に秒が入ります
これを数値変換してやりstartさせることによりリスタートとしています

スタートボタンを押すとカウントダウンが始まり
device108.png

ストップボタンで一時停止します
device109.png

そしてリスタートボタンでそのタイムから再びスタートさせることができます
device110.png
スポンサードリンク

posted by kenken at 17:24 | Comment(0) | TrackBack(0) | タイマー カウントダウン | このブログの読者になる | 更新情報をチェックする

カウントダウンタイマーを作成する


AndroidではCountDownTimerを用いて簡単にタイマー機能を実装することが可能です
手順としては
(1)CountDownTimerを継承したクラスの作成
(2)カウントダウンの開始と中止メソッドの実装
の2つです

CountDownTimerを継承したクラスの作成
CountDownTimerクラスを利用するには、CountDownTimerクラスを継承したクラス
(今回はMyCountDownTimer)を作成します

MyCountDownTimerクラスには2つのメソッドを実装します
onTick():指定したインターバル毎に呼ばれる
onFinish():カウントダウン終了後に呼ばれる
public class MyCountDownTimer extends CountDownTimer{
 
    public MyCountDownTimer(long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
     }
 
    @Override
    public void onFinish() {
        // カウントダウン完了後に呼ばれる
    }
 
    @Override
    public void onTick(long millisUntilFinished) {
        // インターバル(countDownInterval)毎に呼ばれる
            }
}
MyCountDownTimerの第一引数(millisInFuture)はカウントダウンの開始値
第2引数(countDownInterval)は何秒毎にカウントダウンを行うかのインターバルをそれぞれ指定します

onTIck()の引数にはミリ秒単位で数値が渡されてくるので秒への変換は1000をかけます

カウントダウンとの開始と中止
MyCountDownTimerが実装できたらカウントダウンの開始と停止です
カウントダウンの開始には、start()
カウントダウンの中止を行うにはcancel()を利用します
また初期値インスタンス生成の引数にはカウントダウンの開始値とインターバルを
それぞれミリ秒単位で設定します

CountDownTimerサンプルプログラム
今回は2つのボタンを配置しstartボタンを押すと3分間のカウントダウンを開始
stopボタンでストップさせるプログラムを作成しました
まずはレイアウトからです
残り時間を表すTextViewとstartとstopボタンの配置です
main.xml
<?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" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0:00" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stop" />
    
</LinearLayout>

次にMainActivity.javaを変更します
package blog.test;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends Activity {
	TextView timer;
	Button start,stop;
	@Override
	  protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.main); 
	    timer = (TextView)findViewById(R.id.textView1);
	    start =(Button)findViewById(R.id.button1);
	    stop =(Button)findViewById(R.id.button2);
	
	    //CountDownの初期値
	    final MyCountDownTimer cdt = new MyCountDownTimer(180000, 1000);
	     
	    start.setOnClickListener(new View.OnClickListener() {
	    	
	        public void onClick(View v) {
	            // カウントダウン開始
	            cdt.start();
	        }
	    });
	    
	    stop.setOnClickListener(new OnClickListener(){
	        public void onClick(View v) {
	            // カウントダウン中止
	            cdt.cancel();
	        }
	    });
	}	
	
	
	
	public class MyCountDownTimer extends CountDownTimer{

	    public MyCountDownTimer(long millisInFuture, long countDownInterval) {
	        super(millisInFuture, countDownInterval);
	 
	    }
	 
	    @Override
	    public void onFinish() {
	        // カウントダウン完了後に呼ばれる
	        timer.setText("0");
	        Toast.makeText(getApplicationContext(), "終了", Toast.LENGTH_LONG).show();
	    }
	 
	    @Override
	    public void onTick(long millisUntilFinished) {
	        // インターバル(countDownInterval)毎に呼ばれる
	        timer.setText(Long.toString(millisUntilFinished/1000/60) + ":" + Long.toString(millisUntilFinished/1000%60));
	    }
	}
}

MyCountDownTimer cdt = new MyCountDownTimer(180000, 1000)により
初期値は180000ミリ秒(=180秒)、1000ミリ秒毎にonTickが呼ばれる設定です
onTickでは残りの分(millisUntilFinished/1000/60)と
秒(millisUntilFinished/1000%60)に分けて表示させています

これを実行すると
device105.png

startボタンを押すとカウントダウンがスタートします
device106.png

タイマーが0になると終了です
device107.png
スポンサードリンク

posted by kenken at 10:39 | Comment(0) | TrackBack(0) | タイマー カウントダウン | このブログの読者になる | 更新情報をチェックする