【はじめてのKotlinプログラミング(30)】ストップウォッチアプリの作り方~Chronometer~

kotlinで、簡単なストップウォッチアプリを作ってみたいと思います。今回の主な学習のテーマは
chronometerっていう
経過時間を表示させたいときに使用する機能です。

そもそもは、アプリを起動してからの経過時間を表示してくれるもので、まあ、ゲームを開始してからの時間表示なんかをイメージしてもらえればわかりやすいと思いますが、その時間表示の仕組みでchronometerっていうのがあるので
その原理を使って、ストップウォッチも作れるので、実際触っていきましょうとこういうわけです。

それでは前置きはこのくらいにして、早速はじめていきましょう

▽作業環境
AndroidStudio Bumblebee//2021.1.1
win10

動画

コード

▼activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Chronometer
        android:id="@+id/cm"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:textSize="34sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="start"
        app:layout_constraintEnd_toStartOf="@+id/btnStop"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/cm" />

    <Button
        android:id="@+id/btnStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="stop"
        app:layout_constraintEnd_toStartOf="@+id/btnReset"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/btnStart"
        app:layout_constraintTop_toBottomOf="@+id/cm" />

    <Button
        android:id="@+id/btnReset"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="reset"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/btnStop"
        app:layout_constraintTop_toBottomOf="@+id/cm" />


</androidx.constraintlayout.widget.ConstraintLayout>

▼MainActivity.kt

package com.example.stopwatchapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.SystemClock
import android.widget.Button
import android.widget.Chronometer

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

        //1)viewの取得
        val cm:Chronometer = findViewById(R.id.cm)
        val btnStart:Button = findViewById(R.id.btnStart)
        val btnStop:Button = findViewById(R.id.btnStop)
        val btnReset:Button = findViewById(R.id.btnReset)
        var offsetTime:Long = 0 //5)相殺時間(最初の空白の時間)

        //9)ボタンの有効無効(起動直後)
        btnStart.isEnabled = true
        btnStop.isEnabled = false
        btnReset.isEnabled = false

        //2)スタートボタン
        btnStart.setOnClickListener {
            //cm.base = SystemClock.elapsedRealtime() //4)表示の起点(経過時間のところから)
            //7)スタート・リスタート=経過時間全体 - 最初の空白の時間
            cm.base = SystemClock.elapsedRealtime() - offsetTime
            cm.start()

            //9)ボタンの有効無効(ストップボタンだけ押せる)
            btnStart.isEnabled = false
            btnStop.isEnabled = true
            btnReset.isEnabled = false
        }

        //3)ストップボタン
        btnStop.setOnClickListener {
            cm.stop()

            //6)相殺時間 = ストップまでの時間(全体) - スタートしてからの時間
            offsetTime = SystemClock.elapsedRealtime() - cm.base

            //9)ボタンの有効無効(ストップボタンだけ押せない)
            btnStart.isEnabled = true
            btnStop.isEnabled = false
            btnReset.isEnabled = true
        }

        //8)リセットボタン
        btnReset.setOnClickListener {
            cm.base = SystemClock.elapsedRealtime()  //表示を0に戻す
            offsetTime = 0

            //9)ボタンの有効無効(最初に戻るのでスタートだけ押せる)
            btnStart.isEnabled = true
            btnStop.isEnabled = false
            btnReset.isEnabled = false
        }
    }
}

テキスト

xmlは割愛

1)Viewの取得

まずはいつものようにviewを取得していきましょう。
onCreate 波カッコ 閉じるの前、setContentViewの下に書いていきます

        //1)viewの取得
        val cm:Chronometer =findViewById(R.id.cm)
        val btnStart:Button =findViewById(R.id.btnStart)
        val btnStop:Button =findViewById(R.id.btnStop)
        val btnReset:Button =findViewById(R.id.btnReset)

2,3)スタートとストップ

それでは、スタートとストップをやっちゃいましょう。

この下に
//2)スタートボタン

続けて
//3)ストップボタン
を書いていきます。

まずはスタートボタンですが、スタートボタンはこれ(btnStart)なので
btnStartが押されたときということで、
setOnClickListener 波カッコ。

中は、Chronometerをスタートさせるということで

        //2)スタートボタン
        btnStart.setOnClickListener {
                        cm.start()
        }

ストップボタンも、ほぼ同じことをしてあげればいいので
ストップボタン(btnStop)が押されたときということで
setOnClickListener 波カッコ。

        //3)ストップボタン
        btnStop.setOnClickListener {
            cm.stop()
        }

実はこれでもう、とりあえず表示は動くようになりました。
いったん、エミュレータを起動して確認してみましょう。

それではスタートボタンを押して、時間が表示されるかどうか確認してみましょう。

はい、こうですね。ちゃんと時間が表示されました。ただし、なんか途中から始まりましたよね。

これは後でなおすとして、ひとまずストップボタンをおしてみると。

はい、ちゃんと止まりました。

ひとまず、スタートとストップは押せるようになったんですが。なんかさっき、ちょっとオカシかったですよね。

で、結論からいうと、Chronometerっていうのは、アプリを起動した直後から時間が経過します。
で、スタートというのは、単にそれを表示するだけの呪文です。

もう1回、エミュレータを起動してみましょうか。
はい、今エミュレータを起動しました。
で、今すでに、時間経過がはじまっています。

2、3、4・・・とかですね。

で、スタートを押すと、その時の時刻が表示される、という仕組みです。

なので次に、スタートボタンをおしたところを「起点」とする、という呪文を書いていきましょう。

4)スタートボタンを押したタイミングを起点にする

それでは今、アプリを起動したら時間経過がはじまっているので
ボタンを押したタイミングを起点とする、という呪文を唱えてあげましょう。

2番の cm.start() の前に、
cmの起点ということで cm.base 
で、そこまでの経過時間を取得するには、こはもう決まり文句なので、このまま書いてください。

       //2)スタートボタン
        btnStart.setOnClickListener {
            cm.base = SystemClock.elapsedRealtime() //4)表示の起点(経過時間のところから)
            cm.start()
        }

これは
//4)表示の起点(経過時間のところから)
elapsed(イラプスト)っていうのは、経過するっていう意味です。
で、Realtime()って書いてあるんで、立ち上げてからの実際の経過時間っていう意味です。

時間は経過しているわけですが、ボタンを押したタイミングをベース、起点として
スタート。(cm.start())

だから3秒経過してたら、3秒のところを起点・基準にしてスタート。
8秒なら、8秒のところを起点として、スタート、っていう意味になります。

これで時間表示が、ボタンを押したタイミングから開始されているはずなのでエミュレータを起動して確認してみましょう。

****************************

はい、エミュレータを起動できました。
すでに、時間経過自体は始まっているわけですが

今度からはボタンを押したタイミングから表示されるはずなので、押してみます。

はい、ちゃんと無事、0から、つまり押したタイミングから始まっているのがわかります。

で、ストップで止まる。

ただし、今は毎回押したタイミングから0で始まるので、次にですね、
ストップを押したら、続きから開始される、というのをやっていきましょう。

5)相殺時間の変数
6)相殺時間の計算
7)表示の起点 = 経過時間全体 - 最初の空白の時間

それでは、ストップボタンがおされたら、その時間の続きから開始されるようにしていきましょう。

てことで、ストップボタンがおされたら、つまり

cm.stop()

の下に、ストップが押された時までの全体の経過時間から、スタートしてからの時間を引いて
最初の空白の時間というのを取得していきましょう。

//6)相殺時間(最初の空白の時間)

が、その前に、そのための受け皿を変数で用意してあげる必要があるので、変数を用意します。

var offsetTime :Long = 0 //5)相殺時間(最初の空白の時間)

1番に続けてvar ここは後で代入するのでvalではなくってvar。
相殺時間なので offsetTimeとかにしてあげて、
型は、以前、カウントダウンタイマーのところでもやりましたけど
intではなくって、Longです、
ひとまず中身は 0

プログラミングの世界では、単位はミリ秒なので
1秒は、1000。
10秒は1万。
100秒は10万、とかですね、桁数が多くなるのでintではなくってlongを使うようになっています。

で、これで受け皿ができたので
ストップボタンがおされたら、

//6)相殺時間 = ストップまでの時間(全体) - スタートしてからの時間

ストップが押された時までの経過時間全体から、スタートからの時間をひく。

//6)相殺時間 = ストップまでの時間(全体) - スタートしてからの時間
offsetTime = SystemClock.elapsedRealtime()-cm.base

そうすると、最初の空白の時間が算出できます。

******************************************
これで、最初の空白時間が算出できたので
リスタートは、経過時間全体から、この空白時間を引いてあげた、
残りの時間からスタートすればOK、ということになります。

それがわかったら、スタートボタンに戻っていただいて
今のこの cm.base はいったんコメントで消して、
スタート、もしくはリスタートの時をこのようにかきかえます

//7)スタート・リスタート=経過時間全体 - 最初の空白の時間
cm.base = SystemClock.elapsedRealtime()-offsetTime

最初のスタートの時は、offsetTimeは0なので、
イラプストタイムのSystemClock.elapsedRealtime()の
その時の経過時間のタイミングからスタート。

ストップボタンが押されたら、offsetTimeに数字が入ってきます。
なので、全体の時間から、offsetTimeを引いた残りの時間からスタート。
これがリスタートの仕組みです。

それではエミュレータを起動して確認してみましょう。

▼今は、offsetTimeは0なので、スタートボタンがおされた時間から表示が開始されます。

スタート。

こうですね。

でストップボタンを押す。

▼そうすると、今度は、offsetTimeに経過時間全体から、スタート以降の時間を引いた残りの時間が入ってくる。

ここでスタートを押すと、続きから開始されるはずなので
もう一度スタートを押してみると

はい、こうですね。

ちゃんと続きから開始されているのがわかります。

で、ストップボタンで止まると。

あとはリセットボタンを作ったら完成です。

8)リセットボタン

それでは最後に、リセットボタンを作れば完成です。

ストップボタンの、波カッコ閉じるの下に
書いていきましょう。

//8)リセットボタン

btnReset を押した時ということで
setOnClickListener 波カッコ。

まずはcm.base、つまりスタートの起点をもう一度0に戻してあげる必要があるので
cm.base=SystemClock.elapsedRealtime() //表示を0に戻す

そして、相殺時間も0に戻す。
offsetTime = 0

これで完成です。

        //8)リセットボタン
        btnReset.setOnClickListener {
            cm.base=SystemClock.elapsedRealtime() //表示を0に戻す
            offsetTime = 0
        }

9)ボタンの有効・無効

それでは最後、微調整ですが
ボタンの有効無効もやっておきましょう。

まずは、起動直後はスタートボタンだけ押せるので
btnStart のisEnabled  がtrue
それ以外は false
//9)ボタンの有効無効(起動直後)
btnStart.isEnabled = true
btnStop.isEnabled = false
btnReset.isEnabled = false

*********************************
スタートボタンが押された時には、ストップボタンだけ押せるようにしたいので
//9)ボタンの有効無効(ストップボタンだけ押せる)
btnStart.isEnabled = false
btnStop.isEnabled = true
btnReset.isEnabled = false

*********************************
スタートボタンが押された時には、スタートボタン・リスタートボタンは押せる、つまり
ストップボタンだけ押せないようにしたいので

//9)ボタンの有効無効(ストップボタンだけ押せない)
btnStart.isEnabled = true
btnStop.isEnabled = false
btnReset.isEnabled = true

*********************************
最後、リセットボタンは、最初に戻るので、

//9)ボタンの有効無効(最初に戻るのでスタートだけ押せる)

スタートボタンのやつをそっくりそのままコピーして、貼り付け
btnStart.isEnabled = true
btnStop.isEnabled = false
btnReset.isEnabled = false


以上です。

 

    コメントを残す