marunomaruno-memo

marunomaruno-memo

Android ダイアログ (4) プログレスダイアログ

2011年08月15日 | Android
Android ダイアログ (4) プログレスダイアログ
================================================================================

Android 端末が時間のかかる処理を行っているときに、ユーザーが勘違いして戻るボタン
を押したり、端末を再起動しないように、処理待ちのダイアログを表示させることができ
る。

処理待ちのダイアログの表示は、本当に処理待ちだけを表すものと、進捗率を表示するも
のの 2 種類ある。どちらも同じ ProgressDialog クラスのオブジェクトとして作る。

処理待ちのサンプル
Dialog05    シンプルなもの
Dialog051   キャンセルボタンつきのプログレスダイアログ
Dialog052   プログレスダイアログと作業用のスレッドを別にする

進捗つきのプログレスダイアログ
Dialog06    シンプルなもの
Dialog061   プログレスダイアログと作業用のスレッドを別にする


■ 処理待ちのダイアログ

まずは、処理待ちのダイアログ。
処理待ち、進捗つきのどちらにも共通するが、基本的な使い方は以下のとおり。
(1) オブジェクトを作って、
    ProgressDialog progressDialog = new ProgressDialog(this);
(2) スタイル(処理待ちか進捗つき)を設定して、
    progressDialog.setProgressStyle(スタイル);
(3) 表示する。
    progressDialog.show();

そして、プログレスダイアログを表示している間に行う処理は、別スレッドとして定義し
て、それを起動する。
        Thread worker = new Thread(this);
        worker.start();

□ Dialog05Activity.java
---
package jp.marunomaruno.android.sample;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;

public class Dialog05Activity extends Activity implements Runnable {    // (1)

    private ProgressDialog progressDialog;                              // (2)

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        progressDialog = new ProgressDialog(this);                      // (3)
        progressDialog.setTitle(R.string.dialogTitle);
        progressDialog.setMessage(getString(R.string.dialogMessage));
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);  // (4)
//        progressDialog.setCancelable(false);    // デフォルトはtrue   // (5)
        progressDialog.show();

        Thread worker = new Thread(this);                               // (6)
        worker.start();
    }

    public void run() {                                                 // (7)
        // ダイアログを出している間に行う処理                           // (8)
        try {
            Thread.sleep(6 * 1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // ダイアログを消去する
        progressDialog.dismiss();                                       // (9)
    }
}
---

□ public class Dialog05Activity extends Activity implements Runnable { // (1)

処理待ちダイアログを表示するということは、ダイアログを表示している間に、別のスレ
ッドが何かしらの作業をすることになる。その作業部分は、別スレッドで行うので、この
サンプルでは、自分自身に Runnable インターフェースを実装し、run() メソッドを実装
して作る。
もちろん、別スレッドにするオブジェクトは、別クラスで作ることも可能である。


□ private ProgressDialog progressDialog;                              // (2)

作業しているスレッドと、この ProgressDialog オブジェクトを共有したいので、ここで
はインスタンス変数として定義する。

・ProgressDialogの階層

java.lang.Object
   + android.app.Dialog
         + android.app.AlertDialog
               + android.app.ProgressDialog


・おもなメソッド(おもに STYLE_SPINNER で使うかな、と思われるもの)
---
void  onStart()
void  setMessage(CharSequence message)
void  setProgressStyle(int style)
---

▼ ProgressDialog クラスには、staticなshow() メソッドがあるが、API 上にはなんの
記述もない。cancelListener が引数にないものはどうやって終わらせるのかな?  スー
パークラスを含めて、static メソッドはこれしかない。


□ progressDialog = new ProgressDialog(this);                        // (3)

ProgressDialogのオブジェクトを生成する。
自分自身の画面に表示するので、context は自分自身(this)。

・コンストラクター
---
ProgressDialog(Context context)
ProgressDialog(Context context, int theme)
---

なお、ProgressDialog には、AlertDialog のように、Builder クラスがないので、メソ
ッド・チェーンのようなことはできない。

□ progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);    // (4)

ダイアログの表示形式を指定する。表示形式は ProgressDialog クラスの定数として定義
してある。
---
int STYLE_HORIZONTAL  進捗バー
int STYLE_SPINNER     処理待ち
---


□ progressDialog.setCancelable(false);    // デフォルトはtrue       // (5)

プログレスバーを表示しているときに、処理をキャンセルできるかどうかを指定する。
デフォルトは、「戻る」ボタンでキャンセルできる。キャンセルしたくない場合は、
setCancelable(false)
で指定する。


□ Thread worker = new Thread(this);                                 // (6)
   worker.start();

処理待ちしている間の処理を記述するスレッドを起動する。


□ public void run() {                                               // (7)

処理待ちしている間の処理を記述する。


□ // ダイアログを出している間に行う処理                             // (8)

ここに、ダイアログを出している間に行う処理を記述する。
今回は、単に5秒間スリープするだけ。


□ progressDialog.dismiss();                                         // (9)

処理が終わったら、ダイアログを消去する。


□ values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, Dialog05Activity!</string>
    <string name="app_name">Dialog05Activity</string>
    <string name="dialogTitle">処理中...</string>
    <string name="dialogMessage">しばらくお待ちください</string>
</resources>
---


■ キャンセルボタンつきのプログレスダイアログ

デフォルトでは、「戻る」ボタンでキャンセルできる。
ただし、この操作を禁止して、ダイアログ上にキャンセルボタンを明示して、それを使わ
せることもできる。

このサンプルでは、ボタンのリスナー、キャンセルされたときのリスナーを無名インナー
クラスで実装している。


□ Dialog051Activity.java
---
package jp.marunomaruno.android.sample;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.Bundle;

public class Dialog051Activity extends Activity implements Runnable {

    private ProgressDialog progressDialog;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final Thread worker = new Thread(this);                         // (1)

        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle(R.string.dialogTitle);
        progressDialog.setMessage(getString(R.string.dialogMessage));
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        progressDialog.setCancelable(false);                            // (2)
        progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,       // (3)
                getString(R.string.cancelButtonTitle),
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel(); // ダイアログをキャンセルする
                    }
                });
        progressDialog
                .setOnCancelListener(new DialogInterface.OnCancelListener() {   
 // (4)
                    public void onCancel(DialogInterface dialog) {
                        worker.stop(); // スレッドを停止する
                    }
                });

        progressDialog.show();

        worker.start();
    }

    public void run() {
        // ダイアログを出している間に行う処理
        try {
            Thread.sleep(5 * 1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        progressDialog.dismiss();    // ダイアログを消去する
    }
}
---

□ final Thread worker = new Thread(this);                         // (1)

今回は無名インナークラスから、このスレッドのオブジェクトを使うので、final 指定す
る。


□ progressDialog.setCancelable(false);                            // (2)

プログレスバーを表示しているときに、処理をキャンセルできなくする。
こうすることで、自分で表示したボタンを使わせることができる。


□ progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,       // (3)
        getString(R.string.cancelButtonTitle),
        new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                dialog.cancel(); // ダイアログをキャンセルする
            }
        });


キャンセルボタンを設定する。

・ボタンを設置するメソッド
---
void  setButton(int whichButton, CharSequence text, 
                DialogInterface.OnClickListener listener)
void  setButton(int whichButton, CharSequence text, Message msg)
---

引数
    int whichButton     ボタンの種類。ボタンの種類は、DialogInterface インターフ
						ェースの定数 BUTTON_NEGATIVE、BUTTON_NEUTRAL、
						BUTTON_POSITIVE を使う。
    CharSequence text   ボタンに表示する文字列
    DialogInterface.OnClickListener listener    
                        ボタンをクリックしたときのリスナー
    Message msg         ボタンをクリックしたときに送信するメッセージ

ここでは、キャンセルボタンが押されたら、次のメソッドでダイアログをキャンセルする。
    dialog.cancel(); // ダイアログをキャンセルする

このメソッドにより、DialogInterface.OnCancelListener の onCancel() メソッドが動
作する。

なお、setPositiveButton() のようなメソッドは、AlertDialog.Builder クラスのメソッ
ドで、ProgressDialog、AlertDialog、Dialog クラスにはない。したがって、
setButton() を使って、ボタンの種類も引数で指定する。


□ DialogInterface.OnClickListener

ボタンがクリックされたときの動作を規定するリスナー。
宣言されているメソッドは、以下のもの。

abstract void  onClick(DialogInterface dialog, int which)

このメソッドは、AlertDialogのところで説明している。


□ progressDialog
        .setOnCancelListener(new DialogInterface.OnCancelListener() {    // (4)
            public void onCancel(DialogInterface dialog) {
                worker.stop(); // スレッドを停止する
            }
        });

ダイアログがキャンセルされたときのリスナーを設定する。
ここでは、stop() メソッドにより、スレッドを停止させている。


□ DialogInterface.OnCancelListener

ダイアログがキャンセルされたときのリスナー。
宣言されているメソッドは、以下のもの。

abstract void  onCancel(DialogInterface dialog)


□ values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, Dialog051Activity!</string>
    <string name="app_name">Dialog051Activity</string>
    <string name="dialogTitle">処理中...</string>
    <string name="dialogMessage">しばらくお待ちください</string>
    <string name="cancelButtonTitle">キャンセル</string>
</resources>
---


■ 作業用のスレッドを別クラスにする

ここまでのサンプルは、作業用のスレッドの中に、次のようなプログレスダイアログを直
接制御する文が入っていた。
        progressDialog.dismiss();    // ダイアログを消去する

ただ、これはあまり望ましいことではない。
このサンプルは、直接、プログレスダイアログを制御するのではなく、間接的にプログレ
スダイアログを制御するようにする。
そのために、Android では、メッセージというものを使って、スレッド間で制御ができる
ようにしている。

□ Dialog052Activity.java
---
package jp.marunomaruno.android.sample;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

public class Dialog052Activity extends Activity {

    private ProgressDialog progressDialog;
    private Handler progressDialogHandler;                              // (1)

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        progressDialogHandler = new PprogressDialogHandler();           // (2)

        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle(R.string.dialogTitle);
        progressDialog.setMessage(getString(R.string.dialogMessage));
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        progressDialog.show();

        Thread worker = new Thread(new WorkerThread());                 // (3)
        worker.start();
    }

    /**
     * メッセージを受け取ったときの動きを規定するハンドラー
     */
    private class PprogressDialogHandler extends Handler {              // (4)
        @Override
        public void handleMessage(Message msg) {                        // (5)
            // プログレスダイアログを終了する
            progressDialog.dismiss();
            progressDialog = null;
        }
    }

    /**
     * 作業をするスレッド
     */
    private class WorkerThread implements Runnable {
        public void run() {
            try {
                Thread.sleep(6 * 1000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 作業終了時に、プログレスダイアログを終了するようにハンドラーを呼ぶ
            progressDialogHandler.sendEmptyMessage(-1);                 // (6)
        }
    }

}
---

□ private Handler progressDialogHandler;                            // (1)

Handler クラスのインスタンス変数を宣言する。

java.lang.Object
   + android.os.Handler

Handler クラスは、マルチスレッドで、スレッド間でメッセージをやり取りするのを実現
するためのクラス。

詳細は、以下のURLを参照のこと。
Android の Handler とは何か?
http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html
---
・AndroidのUI操作はシングル・スレッド モデル
・ユーザビリティ向上の為にはマルチスレッドが必要
・Handlerで実現
・Handlerを使わない場合に起きる例外は実行スレッドのチェックで発生
・Handlerを使うと、UI Threadの持つキューにジョブを登録できる
・キューはUI Threadにより実行される
・別スレッドからUI Threadに処理を登録するのでスレッドチェックで例外が発生しない
---

・メッセージを送るメソッド
---
final boolean  post(Runnable r)
final boolean  postAtFrontOfQueue(Runnable r)
final boolean  postAtTime(Runnable r, Object token, long uptimeMillis)
final boolean  postAtTime(Runnable r, long uptimeMillis)
final boolean  postDelayed(Runnable r, long delayMillis)

final boolean  sendEmptyMessage(int what)
final boolean  sendEmptyMessageAtTime(int what, long uptimeMillis)
final boolean  sendEmptyMessageDelayed(int what, long delayMillis)

final boolean  sendMessage(Message msg)
final boolean  sendMessageAtFrontOfQueue(Message msg)
boolean        sendMessageAtTime(Message msg, long uptimeMillis)
final boolean  sendMessageDelayed(Message msg, long delayMillis)
---

・メッセージを受け取るメソッド
---
void  handleMessage(Message msg)
---


□ progressDialogHandler = new PprogressDialogHandler();           // (2)
□ private class PprogressDialogHandler extends Handler {          // (4)
□ public void handleMessage(Message msg) {                        // (5)

メッセージを受け取ったときの動きを規定するハンドラー・クラス(内部クラス)のオブジ
ェクトを生成する。
そのクラスは、Handlerクラスのサブクラスとして作る。
オーバーライドするメソッドは、つぎのメソッド。
    void  handleMessage(Message msg)
このメソッドで、メッセージを受け取る。
今回は、プログレスダイアログを終了している。


□ Thread worker = new Thread(new WorkerThread());                // (3)
□ progressDialogHandler.sendEmptyMessage(-1);                    // (6)

作業用のスレッドのオブジェクトを生成する。
作業用スレッドでは、単純に、作業だけのことを記述している。
作業が終わったときに、終了したよ、というメッセージをハンドラーに送信しているだけ
なので、このスレッドでは、プログレスダイアログについてはなにも知らなくてよい。
今回は、sendEmptyMessage(-1) として送信している。
引数としての -1 は、任意の値。この値は、Handler.handleMessage(Message msg) の引
数のMessageオブジェクトのインスタンス変数 what に格納されている。
Messageクラスについては、もう少し後 (Dialog061 アプリ)で記する。


■ 進捗つきのプログレスダイアログ

プログレスダイアログとして、プログレスバーで進捗率がわかるもののサンプル。

作業と、プログレスバーの進捗率の設定が分かれていないバージョン。

ProgressDialog クラスを使うのは一緒で、そのスタイルを、STYLE_SPINNER ではなく、
STYLE_HORIZONTAL にすることで実現する。
なお、進捗率は、setProgress() メソッドで指定する。

このサンプルは、0.5 秒ごとに進捗率を 10% ずつ上げていく。5 秒で 100% となって終
了する。


□ Dialog06Activity.java
---
package jp.marunomaruno.android.sample;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;

public class Dialog06Activity extends Activity {

    private ProgressDialog progressDialog;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle(R.string.dialogTitle);
        progressDialog.setMessage(getString(R.string.dialogMessage));
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // (1)
        progressDialog.show();
        new Thread(new WorkerThread()).start();                           // (2)
    }
    
    /**
     * 作業しながらプログレスバーを出すスレッド
     */
    private class WorkerThread implements Runnable {
        public void run() {
            try {
                for (int i = 0; i <= 10; i++) {
                    Thread.sleep(500);            // 0.5秒
                    progressDialog.setProgress(i * 10);                   // (3)
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            progressDialog.dismiss();
        }
    }
}
---

□ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);    // (1)

ProgressDialog クラスのおもなメソッド(おもに STYLE_HORIZONTAL で使うかな、と思
われるもの)
---
int   getMax()
void  incrementProgressBy(int diff)
void  setMax(int max)
void  setMessage(CharSequence message)
void  setProgress(int value)
void  setProgressNumberFormat(String format)
void  setProgressPercentFormat(NumberFormat format)
---

進捗率の最大値は、setMax() で指定する。0 から 10000 まで指定できる。デフォルトの
最大値は 100。
これに合わせて、setProgress() で現在の進捗率を指定すると、プログレスバーの進捗率
が、% で表示される。


□ new Thread(new WorkerThread()).start();                            // (2)

作業用のスレッドを生成して起動。


□ progressDialog.setProgress(i * 10);                                // (3)

0%、10%、20%、...、100% までの進捗率なので、その値を設定する。
デフォルトの最大値が100なので、そのままの%の値を指定すればよい。


■ 作業用のスレッドと別のスレッドでプログレスバーを表示する

Handlerクラスを使って、進捗率を作業用スレッドとは独立に管理するバージョン。

□ Dialog061Activity.java
---
package jp.marunomaruno.android.sample;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class Dialog061Activity extends Activity {

    private ProgressDialog progressDialog;
    private Handler progressDialogHandler;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        progressDialogHandler = new PprogressDialogHandler();

        progressDialog = new ProgressDialog(this);
        progressDialog.setTitle(R.string.dialogTitle);
        progressDialog.setMessage(getString(R.string.dialogMessage));
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progressDialog.show();
        progressDialog.setProgress(0);                               // (1)

        Thread worker = new Thread(new WorkerThread());
        worker.start();
    }

    /**
     * メッセージを受け取ったときの動きを規定するハンドラー
     */
    private class PprogressDialogHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d("TEST", msg.toString());
            
            // 値が非負なら、プログレスバーを更新する
            if (msg.what >= 0) {                                    // (2)
                progressDialog.setProgress(msg.what);               // (3)
                return;
            }
            
            // プログレスダイアログを終了する
            progressDialog.setProgress(100);                        // (4)
            progressDialog.dismiss();
            progressDialog = null;
        }
    }

    /**
     * 作業をするスレッド
     */
    private class WorkerThread implements Runnable {
        public void run() {
            try {
                for (int i = 0; i <= 10; i++) {
                    Thread.sleep(500);        // 0.5秒
                    Message msg = new Message();                    // (5)
                    msg.what = 0;                                   // (6)
                    msg.arg1 = i * 10;                              // (7)
                    progressDialogHandler.sendMessage(msg);         // (8)
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 作業終了時に、プログレスダイアログを終了するようにハンドラーを呼ぶ
            progressDialogHandler.sendEmptyMessage(-1);             // (9)
        }
    }
}
---

□ progressDialog.setProgress(0);                          // (1)

プログレスバーを表示する際に、進捗率を 0% にしておく。


□ if (msg.what >= 0) {                                    // (2)
□ progressDialog.setProgress(msg.what);                   // (3)

受け取ったメッセージを処理する。
    void handleMessage(Message msg)

今回は、メッセージに従って、進捗率を指定したり、プログレスダイアログを終了したり
している。
handleMessage(Message msg) メソッドの引数 msg のインスタンス変数 what に、sendEm
ptyMessage() メソッドで指定した値が入っている。

値が 0 以上のときは、進捗率、負数のときは、ダイアログを終了するようにしている。


□ Message クラス

java.lang.Object
   + android.os.Message

Handlerクラスを使ってメッセージを送信するときのデータのオブジェクトの型。
つぎのインスタンス変数を使ってデータを送る。
int 型のデータひとつかふたつであれば、arg1、arg2 を使えばよい。それ以外は、オブ
ジェクトを使う。
メッセージの種類は what を使う。

・おもな public インスタンス変数
---
public int     arg1   データ
public int     arg2   データ
public Object  obj    データ
public int     what   メッセージの種類
---


□ progressDialog.setProgress(100);                // (4)

処理が終わったときは、進捗率を 100 にする。


□ Message msg = new Message();                    // (5)
□ msg.what = 0;                                   // (6)
□ msg.arg1 = i * 10;                              // (7)

Messageオブジェクトに、進捗率を設定して、ハンドラーに送信する。
進捗率は、0~100 までの値なので、arg1 に設定しておけばよい。
送りたいデータが2つのint値で済まないような場合は、obj を使う。
したがって、long値を送りたい場合も、Long型のオブジェクトを作って送る必要がある。


□ progressDialogHandler.sendMessage(msg);         // (8)

Message オブジェクトを送るので、メソッドは
    final boolean sendMessage(Message msg)
を使う。


□ progressDialogHandler.sendEmptyMessage(-1);     // (9)

処理が終わったときは、what を -1 にして送る。
ここでは、what だけでよいので、sendEmptyMessage() でかまわない。

                                                                            以上