【Kotlin】Timerでメトロノームアプリを作る

# はじめに
メトロノームアプリを作る際に、テンポを毎回更新するところで少し詰まったので記事にしました。

作ったのはこんなアプリです。

 - 中央のボタンでスタート・ストップ
 - NumberPickerで速度調整（**再生中の速度変更も可**）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549670/04e442d4-a8ff-9afc-1a42-07b506eaef4f.gif" width="400 px" >

音がないのでわかりづらいですが、スタートボタンを押すと表示されているテンポで音が流れます。

## 画面を作る
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549670/7b5b5ffe-c3dc-fe2d-ca32-3a3fb4c0a904.png" width="500 px">

NumberPicker（id:`tempoPicker`）と再生・停止用にImageView（id:`startButton`）だけあれば大丈夫です。
（ボタンをImageViewで作ってます）


## 素材を入れる
<img width="538" alt="スクリーンショット 2019-12-22 3.01.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549670/0971feb2-4d0a-ef54-b098-482cd8664243.png">

 - ボタンの画像を切り替えるための「start.png」「stop.png」
 - メトロノームのSE用の「metronome.mp3」

を入れておきます。SEは途中でカットされるので1秒以上あるものならなんでも大丈夫です。

## コードを書く
MainActivity.ktに以下のコードを書いてください。

```Kotlin:MainActivity.kt
package パッケージ名

import android.media.MediaPlayer
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import kotlinx.android.synthetic.main.activity_main.*
import kotlin.concurrent.timer

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var tempo : Int = 40 //テンポ
        val handler : Handler = Handler() //タイマーで画面の変更が行われるときに必要
        var interval : Long = (60000/tempo).toLong() //テンポをインターバルに変換
        val mediaPlayer = MediaPlayer.create(this, R.raw.metronome)
        var metronome = timer(period = interval) {}
        var isPlaying : Boolean = false //メトロノームの再生状態（true -> 再生中, false -> 停止）

        tempoPicker.minValue = 40
        tempoPicker.maxValue = 208

        fun setMetronome() {
            interval = (60000/tempo).toLong()
            metronome.cancel()
            metronome = timer(period = interval) {
                handler.post {
                    mediaPlayer.stop()
                    mediaPlayer.prepare()
                    mediaPlayer.start()
                }
            }
        }

        tempoPicker.setOnValueChangedListener { picker, oldVal, newVal ->
            tempo = newVal
            if(isPlaying) setMetronome()
        }

        startButton.setOnClickListener {
            when(isPlaying) {
                true -> {
                    startButton.setImageResource(R.drawable.start)
                    metronome.cancel()
                    isPlaying = false
                }
                false -> {
                    setMetronome()
                    startButton.setImageResource(R.drawable.stop)
                    isPlaying = true
                }
            }
        }
    }
}

```

とりあえずこれで動きます。

## 詰まった点
### Timer
タイマーの引数`period`には待機時間が入ります。
今回のアプリでは、テンポの更新に伴って待機時間も連動するようにしたかったのですが、変数を指定しただけでは反映されませんでした。
なので、テンポが更新される度に下記のようにタイマーを再定義する必要があります。
また、`metronome.cancel()`を直前に入れないと新しいタイマーがどんどん生成されてしまうので注意です。

```Kotlin:
//更新する度に実行
metronome.cancel() 
metronome = timer(period = interval) {
    //定期的に実行したい命令
}
```

### MediaPlayer
メトロノームのように、短いSEを定期的に鳴らす場合は一度前の音を`stop()`で停止し、`prepare()`、`start()`の順で行う必要があります。

```Kotlin:
mediaPlayer.stop()
mediaPlayer.prepare()
mediaPlayer.start()
```

# 素材
スタート・ストップボタンの画像も作ったのでよければ使ってください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549670/567fa2f2-b69e-004e-44bc-6589f41a9233.png" width="200 px"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549670/93acfc39-b1a3-0dc5-7f54-4e7477dea89b.png" width="200 px">