Kotlin:時計アプリを作成

はじめに

Kotlinで時計アプリを作ってみます。

ゴールは以下の機能を有するアプリを作ることに定めます。

  • シミュレータ上で日付・時間を表示する。
  • 時間はリアルタイムで更新される。

んで、下記の内容を把握できるかなぁ〜と思っています。

  • 関数の作り方などの基本的な内容をマスターする。
  • 日付・時間の操作方法をマスターする。
  • 画面のリアルタイム更新の方法をマスターする。

では、早速挑戦です!

プロジェクト作成の主な設定は以下のような感じです。
基本的にデフォルトの設定になります。

設定名 設定内容
アプリ名 clock
include Kotlin support チェックを入れる
Activity name MainActivity
Layout name activity_main

画面を設定する。

画面ですが、次のファイルを開くと編集可能です。

f:id:project-unknown:20181008184035p:plain:w350

app>res>layout>activity_main.xmlを開きます。

デフォルトでtextViewが一つありますので、これのIDだけ変更しておきます。 IDは「dateView」としておきます。

f:id:project-unknown:20181008211650p:plain

時間を取得する方法

時間の取得は、 「Calendar」を利用します。

val calendar = Calendar.getInstance()
// 時・分・秒を設定する。
val hour = calendar.get(Calendar.HOUR)
val minute = calendar.get(Calendar.MINUTE)
val second = calendar.get(Calendar.SECOND)

// dateViewのテキストを変更
val test = findViewById<TextView>(R.id.dateView)
test.text = "${hour}時${minute}分${second}秒"

結果は・・・
7時10分11秒と表示されました。
できれば、19時と表示したいので、下記のように修正しました。

val calendar = Calendar.getInstance()
// 時・分・秒を設定する。
val hour = calendar.get(Calendar.HOUR_OF_DAY)
val minute = calendar.get(Calendar.MINUTE)
val second = calendar.get(Calendar.SECOND)

// dateViewのテキストを変更
val test = findViewById<TextView>(R.id.dateView)
test.text = "${hour}時${minute}分${second}秒"

HOUR:午前、午後の何時(0〜11)を返す。
HOUR_OF_DAY:1日の何時(0〜23)を返す。

ここまでは簡単ですかね〜

リアルタイム更新

リアルタイム更新が曲者です。

timer関数を使えばいいと思うのですが、

f:id:project-unknown:20180923172732p:plain

引数が多くて、わかりにくい!
何を設定すればいいの!

調査した結果、引数の説明はこんな感じです。

引数 説明
name タイマーを実行しているスレッドに使用する名前。
daemon trueの場合、スレッドはデーモンスレッドとして起動されます
(デーモンスレッドのみが実行されているときにVMは終了します)
initialDelay タイマー作成後、指定したミリ秒後に処理を開始する。
period 指定したミリ秒後間隔で処理する。
action 定期的に実行する処理

この中で必ず指定しないといけないのは、3つ

  • name
  • period
  • action

指定した結果は、こんな感じです。

// nameとperiodを指定、{}内が全部actionの指定
timer(name = "testTimer",period = 1000) {

      val calendar = Calendar.getInstance()
      // 時・分・秒を設定する。
      val hour = calendar.get(Calendar.HOUR_OF_DAY)
      val minute = calendar.get(Calendar.MINUTE)
      val second = calendar.get(Calendar.SECOND)

      // dateViewのテキストを変更
      val test = findViewById<TextView>(R.id.dateView)
      test.text = "${hour}時${minute}分${second}秒"

}

これを実行した結果がこれ!

f:id:project-unknown:20180923195000p:plain

ええ!!!!

「アプリ名 keeps stopping」
ビルドは成功したのに、Stopしてしまいました。
原因はこいつでした。

val test = findViewById<TextView>(R.id.dateView)
test.text = "${hour}時${minute}分${second}秒"

Androidには、
「画面に関する処理はメインスレッドから行わなければならない」
という仕様があるためのようです。

timerのname引数の説明を読むと、
「タイマーを実行しているスレッドに使用する名前」
となっているため、timerメソッドを利用した段階で別スレッドを用意してしまい、その中で画面に関する処理を記載したため、停止してしまったようです。

ハンドラを用意する。

ハンドラはスレッド間の通信を行うことができます。

timerのスレッドで処理した内容をハンドラでメインスレッドに送信してあげます。

つまり、こんな感じで書き換えました。

// ハンドラのインスタンス作成
val hander = Handler()
        
        timer(name = "testTimer",period = 1000) {

            val calendar = Calendar.getInstance()
            // 時・分・秒を設定する。
            val hour = calendar.get(Calendar.HOUR_OF_DAY)
            val minute = calendar.get(Calendar.MINUTE)
            val second = calendar.get(Calendar.SECOND)

            // ハンドラで処理したい内容をメインメソッドに送信
            hander.post {
                val test = findViewById<TextView>(R.id.dateView)
                test.text = "${hour}時${minute}分${second}秒"

            }
        }

ハンドラを用意して、timerスレッド内でメインスレッドで処理してほしい内容をメインメソッドに送信します。

これにより、画面に関する処理がメインメソッド上で処理され、停止することなく処理されるようになります。

実行結果は以下の通りです。

f:id:project-unknown:20180923203644p:plain:w350

まとめ

リアルタイム処理を学ぶつもりが、ハンドラを勉強することになってしまいました。

ハンドラって「イベントハンドラ」とかいろんなところで聞いていたんですが、よくわかってなかったんだってわかりました。