【はじめてのAndroidアプリ開発】タイマーアプリを作ってみよう



【はじめてのAndroidアプリ開発】タイマーアプリを作ってみよう

Androidのアプリ開発をはじめようと考えている方、もしくははじめたばかりの方向けにお届けしている 【はじめてのAndroidアプリ開発】 シリーズ。 今回は 『タイマーアプリ』の作り方をご紹介。

CountDownTimer など Android 特有のクラスの使い方に慣れていきましょう。

★こんな方に本稿は役立ちます★

  • Android アプリ開発初心者
  • 参考書を開いて、フリーズした方
  • タイマーやカウントダウンなどの時間制御の機能を使いたいと思っている方
  • UI 部品とプログラムの接続方法について知りたいと思ってる方

★このアプリ開発を通して習得できるスキル★

  • UI 部品の基本的なレイアウト
  • UI 部品とプログラムの接続
  • タイマー制御
  • ボタンやテキストの表示・非表示の制御

★今回使用するエミュレーター環境★

  • Android 9 (Pie) API 28
目次
  1. 【はじめてのAndroidアプリ開発】タイマーアプリを作ってみよう
  2. 今回ご紹介するタイマーアプリの制作の様子
  3. タイマーアプリの UI 部品を設定
  4. UI 部品セット時に発生した黄色エラーの対策
  5. タイマーアプリのプログラム
  6. タイマーアプリを実行
  7. まとめ

【はじめてのAndroidアプリ開発】タイマーアプリを作ってみよう

今回ご紹介するタイマーアプリの制作の様子

30分オーバーになりますが、まずは本チュートリアルをご参照いただく前に、実際にタイマーアプリを作っている様子を視聴してみてください。

今回動画を用意させて頂いた理由としては、私自身が Android アプリの参考書を開いて、フリーズしました。それでどうしたら学習効率が上がるかなと模索していたところ動画を見ることによって知らない文法やクラスを使っていても、Androidアプリの開発を楽しめそうと思い、テキストより動画の方が好ましいのかなと判断した結果です。

ただ、動画だけでは説明しきれない部分も多くありますので、以下テキストに各工程をまとめさせて頂きました。

タイマーアプリの UI 部品を設定

テキストとボタンの設置(初期)

image

まずは今回タイマーアプリで使用する時刻のテキストラベルとスタートボタンとリセットボタン、ボタン二つの UI部品 をセットしましょう。 

UI 部品のセットについて は、activity_main.xml のデザイン画面からパレット内の UI 部品を選択してドラッグ&ドロップでも設定可能ですが、今回は id の設定のこともありましたので、テキストコードから直接 UI 部品をセットしていくようにしました。

追加したプログラムについては以下の内容です。


<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="00:00"/>
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="スタート" />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="リセット" />

ここまでの activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="00:00"/>
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="スタート" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="リセット" />
</android.support.constraint.ConstraintLayout>

各 UI 部品のレイアウトについては、デフォルトで使用を推奨される wrap_content を選択。 wrap_content は、文字のサイズが大きくなっても小さくなっても、その枠内にうまく表示してくれるレイアウト機能。今後アプリ開発を行う上でもよく使うレイアウト機能と思います。

ここで一旦コードで書いた UI 部品がどのように画面に反映されているか確認してみましょう。

image

最初はラベルとボタンが全部重なっているのですが、上記画像は見やすいように各部品を少し下にずらしたものになります。

ボタンの文字の大きさはいいと思うのですが、時刻を表示する数字がちょっと小さいので数字のサイズを大きくしてみましょう。

UI部品のレイアウト

文字の大きさの変更については、 textsize を使うと変更できます。

【テキストラベルのコード】

<TextView
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="00:00"
   android:textSize="80sp"
   />
ここまでのactivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="00:00"
        android:textSize="80sp"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="スタート" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="リセット" />

</android.support.constraint.ConstraintLayout>

使用できる文字サイズの単位は以下の通り。

  • px(ピクセル)
  • in(インチ)
  • mm(ミリメートル)
  • pt(ポイント)
  • dp(Density-independent Pixels)
  • sp(Scale-independent Pixels)

今回使用した単位 sp は、ユーザー端末の文字サイズの設定によっても柔軟にサイズが変わる機能。 時間に余裕がある方は他の単位も試しに使ってレイアウトがどのように変わるかチェックしてみてください。

テキストとボタンのレイアウトについてはこれぐらいにして、あとは画面で見やすいような表示レイアウトにしたいと思います。

image

レイアウトの設定手順については、動画 の4分20秒ぐらいから見ていただくと分かるのですが、まずは表示時刻を縦横中央表示されるようにセットして、その下にスタートボタン、その下にリセットを配置。横方向は中央で縦方向は少しだけ間隔を保つようにと部品同士を接続していきます。

ここまでのactivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="00:00"
        android:textSize="80sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="スタート"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="リセット"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button3" />

</android.support.constraint.ConstraintLayout>

以上でレイアウトについては完了とします 。

後は黄色のエラーが発生しますので、そのエラー解除を確認していきましょう。

UI 部品セット時に発生した黄色エラーの対策

image

【エラー内容】

一つ目のエラー
Hardcoded string "00:00", should use @string resource

二つ目のエラー
Hardcoded string "スタート", should use @string resource

三つ目のエラー
Hardcoded string "リセット", should use @string resource

このエラーは簡単に言いますと、activitity_main.xml で使用したテキストの書き方が違うよというエラーになります。 例えばスタートボタンの部分。推奨されているコードで書くと以下の様に。

android:text="@string/Start"

しかし、リファレンス通りの書き方だけではエラーは消えません。

image

エラーの理由を確認すると、スタートという文字列に対して値が設定されていないため。 試しにデザイン画面で今の状態を確認。

image

スタートボタンの黄色エラーは消えていますが、ボタンの文字はスタートではありません、 @ string に変わっています。この問題を解決する方法は @ string/Start の文字上で左クリックして、行の左に表示されている赤色のバルマークを左クリック。すると設定画面に移動できるメニューが表示。

image

「Create string value resource '○○' 」を左クリックしましょう。そして下記のようにポップアップ表示される New string Value Resource の Resource Value のところに画面上で表示させたい文字を入力。今回はスタートボタンなのでスタートと入力し OK をクリックします。

image

【Resource Value入力後】

image

なお上記のように @string/〇〇 とリファレンス通りコードを入力していない場合でも、下記のようにリソースネームをセットすることによってエラーを解決することも可能。文字「リセット」のところで黄色いバルーンをクリックするか、もしくはリセットの文字の上で Altキーを押しながら Enter を押すと Resource Value をセットすることが可能。

【文字上で Alt + Enter】

image

【Resource name入力後】

image

【修正結果】

image

ここまでのactivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/_00_00"
        android:textSize="80sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="@string/Start"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="@string/reset"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button3" />

</android.support.constraint.ConstraintLayout>

UI部品の接続準備

image

気づいておられた方もいらっしゃるかもしれませんが、 activity_main.xml を編集していく中で、自動的に id が割り当てられている UI 部品もありますね。

この id を使うことでメインアクティビティで UI 部品をプログラム制御することが可能。

デフォルトセットの id をもう少し分かりやすいように変えてみました。

<TextView
   android:id="@+id/text_view_countdown"

<Button
   android:id="@+id/button_start_pause"

<Button
   android:id="@+id/buttonreset"
ここまでのactivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

   <TextView
       android:id="@+id/text_view_countdown"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/_00_00"
       android:textSize="80sp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <Button
       android:id="@+id/button_start_pause"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:text="@string/Start"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/text_view_countdown" />

   <Button
       android:id="@+id/buttonreset"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:text="@string/reset"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/button_start_pause"
       tools:ignore="HardcodedText" />

</android.support.constraint.ConstraintLayout>

また今回二つ目のボタン「リセットボタン」は、表示したり表示しなかったり状況に応じてレイアウトを変えたいので visibility のコードも追加しておきます

tools:visibility="visible"
ここまでのactivity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

   <TextView
       android:id="@+id/text_view_countdown"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/_00_00"
       android:textSize="80sp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <Button
       android:id="@+id/button_start_pause"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:text="@string/Start"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/text_view_countdown" />

   <Button
       android:id="@+id/buttonreset"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="8dp"
       android:text="@string/reset"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.5"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/button_start_pause"
       tools:visibility="visible"
       tools:ignore="HardcodedText" />

</android.support.constraint.ConstraintLayout>

レイアウトの activity_main.xml の方はこれで一旦終了です。次はタイマーの振る舞いをプログラムする MainActivity.Java を編集していきたいと思います。


タイマーアプリのプログラム

image

動画へのリンク

MainActivity.javaの初期設定

まずは今回のプログラムで必要となるクラスを読み込んでおきましょう。

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import java.util.Locale;

今回の主役となるクラスは、 CountDownTimer() です。

次に今読み込んだ各クラスをアクセス修飾子しゅうしょくしとしてセットし、本プログラム内で各機能を使えるように設定しておきます。

private  TextView mTextViewCountDown;
private  Button mButtonStartPause;
private  Button getmButtonReset;

private  CountDownTimer mCountDownTimer;
アクセス修飾子とは

アクセス修飾子とはプログラムへのアクセスレベルを設定できる機能のこと。

Java プログラムで必ずと言っていいほど登場してくる public class 〇〇。この public という部分がアクセス修飾子になります。

public 以外にも private や protected などが存在。各アクセス修飾子の違いは下記のようになります。


public / すべてのクラスからアクセスできる

protected / 現在のクラスとサブクラスからアクセスできる

private / 現在のクラスからだけアクセスできる


ここまでの MainActivity.java

package com.example.timerapp2;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    private  TextView mTextViewCountDown;
    private  Button mButtonStartPause;
    private  Button getmButtonReset;

    private  CountDownTimer mCountDownTimer;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

尚、Android アプリを起動した時に 一番初めに読み込まれる項目は

@Override
    protected void onCreate(Bundle savedInstanceState) {

の onCreate() から。つまり制御文は、 onCreate()以下に記述していきます。

アクセス修飾子の設定が完了したら、次は今回のタイマープログラムで利用する時間の定義とタイマーが起動してるかどうかの TimerRunning を設定しておきたいと思います。(プロパティの設定)

【タイマーする時間 10秒】

private static final long START_TIME = 10000;

【タイマーのプロパティ設定】

 private long mTimeLeftInMillis = START_TIME;

【タイマー起動中?のプロパティ設定】

private  boolean mTimerRunning;
ここまでの MainActivity.java

package com.example.timerapp2;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    private static final long START_TIME = 10000;

    private  TextView mTextViewCountDown;
    private  Button mButtonStartPause;
    private  Button getmButtonReset;

    private  CountDownTimer mCountDownTimer;
    private  boolean mTimerRunning;

    private long mTimeLeftInMillis = START_TIME;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

タイマー起動中を真偽する mTimeRunning。 この mTimerRunning は任意の値で、 Java の Timer クラスとは特に関係ないですね。

UI 部品との接続

activity_main.xml でセットした UI部品と本プログラムの連携するために id をセットしておきましょう。

mTextViewCountDown = findViewById(R.id.text_view_countdown);
mButtonStartPause = findViewById(R.id.button_start_pause);
getmButtonReset = findViewById(R.id.buttonreset);
ここまでの MainActivity.java

package com.example.timerapp2;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private static final long START_TIME = 10000;


    private  TextView mTextViewCountDown;
    private  Button mButtonStartPause;
    private  Button getmButtonReset;

    private  CountDownTimer mCountDownTimer;
    private  boolean mTimerRunning;

    private long mTimeLeftInMillis = START_TIME;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextViewCountDown = findViewById(R.id.text_view_countdown);
        mButtonStartPause = findViewById(R.id.button_start_pause);
        getmButtonReset = findViewById(R.id.buttonreset);
    }
}

ここの

findViewById(R.id.text_view_countdown)

ちょっと分かりにくいですよね。まず findViewById() については Viewクラスに属する機能。UI 部品との接続にはこの findViewById() が必要に。

そして findViewById() の中身の 「R」 も Android 特有のクラス。 Rを使うことで xml ファイルと Java ファイルの相互関係を構築するというもの。最後に id.〇〇 ということろは、 activity_main.xml でセットした id を指定。

UI 部品とプログラムを接続するには、以上の様なコードが必要になってきますね。

タイマープログラムのフロー

image

タイマープログラムを作成していく前に、改めてタイマープログラムの流れを確認しておきたいと思います。基本的な流れは上図のようなフローになるでしょう。シンプルに考えると終了部分以外のスタートボタン・一時停止・再開・リセットの各ボタンを用意し、それぞれの機能をプログラムしていけばシンプル。

しかし、それでは記述量が多くなりますので、今回はスタートボタンの中に一時停止と再開の機能を同封。その結果二つのボタンでタイマー制御が可能に。

まずは二つのボタンを操作できるプログラムを書いておきましょう

mButtonStartPause.setOnClickListener({
スタートボタンの中身
});

getmButtonReset.setOnClickListener({
リセットボタンの中身
});

タイマープログラムのメソッド(機能)

次にタイマーアプリで必要な機能の部分を確認しておきます。

  • タイマー開始
  • 一時停止
  • リセット
  • タイマー表示

これらの機能をプログラムで書くと以下のようにあるでしょう。

private void startTimer(){
// タイマー実行

private void pauseTimer(){
// 一時停止
}

private void resetTimer(){
// リセット
}

private void updateCountDownText(){
// 時刻の表示
}
ここまでの MainActivity.java

package com.example.timerapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private static final long START_TIME = 10000;

    private  TextView mTextViewCountDown;
    private  Button mButtonStartPause;
    private  Button getmButtonReset;

    private  CountDownTimer mCountDownTimer;
    private  boolean mTimerRunning;

    private long mTimeLeftInMillis = START_TIME;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextViewCountDown = findViewById(R.id.text_view_countdown);
        mButtonStartPause = findViewById(R.id.button_start_pause);
        getmButtonReset = findViewById(R.id.buttonreset);

        mButtonStartPause.setOnClickListener({
        });

        getmButtonReset.setOnClickListener({
        });

        updateCountDownText();
    }

    private void startTimer(){
    }

    private void pauseTimer(){
    }

    private void resetTimer(){
    }

    private void updateCountDownText(){
    }
}

あとは各イベントの内容、それから各機能の内容をプログラムで書いていけばいいですね。

スタートボタンをプログラム

mButtonStartPause.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        Sysm.out.println(mTimerRunning);
        if (mTimerRunning) {
            pauseTimer();
        } else {
            startTimer();
        }
    }
});

こちらではスタートボタンに関する振る舞いをプログラム。 まず冒頭の mButtonStartPause.setOnClick・・・・・ についてご説明します。

mButtonStartPauseは、 id接続時に設定した

mButtonStartPause = findViewById(R.id.button_start_pause);

のことですね。つまりスタートボタンの意味。そして setOnClickListener() は、 Viewクラスの機能*で、複数のボタンの扱う時に使用。そして setOnClickListener() のカッコ内の

new View.OnClickListener(){}

これは new演算子。 Viewクラスの OnClicklisterner() を呼び出しています。OnClicklisterner()は、 タップイベントの onClick() メソッド使用時に必要。

タップイベント onClick() については、

@Override
public void onClick(View v) {
    Sysm.out.println(mTimerRunning);
    if (mTimerRunning) {
        pauseTimer();
    } else {
        startTimer();
    }
}

まず冒頭の @override は、スーパークラス(親クラス)のメソッドを上書きする時に必要。今回は、 メソッド onClick() を上書きしていきますね。

そして onClick() 内の 「View v」。これはちょっと分かりにくいですよね。理解への道のりとして、まず 1つは決まった書式ということ。 androidの 公式サイト にもそんなもんだ的な記述が。

image

そしてそれは Java のメソッド宣言に基づいた記述方法。

【Javaメソッド宣言】

戻り型 メソッド名 (引数型 引数名) {
  メソッド本体
};

とりあえず publick void と一緒の感覚で、「View v」 も定型文として覚えておいてもいいかと思います。

そしてスタートボタンがタップされた時の振る舞いは、 if文で制御。

if (mTimerRunning) {
    pauseTimer();
} else {
    startTimer();
}

まず mTimerRunning が一つのキーポイントになってきます。これはアクセス修飾子でもセットした真偽値。 mTimerRunning という名称から Java のタイムクラスの何かと想像するかもしれないんですが、これは全くの任意の値。

この mTimerRunning の真偽は、スタートボタンがタップされるたびに true ・ false が切り替えるようになります。 アプリを起動したばかりの状態では、 mTimerRunning は faulse ですが、スタートボタンがタップされていないのでタイマーは止まった状態。

一度タップしても mTimerRunning は変わることなく、 startTimer() メソッドが実行されて、タイマースタート。この mTimerRunning の様子は、エミュレーター実行時に確認できるようプリント文を追加してます。

【mTimerRunningの真偽が変わる様子】

image

リセットボタンをプログラム

getmButtonReset.setOnClickListener(new View.OnClickListener(){
   @Override
   public void onClick(View v){
       resetTimer();
   }
});

リセットボタンがタップされた時の振る舞いについては、これから作成する resetTimer() メソッドを実行するのみとしました。

タイマー時刻の表示をプログラム

image

private void updateCountDownText(){
    int minutes = (int)(mTimeLeftInMillis/1000)/60;
    int seconds = (int)(mTimeLeftInMillis/1000)%60;
    String timerLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
    mTextViewCountDown.setText(timerLeftFormatted);
}

本プログラムで登場する時間のデフォルト単位は、ミリセカンド。ミリセカンドのままではタイマーの経過状況が見にくいので、こちらでミリセカンドを「分」と「秒」に変換して見やすいような値を出力したいと思います。

int minutes = (int)(mTimeLeftInMillis/1000)/60;

まず分(minutes)の演算内容ですが、初期設定値をミリセカンドからセカンドに変換して、そして60 で割ることによって分に換算。

int seconds = (int)(mTimeLeftInMillis/1000)%60;

秒(seconds)については初期設定値のミリセカンドをセカンドに変換して、そして60で割った余りを秒として記録。

あとは、分と秒の表示形式を二桁で整えたいために、 format メソッドを使って整形してます。最後にsetText()メソッドで 二桁に整形した時間をテキスト部品の mTextViewCountDown に表示。

スタート・メソッドをプログラム

image

private void startTimer(){
    mCountDownTimer = new CountDownTimer(mTimeLeftInMillis,1000) {
        @Override
        public void onTick(long millisUntilFinished) {
            mTimeLeftInMillis = millisUntilFinished;
            updateCountDownText();
        }
        @Override
        public void onFinish() {
            mTimerRunning = false;
            mButtonStartPause.setText("スタート");
            getmButtonReset.setVisibility(View.INVISIBLE);
        }
    }.start();

    mTimerRunning = true;
    mButtonStartPause.setText("一時停止");
    getmButtonReset.setVisibility(View.INVISIBLE);
}

アプリが起動して最初にスタートボタンが押されたときに実行される startTimer() メソッド に関するプログラムをご紹介。

こちらでは主に Android の CountDownTimerクラスを使ってタイマー機能を実装。

CounrDownTimerクラスの公式ドキュメント

まず時刻のカウントダウンについては 、new で CountDownTimer() を呼び出し。カッコ内は、リファレンス通り、 開始時間とカウントダウンの幅を設定。

開始時刻はアクセス修飾子で設定した

private long mTimeLeftInMillis = START_TIME;

今回は 10秒(10000)ですね。カウントダウンの単位はミリセカンドなので、 1000とすると1秒ずつカウントダウンされるようになります。

あとは CountDownTimerクラス内の onTick()メソッドで上記設定内容を刻んでくれればカウントダウンされますので、その内容を記述すると下記のように。最後の updateCountDownText(); で画面に 『スタート時刻 ー 刻んだ時刻』 の結果を出力。

@Override
public void onTick(long millisUntilFinished) {
    mTimeLeftInMillis = millisUntilFinished;
    updateCountDownText();
}

あと自分でプログラムを書いた方なら分かると思いますが、 CountDownTimer() クラス記述時に onFinish() メソッドも自動的にコード表示されたと思います。これはタイマーの値が0になった時の振る舞いをプログラムしておく必要があるため。

今回はシンプルにボタンの表示を初期値に戻すのみとしています。

【タイマーが0になった時の振る舞い】

@Override
public void onFinish() {
    mTimerRunning = false;
    mButtonStartPause.setText("スタート");
    getmButtonReset.setVisibility(View.INVISIBLE);
}

一時停止メソッドのプログラム

image

private void pauseTimer(){
    System.out.println("一時停止処理前:" + mTimerRunning);
    mCountDownTimer.cancel();
    mTimerRunning = false;
    System.out.println("一時停止処理後:" + mTimerRunning);
    mButtonStartPause.setText("スタート");
    getmButtonReset.setVisibility(View.VISIBLE);
}

こちらはタイマー実行中に、一時停止ボタンが押された場合の振る舞いを示した内容になります。

まず最初にスタートボタンが押されたときに実行されていた mCountDownTimer を中止。そして mTimerRunning の真理値を強制的に false にセット。これは一時停止の状態から再びスタートボタンが押されたときカウントダウンを開始するために mTimerRunning の真偽値を変更しています。

分かりにくい mTimerRunning については、一時停止メソッドの実行前と実行あとにプリント文を入れて 、 mTimerRunning の真偽の値をチェック。

あとボタンの表示内容については、今回はリセットボタンが表示されるように setVisibility() で VISIBLE をセット。これで最初は表示されていなかったリセットボタンが、一時停止時に出てくるようになります。

リセット・メソッドのプログラム

image

private void resetTimer(){
    mTimeLeftInMillis = START_TIME;
    updateCountDownText();
    mButtonStartPause.setVisibility(View.VISIBLE);
    getmButtonReset.setVisibility(View.INVISIBLE);
}

こちらはリセットボタンが押されたときの振る舞いをプログラム。 まず今まで刻まれてきた mTimeLeftMillis の値を時刻初期値の START_TIME にセット。そしてリセットした時刻の内容を画面上に updateCountDownText() で表示。あとボタンについてはアプリ起動初期時のスタートボタンのみ表示するように設定しています。

最終的な MainActivity.java

package com.example.timerapp2;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.TextView;
import android.widget.Button;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    private static final long START_TIME = 10000;

    private  TextView mTextViewCountDown;
    private  Button mButtonStartPause;
    private  Button getmButtonReset;

    private  CountDownTimer mCountDownTimer;
    private  boolean mTimerRunning;

    private long mTimeLeftInMillis = START_TIME;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        System.out.println("mTimerRunningの初期値は? " + mTimerRunning);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextViewCountDown = findViewById(R.id.text_view_countdown);
        mButtonStartPause = findViewById(R.id.button_start_pause);
        getmButtonReset = findViewById(R.id.buttonreset);

        mButtonStartPause.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                System.out.println("mTimerRunningの値は? " +mTimerRunning);
                if (mTimerRunning) {
                    pauseTimer();
                } else {
                    startTimer();
                }
            }
        });

        getmButtonReset.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                resetTimer();
            }
        });

        updateCountDownText();
    }

    private void startTimer(){
        mCountDownTimer = new CountDownTimer(mTimeLeftInMillis,1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                mTimeLeftInMillis = millisUntilFinished;
                updateCountDownText();
            }

            @Override
            public void onFinish() {
                mTimerRunning = false;
                mButtonStartPause.setText("スタート");
                getmButtonReset.setVisibility(View.INVISIBLE);
            }
        }.start();

        mTimerRunning = true;
        mButtonStartPause.setText("一時停止");
        getmButtonReset.setVisibility(View.INVISIBLE);
    }

    private void pauseTimer(){
        System.out.println("一時停止処理前のmTimerRunningは? " + mTimerRunning);
        mCountDownTimer.cancel();
        mTimerRunning = false;
        System.out.println("一時停止処理後のmTimerRunningは? " + mTimerRunning);
        mButtonStartPause.setText("スタート");
        getmButtonReset.setVisibility(View.VISIBLE);
    }

    private void resetTimer(){
        mTimeLeftInMillis = START_TIME;
        updateCountDownText();
        mButtonStartPause.setVisibility(View.VISIBLE);
        getmButtonReset.setVisibility(View.INVISIBLE);
    }

    private void updateCountDownText(){
        int minutes = (int)(mTimeLeftInMillis/1000)/60;
        int seconds = (int)(mTimeLeftInMillis/1000)%60;
        String timerLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
        mTextViewCountDown.setText(timerLeftFormatted);
    }
}

以上が基本的なタイマーアプリのプログラムになります。それでは実際にエミュレーターを実行しましょう。

タイマーアプリを実行

image

きちんとタイマーアプリとして、カウントダウン・一時停止・リセットが起動していることが確認できます。 またアプリの稼働状況をチェックしていますと、なかなか説明のつかなかった真偽値 mTimerRunning の様子も確認することが可能。スタートボタンがタップされる度に、真偽が反転してることを確認できますね。

今回のタイマーアプリのプロジェクトファイル
Android-Simple-Timer

まとめ

多くの方が当たり前のように使っているタイマーアプリ。実際に作ってみるといかがでしたでしょうか?

今回はアンドロイドアプリの参考書を開いても前に進まない方の少しの役にでも立てればと思い、動画を含めコンテンツ作成させていただいた次第です。

Java 自体も初心者、 Android 開発自体も初心者という立場であればなかなか少しずつ前に進むというのも難しいのではないでしょうか?

しかし、学習環境に目を向けてみると、独学の場合は、夜眠くなっても頑張っておかないと前に進まないし、と思ってムリをして、疲れをためておられるのではないでしょうか?

一方、プログラミング学習のサービスを利用している方は、分からないと決めたら後は無理をせず休んで大丈夫。翌日にでもスクールの先生に聞いて確認することはできます。

この場合、体力の低下も少なくモチベーションも維持したままアプリ開発の勉強を進めることができるでしょう。一方独学で励まれておられる方の中には無理をして疲れをため、モチベーションが下がり、挫折してしまうケースも少なくないと思います。

このように 『アプリ開発』 という最終目的を達成するのであれば、プログラミング・スクールという選択肢も今一度考えてもいいのではないでしょうか?

コードキャンプでは、「時」と「場所」の制約を極力減らしたオンライン形式のプログラミング・レッスンを提供中。

オンラインのプログラミングレッスンが未体験の方は、一度無料お試しされてみてはいかがでしょうか?

オシママサラ
この記事を書いた人
オシママサラ
\ 無料体験開催中!/自分のペースで確実に習得!
オンライン・プログラミングレッスンNo.1のCodeCamp