Handlerとタイマー
今回はHandlerと、それを使ったタイマー処理を行います。
以下では、HelloIchiNiSanというプロジェクトで、label1というTextViewが一つあるとします。
Handlerとその使い方
タイマー処理にはHandlerというものを使います。
まずメンバ変数に以下のように定義します。
val handler = Handler()
Handlerが赤くなった時のimportは二つ選択肢があると思いますが、android.osの方を選びます。
そうするとHandler()の所に打ち消し線が出ると思いますが気にしないでください。(Handler()
の代わりにHandler(Looper.getMainLooper())
とすると消せます、気になる人はこっちを使ってもOK)
そしてタイマー処理するのは、以下のようにpostDelayedを使います。
handler.postDelayed({ /* ここに何か書く */ }, 5000)
postDelayedの1つ目の引数は時間が来た時に実行する処理を、2つ目の引数が、何msec秒後かを書きます。 msecなので5000msecで5秒です。100で0.1秒。 なお、ぴったりその時間後に呼んでくれる訳ではなく、ちょっとズレたりします。
処理を書くのは最初の引数の中のカッコですが、だいたいエンターキーで改行を入れる方がいいでしょう。以下みたいになります。
handler.postDelayed({
/* ここに何か書く */
}, 5000)
例えばいつものようにonCreate(いつもsetOnClickListenerとか書いている所)に以下を書いてみましょう。
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "ごー"
}, 5000)
こうすると、アプリが起動した5秒後に、”ごー”と出ます。
二回タイマーを仕掛けるには?
タイマーの最後に次のタイマーを仕掛けると、二回目のタイマーを実行出来ます。
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "いーち"
handler.postDelayed({ /* ここに二回目の処理 */ }, 1000)
}, 1000)
二回目の処理の所にも改行を入れると以下のようになる。
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "いーち"
handler.postDelayed({
/* ここに二回目の処理 */
}, 1000)
}, 1000)
2秒後に「にー」と表示するには以下。
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "いーち"
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "にー"
}, 1000)
}, 1000)
3秒後に「さーん」とするにはさらに以下になります。
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "いーち"
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "にー"
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "さーん"
}, 1000)
}, 1000)
}, 1000)
こういうコードは読むのは難しいですね。書く方は毎回以下みたいにしたあとに、
findViewById<TextView>(R.id.label1).text = "ほげ"
handler.postDelayed({}, 1000)
中括弧の所でEnter押すだけなので見た目よりも簡単なのですが、読むのが辛い。
こういう風になんちゃらの中になんちゃらがあって、その中にさらになんちゃらがあって〜というのを、「ネスト」といいます。 入れ子の構造ですね。 この階層が深い事を「ネストが深い」などと言います。
ネストが深いコードは読みづらいのでどうにかした方がいいのですが、その前に少し練習問題をやってみましょう。
課題: 「いーち、にー、さーん、ダー!」せよ
- 1秒後に「いーち」
- 2秒後に「にー」
- 3秒後に「さーん」
- 4秒後に「ダー!」
と表示せよ。
関数を使ってネストを減らす
関数を使ってネストを減らすように工夫するのが普通です。 どう工夫するかは問題ごとに考える必要がありますが、 まずは今回の以下のコードをどうするかを見ていきます。
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "いーち"
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "にー"
handler.postDelayed({
findViewById<TextView>(R.id.label1).text = "さーん"
}, 1000)
}, 1000)
}, 1000)
これは、「1秒ごとに次のメッセージを表示する」という事をやっているので、そういう関数を作れば良い事になる。
具体的にはメンバ変数に「現在何番目か」を表す変数を用意して、タイマーごとにその変数を一つ進めてメッセージを表示すれば良い。 イメージとしてはこんな感じ。
メンバ変数として以下を定義して、
val messages = listOf("いーち", "にー", "さーん")
var index = 0
関数、次のラベルという事でtsuginoLabelという関数を作るとすると、だいたい以下みたいな感じ
fun tsuginoLabel() {
val text = messages[index]
findViewById<TextView>(R.id.label1).text = text
index += 1 // 次に進める
handler.postDelayed( { tsuginoLabel() }, 1000)
}
これだとindexが3になった1秒後にクラッシュしてしまいますが、それをどう直すかは課題としましょう。
まずはこのコードを理解します。関数の中は以下のようになっています。
val text = messages[index]
findViewById<TextView>(R.id.label1).text = text
index += 1 // 次に進める
handler.postDelayed( { tsuginoLabel() }, 1000)
最初の行はindex番目のメッセージを取っています。そしてそれを二行目でセットする。ここまではいいでしょう。
三行目はちょっと新しいですね。
index += 1
+=
は文字列の連結でやったと思いますが、以下と同じ意味になるのでした。
index = index + 1
つまり、indexを現在の値より1増やす、という意味になります。 indexが0なら1になり、1だったら2になります。
最後の行は以下です。
handler.postDelayed( { tsuginoLabel() }, 1000)
これで1秒後にtsuginoLabel()
を実行します。関数を呼ぶだけとかなら、わざわざ中括弧で改行しないでもいいでしょう。
tsuginoLabelを実行すると、1秒後にtsuginoLabelを実行します。 この1秒後のtsuginoLabelを実行すると、そのさらに1秒後にtsuginoLabelを実行します。 これは、このままでは永遠に続いてしまいます。(そして4回目にはmessagesの存在しない要素を取り出そうとしてクラッシュする)
「さーん」を表示した所で止めるには、この最後のpostDelayedを3回目のtsuginoLabelでは呼ばないようにすれば良い訳です。
ただその課題に行く前に、最後にこの関数をonCreateで呼ぶようにします。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ここを追加
handler.postDelayed({ tsuginoLabel() }, 1000)
}
これで大枠は完成です。4秒後にクラッシュするのを確認して、次の課題をやりましょう。
課題:4秒後に落ちるのを直せ
indexが3になったらhandler.postDelayedを呼ばなければ良いので、if文でどうにかしてください。
課題:Retryボタンをつけろ
「さーん」まで行ったらRetry出来るRetryボタンをつけてください。
なお、handlerのタイマーはキャンセルとかは出来ません。 キャンセルはいろいろと難しいので、なるべく「キャンセルが必要無い」ように振る舞いを決めるのがオススメです。
という事でRetryボタンもenabledをfalseにしておいて、「さーん」まで行ったらenabledにしましょう。
課題:繰り返し「いーち」、「にー」、「さーん」、「いーち」、「にー」…と表示し続けるアプリを作れ
indexを0に戻してもいいし、index % 3
を使ってもいいです。
どちらの解き方も分かるのが理想ですね。