3
4

More than 3 years have passed since last update.

Timerでストップウォッチ

Posted at

個人開発でタイマー的なものを作ろうと思って、実装できたので備忘録として書きます。

目標

実装

Gradle

build.gradle
android {
    ...
    dataBinding {
        enabled = true
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}
dependencies {
    ...
    // coroutine
    def coroutinesVersion = '1.3.7'
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"

    // ktx
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
    implementation "androidx.fragment:fragment-ktx:1.2.4"
}

ViewModel

Timer().scheduleAtFixedRate(delay, period, TimerTask)は、delay(ms)後にTimerTaskをperiod(ms)間隔で実行します。
今回は、スタートボタンが押されたら、startTimer()を実行し、Timer()を起動します。Timer() で実行するタスクは、100(ms)毎にストップウォッチを100(ms)進めます。ストップボタンが押された場合は、Timer()を破棄(cancel())します。一度、破棄されたTimer()は再度実行不可能なので、スタートボタンが押せる度に新しいインスタンスを作る。

MainViewModel.kt
class MainViewModel : ViewModel() {
    private var timer = Timer()
    private val timerTask: TimerTask.() -> Unit = {
        viewModelScope.launch {
            _time.value = time.value?.plus(1)
        }
    }

    private val _time = MutableLiveData<Int>(0)
    val time: LiveData<Int>
        get() = _time

    private val _timerState = MutableLiveData<TimerState>(TimerState.Stop)
    val timerState: LiveData<TimerState>
        get() = _timerState

    val timerText: LiveData<String> = Transformations.map(time) {
        val hour = it / 36000
        val minute = (it % 36000) / 600
        val second = ((it % 36000) % 600) / 10

        "%02d:%02d:%02d".format(hour, minute, second)
    }

    fun startTimer() {
        _timerState.value = TimerState.Start
        timer = Timer()
        timer.scheduleAtFixedRate(0, 100, timerTask)
    }

    fun stopTimer() {
        _timerState.value = TimerState.Stop
        timer.cancel()
    }

    fun resetTimer() {
        _time.value = 0
    }
}

State

TimerStateは、ストップウォッチの状態を管理します。

TimerState.kt
sealed class TimerState {
    object Start: TimerState()
    object Stop: TimerState()
}

Layout

DataBindingを使用しており、ViewModelの状態を自動で変更してくれます。
android:clickableは、trueだとボタンを押すことができます。これを設定しないと、スタートボタンを連打するとCoroutineが無限に生成されてストップウォッチのタイマーがものすごい速さで進みます。他の対処方法やそもそも実装方法が間違ってるかもしれません。
android:alphaで、クリックできなくなったわかりやすくするためにボタンの色を薄くしています。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <data>

        <import type="com.example.myapplication.TimerState" />

        <variable
            name="viewmodel"
            type="com.example.myapplication.MainViewModel"
            />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout

        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        >

        <TextView
            android:id="@+id/timer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="140dp"
            android:text="@{viewmodel.timerText}"
            android:textSize="30sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="@string/timer"
            />

        <Button
            android:id="@+id/start_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="220dp"
            android:onClick="@{() -> viewmodel.startTimer()}"
            android:text="@string/start"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:clickable="@{viewmodel.timerState instanceof TimerState.Start ? false : true}"
            android:alpha="@{viewmodel.timerState instanceof TimerState.Start ? 0.2f : 1f}"
            />

        <Button
            android:id="@+id/stop_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="220dp"
            android:onClick="@{() -> viewmodel.stopTimer()}"
            android:text="@string/stop"
            app:layout_constraintEnd_toStartOf="@+id/reset_button"
            app:layout_constraintStart_toEndOf="@+id/start_button"
            app:layout_constraintTop_toTopOf="parent"
            android:clickable="@{viewmodel.timerState instanceof TimerState.Stop? false : true}"
            android:alpha="@{viewmodel.timerState instanceof TimerState.Stop ? 0.2f : 1f}"
            />

        <Button
            android:id="@+id/reset_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginTop="220dp"
            android:onClick="@{() -> viewmodel.resetTimer()}"
            android:text="@string/reset"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:clickable="@{viewmodel.timerState instanceof TimerState.Start ? false : true}"
            android:alpha="@{viewmodel.timerState instanceof TimerState.Start ? 0.2f : 1f}"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Activity

bindingの定義などを行う。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val mainViewModel: MainViewModel by viewModels()


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.viewmodel = mainViewModel
        binding.lifecycleOwner = this
    }
}
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4