タイマーやストップウォッチはchronometerを使えば簡単にできますが、自由度があまりありません。カウントアップ専用のTimerTaskを使えば100msec刻みのタイマーが作れます。
2021.2.1
TimerTask
と TimerTask を使います。とは言いましたが、Handlerだけでできるじゃないかというご意見もあります。
Timer TimerTask
タイマーのインスタンスを生成してスケジュールを設定
1 2 3 |
Timer timer = new Timer() // Timerのメソッド schedule (TimerTask task, long delay, long period) |
periodが時間間隔でこれを100にすると100msec間隔を意味します。
TimerTaskを継承したクラスを作り、メソッドのrun()を使って走らせます。(後半に簡略化した例もあります)
1 2 3 4 5 6 7 8 9 10 11 |
class CountUpTimerTask extends TimerTask { @Override public void run() { // handlerを使って処理をキューイングする handler.post(new Runnable() { public void run() { // ... } }); } } |
ここで、TimerTaskの別スレッドができますが、描画処理はmainスレッドでしかできませんのでHandlerのpostで処理待ちにします。
簡単なTimerTask
簡単にタイマーを作ってみます。少し冗長的ですが
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
//package your.package.name; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import android.os.Handler; import android.os.Looper; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private Timer timer; private CountUpTimerTask timerTask; // 'Handler()' is deprecated as of API 30: Android 11.0 (R) private final Handler handler = new Handler(Looper.getMainLooper()); private TextView timerText; private long count, delay, period; private String zero; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); delay = 0; period = 100; // "00:00.0" zero = getString(R.string.zero); timerText = findViewById(R.id.timer); timerText.setText(zero); // タイマー開始 Button startButton = findViewById(R.id.start_button); startButton.setOnClickListener(v -> { // タイマーが走っている最中にボタンをタップされたケース if(null != timer){ timer.cancel(); timer = null; } // Timer インスタンスを生成 timer = new Timer(); // TimerTask インスタンスを生成 timerTask = new CountUpTimerTask(); // スケジュールを設定 100msec // public void schedule (TimerTask task, long delay, long period) timer.schedule(timerTask, delay, period); // カウンター count = 0; timerText.setText(zero); }); // タイマー終了 Button stopButton = findViewById(R.id.stop_button); stopButton.setOnClickListener(v -> { // timer がnullでない、起動しているときのみcancleする if(null != timer){ // Cancel timer.cancel(); timer = null; timerText.setText(zero); } }); } class CountUpTimerTask extends TimerTask { @Override public void run() { // handlerを使って処理をキューイングする handler.post(() -> { count++; long mm = count*100 / 1000 / 60; long ss = count*100 / 1000 % 60; long ms = (count*100 - ss * 1000 - mm * 1000 * 60)/100; // 桁数を合わせるために02d(2桁)を設定 timerText.setText( String.format(Locale.US, "%1$02d:%2$02d.%3$01d", mm, ss, ms)); }); } } } |
レイアウトです
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="20dp" android:orientation="vertical" android:gravity="center" android:background="@color/cardview_light_background" tools:context=".MainActivity"> <TextView android:id="@+id/timer" android:layout_margin="20dp" android:textSize="60sp" android:textColor="#00f" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:text="@string/start" android:id="@+id/start_button" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:text="@string/stop" android:id="@+id/stop_button" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> |
リソース
strings.xml
1 2 3 4 5 6 |
<resources> <string name="app_name">YourAppName</string> <string name="start">Start</string> <string name="stop">Stop</string> <string name="zero">"00:00.0"</string> </resources> |
注意点:
- countで100msec刻みで増加して、longの範囲を越えるとエラーになります。9223372036854775807 から -9223372036854775808
- 時間表示にString.formatを使っていますが
1String.format("%1$02d:%2$02d.%3$01d", mm, ss, ms)
23:9.9 -> 23:10.0 のように桁があっていないと気持ちが悪いので$02dとして2桁限定にしています。レイアウトで対処することも可能でしょう。もっともこれは次で他の方法に変更します。 - タイマーを停止するために使うcancel()でTimerやTimerTaskは破棄されてしまいます。再開するときには再度インスタンスを生成する必要があります。
TimerTask 修正
1 |
String.format("%1$02d:%2$02d.%3$01d", mm, ss, ms) |
はいいのですが、いちいち時分秒の計算は面倒なのでSimpleDateFormatを使います。これは自分で決めたフォーマットに合わせてくれます。
ミリ秒で表されるmillisecがあるとすると、分:秒.ミリ秒(2桁)
1 2 3 |
SimpleDateFormat dataFormat = new SimpleDateFormat("mm:ss.SS", Locale.US); String time = dataFormat.format(millisec); |
但し、これは時刻を表示するものを便宜的に使ったのでLocaleの設定をしないとWarningが出ます。
年月日も追加してみるとわかりますが、startしてからの時間計測はこの例では、起点である1970/1/1からの時間ということになってしまいます。
また、scheduleの代わりに
1 |
scheduleAtFixedRate(TimerTask task, long delay, long period) |
を使います。periodの開始は前回のperiod終了から始まりますので開始までの余計な遅れは無くなります。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
//package your.package.name; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import android.os.Handler; import android.os.Looper; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private Timer timer; // 'Handler()' is deprecated as of API 30: Android 11.0 (R) private final Handler handler = new Handler(Looper.getMainLooper()); private TextView timerText; private long delay, period; private int count; private final SimpleDateFormat dataFormat = new SimpleDateFormat("mm:ss.S", Locale.US); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); delay = 0; period = 100; Button startButton = findViewById(R.id.start_button); Button stopButton = findViewById(R.id.stop_button); timerText = findViewById(R.id.timer); timerText.setText(dataFormat.format(0)); startButton.setOnClickListener(v -> { if (null != timer) { timer.cancel(); timer = null; } // Timer インスタンスを生成 timer = new Timer(); // カウンター count = 0; timerText.setText(dataFormat.format(0)); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { // handlerdを使って処理をキューイングする handler.post(() -> { count++; timerText.setText(dataFormat. format(count*period)); }); } }, delay, period); }); stopButton.setOnClickListener(v -> { // timer がnullでない、起動しているときのみcancleする if (null != timer) { // Cancel timer.cancel(); timer = null; timerText.setText(dataFormat.format(0)); } }); } } |
レイアウトとリソースは前と同じです
Timerを使わずHandlerだけ
みなさんこちらの記事をよく読んでおられるようで、どうもTimerは意味ないというご意見がありまして
TIMER(TASK) = BAD! DO IT THE ANDROID WAY: USE A HANDLER 🙂
これを元に作ってみました。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
//package your.package.name; import androidx.appcompat.app.AppCompatActivity; import android.os.Handler; import android.os.Bundle; import android.os.Looper; import android.widget.Button; import android.widget.TextView; import java.text.SimpleDateFormat; import java.util.Locale; public class MainActivity extends AppCompatActivity { // 'Handler()' is deprecated as of API 30: Android 11.0 (R) private final Handler handler = new Handler(Looper.getMainLooper()); private final Runnable runnable = new Runnable() { @Override public void run() { count ++; timerText.setText(dataFormat. format(count*period)); handler.postDelayed(this, period); } }; private TextView timerText; private final SimpleDateFormat dataFormat = new SimpleDateFormat("mm:ss.S", Locale.US); private int count, period; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); count = 0; period = 100; timerText = findViewById(R.id.timer); timerText.setText(dataFormat.format(0)); Button startButton = findViewById(R.id.start_button); startButton.setOnClickListener(v -> handler.post(runnable)); // タイマー終了 Button stopButton = findViewById(R.id.stop_button); stopButton.setOnClickListener(v -> { handler.removeCallbacks(runnable); timerText.setText(dataFormat.format(0)); count = 0; }); } } |
レイアウトとリソースは前と同じです。
確かに多少シンプルになりました。毎回Timerをnewする必要もなくなりました。
タイマー関連: