2015年3月10日火曜日

【Android】【CountDownTimer】 一時停止の仕組みを改善する その①

先日の投稿で作成したタイマーアプリですが、一時停止の仕組みは以下のようになっています。

① [スタート] 初期値(ミリ秒)をセットして、CountDownTimer をインスタンス化 ⇒ スタート。
② [動作中] onTick毎にtextViewに残り時間を表示。この際、「秒」に換算して表示。
③ [一時停止] CountDownTimer をキャンセル。
④ [再スタート] textViewに表示されている時間(秒)をミリ秒に換算し、新たにCountDownTimer
   をインスタンス化 ⇒ スタート

秒 ⇔ ミリ秒 の変換は、1000 を 乗 or 除算しています。

ここで問題になるのは、一時停止の際に、 1秒以下の値が切り捨てられることです。

より具体的に説明すると、

残り時間(millisUntilFuture) 5999 ミリ秒の時、残り時間は「秒」に換算(1000で除算)されて、画面には 5 と表示されます。

この時一時停止 → 再スタートすると、新たなCountDownTimerは、画面に表示されている値 5 に 1000 を掛けて、残り時間 5000ミリ秒 でスタートします。

つまり、一時停止 → 再スタートで、最大約1秒の程度の切り捨てが発生してしまうのです。

用途にもよりますが、これはイマイチだなと思い、一時停止の仕組みを改善する事にしました。

改善策① 画面表示する単位を変更する。


大変単純な案です。切り捨ての原因になる単位の変換をしない、またはする範囲を狭めます。

例えば、ミリ秒まで画面に表示するようにすると、先ほどの5999はそのままの値で画面に記録され、そのままの値で新たな CountDownTimer に利用できます。

ミリ秒まで使わなくても、一桁、二桁でも1秒以下の単位を増やすと、その分精度が上がります。

例えば、画面上の表示を [ 5.86 ] みたいに、下2桁まで表示してストップウォッチ風にするというのも面白そうです。

どうしても秒単位で表示にしたい場合は、.86 のテキストビューだけ表示を透明にすれば良いのではないでしょうか。(ちょっと場当たり的な感じがしますが。)

ストップウォッチ風の表示にするため、以下の様にコードを変更しました。



activity_main.xml


 ・ 1/10, 1/100秒を表示するためのテキストビュー(textView2)を追加
 ・ 区切りの“.”を表示するテキストビュー(textView3)を追加
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="15"
        android:id="@+id/textView"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="00"
        android:id="@+id/textView2"
        android:layout_alignTop="@+id/textView"
        android:layout_toRightOf="@+id/textView3" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="."
        android:id="@+id/textView3"
        android:layout_centerVertical="true"
        android:layout_alignTop="@+id/textView"
        android:layout_toRightOf="@+id/textView" />

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New ToggleButton"
        android:id="@+id/toggleButton"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true" />
</RelativeLayout>

MainActivity.java


 ・ textView2に、1/10, 1/100秒の桁を表示する処理を追加
  ⇒ カウントダウンタイマーのonTick()メソッドに、次の処理を追加

    textView2.setText(String.valueOf((millisUntilFinished % 1000) / 10));

    millisUntilFinished % 1000
    ⇒ millisUntilFinished(残り時間) % 1000 を秒(1000ミリ秒)で割った余り。
       秒の桁を捨てます。
    (例) 5678 % 1000 = 678 (ミリ秒)

    さらに10で割ることで、1/1000 秒の桁を捨てています。
    (例) 678 / 10 = 67 (10ミリ秒)

 ・ カウントダウンタイマーをスタートする際、textView2からも値を取得して、残り時間をセットする
    処理を追加

  String time1 = ((TextView) findViewById(R.id.textView)).getText().toString();
  String time2 = ((TextView) findViewById(R.id.textView2)).getText().toString();
  myCountDownTimer = new MyCountDownTimer(Integer.parseInt(time1) * 1000
   + Integer.parseInt(time2) * 10, 10);

  ⇒ time1、time2 の値をミリ秒に戻して、合計した値を残り時間にセットしています。

 ・ インターバル時間を10ミリ秒に設定
  ⇒ここの値が大きいと、textView2の表示がガックガックになります。

  なお、先日投稿した様に、インターバルの処理には10~20ミリ秒の誤差があります。
  したがって、(早すぎて見えませんが)1/100 秒の桁はところどころ数字が抜けていると
  思われます。ストップウォッチの精度としては、1/10 秒までが限界かと思います。

 ・ タイマー終了時に、textView1、textView2にそれぞれ"0", "00" をセット

import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.ToggleButton;

public class MainActivity extends ActionBarActivity {

    static MyCountDownTimer myCountDownTimer;

    // CountDownTimerクラスを継承して、MyCountDownTimerを定義
    class MyCountDownTimer extends CountDownTimer {

        TextView textView = (TextView)findViewById(R.id.textView);
        TextView textView2 = (TextView)findViewById(R.id.textView2);
        ToggleButton toggleButton = (ToggleButton)findViewById(R.id.toggleButton);

        public MyCountDownTimer(long millisInFuture, long countDownInterval) {
            super(millisInFuture, countDownInterval);
        }

        // カウントダウン処理
        @Override
        public void onTick(long millisUntilFinished) {
            textView.setText(String.valueOf(millisUntilFinished / 1000)); // 秒の桁を表示
            textView2.setText(String.valueOf((millisUntilFinished % 1000) / 10)); // 10,100分の1秒の桁を表示
        }

        // カウントダウン終了後の処理
        @Override
        public void onFinish() {
            toggleButton.setChecked(false); // toggleボタンをオフにする
            ((TextView)findViewById(R.id.textView)).setText("0");
            ((TextView)findViewById(R.id.textView2)).setText("00"); // テキストビューに00を表示
        }
    }

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

        // トグルボタンをタップした時の処理
        ((ToggleButton)findViewById(R.id.toggleButton)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

                // トグルON
                if (isChecked) {
                    String time1 = ((TextView) findViewById(R.id.textView)).getText().toString(); // textViewの文字列を取得して、timeに格納
                    String time2 = ((TextView) findViewById(R.id.textView2)).getText().toString(); // textView2の文字列を取得して、timeに格納
                    myCountDownTimer = new MyCountDownTimer(Integer.parseInt(time1) * 1000 + Integer.parseInt(time2) * 10, 10); // time1,time2をミリ秒換算して合計し,その値をセットしたmyCountDownTimerをインスタンス化
                    myCountDownTimer.start(); // タイマーをスタート

                    // トグルOFF
                } else {
                    myCountDownTimer.cancel(); // タイマーをストップ
                }
            }
        });

        // トグルボタンをロングタップした時の処理
        ((ToggleButton)findViewById(R.id.toggleButton)).setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                myCountDownTimer.cancel(); // タイマーをストップ
                ((TextView)findViewById(R.id.textView)).setText("15"); // テキストビューに初期値をセット
                ((TextView)findViewById(R.id.textView2)).setText("00"); // テキストビューに初期値をセット
                ((ToggleButton)findViewById(R.id.toggleButton)).setChecked(false); // toggleボタンをオフにする
                return true;
            }
        });
    }
}
(関連)
【Android】【CountDownTimer】カウントダウンタイマーを、1つのボタンで開始、一時停止、リセットする。
【Android】【CountDownTimer】 0秒まで表示する (インターバルの挙動を調べてみる)
【Android】【CountDownTimer】 一時停止の仕組みを改善する その②

0 件のコメント:

コメントを投稿