marunomaruno-memo

marunomaruno-memo

Android センサー (1) 光センサーと加速度センサー

2011年08月28日 | Android
センサー (1) 光センサーと加速度センサー
================================================================================

各種センサーを使うことができる。

■ センサーのタイプ

Sensor クラスの定数で定義してある。以下にまとめる。

センサー名           定数                        単位   値の次元  可
------------------- --------------------------- ------- -------- ------
加速度               TYPE_ACCELEROMETER          m/s^2     3      ○
重力                 TYPE_GRAVITY                m/s^2     3      ※ 
ジャイロスコープ     TYPE_GYROSCOPE              rad/s     3      × 
光                   TYPE_LIGHT                  lux       1      ○ 
線形加速             TYPE_LINEAR_ACCELERATION    m/s^2     3      ※ 
地磁気               TYPE_MAGNETIC_FIELD                   3      ○ 
圧力                 TYPE_PRESSURE               Pa        1      × 
近接                 TYPE_PROXIMITY              cm        1      ○ 
回転ベクトル         TYPE_ROTATION_VECTOR        -         4      ※ 
温度                 TYPE_TEMPERATURE            ℃        1      ○ 
傾き                 TYPE_ORIENTATION            deg       3     非推奨
(○あり、×なし、※Android2.2では対象外)


■ センサーのタイプと値の取得方法

センサーの値を取得するには、つぎの手順が必要。

1. SensorManager のオブジェクトを取得する
2. SensorManager オブジェクトにセンサーのリスナーを登録する
3. センサーの値を取得する

ここでは、値の取得について補足する。
どのセンサーでも、センサーのリスナーの void onSensorChanged(SensorEvent event) 
メソッドの event に、そのときの値が入っている。これのインスタンス変数 values が
配列になっており、センサーからの取得値の次元によって、要素数が決まる。

以下にそれぞれのセンサーについての values について補足する。


□ TYPE_ACCELEROMETER 加速度センサー
    values[0]: X 軸方向の加速度
    values[1]: Y 軸方向の加速度
    values[2]: Z 軸方向の加速度

    X 軸: 実機の横方向(右方向が正)
    Y 軸: 実機のたて方向(上方向が正)
    Z 軸: 実機の前面方向(前面方向が正)

SensorEvent クラスの API に図入りで説明がある。


□ TYPE_GRAVITY 重力センサー
※Android2.2 にはない


□ TYPE_GYROSCOPE ジャイロスコープ・センサー
    values[0]: X 軸方向の角速度
    values[1]: Y 軸方向の角速度
    values[2]: Z 軸方向の角速度


□ TYPE_LIGHT 光センサー
    values[0]: 照度


□ TYPE_LINEAR_ACCELERATION 線形加速センサー
※Android2.2 にはない


□ TYPE_MAGNETIC_FIELD 地磁気センサー
    values[0]: X 軸方向の磁気密度
    values[1]: Y 軸方向の磁気密度
    values[2]: Z 軸方向の磁気密度


□ TYPE_PRESSURE 圧力センサー

□ TYPE_PROXIMITY 近接センサー
    values[0]: 距離


□ TYPE_ROTATION_VECTOR 回転ベクトル・センサー
※Android2.2 にはない


□ TYPE_TEMPERATURE 温度センサー
APIには特に記述はないが、摂氏で取得される。


□ TYPE_ORIENTATION 傾きセンサー
ただし、この定数は非推奨になったので、このセンサーの値は、この定数を使うのではな
く、SensorManager.getOrientation() メソッドを使う。


■ 光センサーの値を表示する

光センサーの値を取得して、表示する。
    単位: lux (ルクス)
    values[0]: 照度

センサーの値を取得するには、つぎの手順が必要。

1. SensorManager のオブジェクトを取得する
2. SensorManager オブジェクトにセンサーのリスナーを登録する
3. センサーの値を取得する


これにしたがって、プログラムを作る。


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

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;

/**
 * 光センサーの値を表示する。
 * @author marunomaruno
 * @version 1.0, 2011-06-25
 */
public class LightSensor01Activity extends Activity {

    // TextView
    private TextView lightTextView; // 光センサー

    private SensorManager sensorManager;                                // (1)
    private SensorEventListener sensorEventListener; 
        // onPause()が呼ばれたときに、すべてのリスナーを解除するため    // (2)

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        lightTextView = (TextView) findViewById(R.id.sensor_value);
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);// (3)
    }

    @Override
    protected void onResume() {
        super.onResume();

        // 光センサーを取得して登録する
        sensorEventListener = new LightSensorListener(lightTextView);   // (4)

        for (Sensor sensor : sensorManager.getSensorList(Sensor.TYPE_LIGHT)) {// (5)
            sensorManager.registerListener(sensorEventListener, sensor,
                    SensorManager.SENSOR_DELAY_NORMAL);                 // (6)
        }
    }

    @Override
    public void onPause() {                                             // (7)
        super.onPause();

        // すべてのリスナーを解除する
        sensorManager.unregisterListener(sensorEventListener);          // (8)
    }
}
---


(1)(3) SensorManager を宣言する。

    private SensorManager sensorManager;                             // (1)  
    sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);// (3)

SensorManager は、センサーにアクセスするためのクラス。


・クラス階層
java.lang.Object
   + android.hardware.SensorManager

public なコンストラクターはない。オブジェクトの取得は、(3) のように、
    getSystemService(SENSOR_SERVICE)
を使う。

実際は、
    Context.getSystemService(Context.SENSOR_SERVICE)
だが、Activity クラスのサブクラスの中から取得しているため、「Context.」部分は必
要ない。


・主なメソッド
---
List<Sensor> getSensorList(int type)
boolean      registerListener(SensorEventListener listener, Sensor sensor, 
                                int rate, Handler handler)
boolean      registerListener(SensorEventListener listener, Sensor sensor, 
                                int rate)
void         unregisterListener(SensorEventListener listener, Sensor sensor)
void         unregisterListener(SensorEventListener listener)
---

メソッドとしては、次のような使い方の構成になっている。

・Android 実機から、使えるセンサーのリストを getSensorList() メソッドで取得する。
・それを、registerListener() メソッドを使って、センサーと、そのリスナーとを紐付
ける。
・センサーが必要なくなったら、unregisterListener() で、紐付けを解除する。


(2)(8) onPause()が呼ばれたときに、すべてのリスナーを解除するために、センサーのリ
スナーを登録しておくリストを宣言する。

    private SensorEventListener sensorEventListener; 
        // onPause()が呼ばれたときに、すべてのリスナーを解除するため    // (2)

リスナーの解除は、(8) にあるように、SensorManager クラスの 
unregisterListener(listener) メソッドを使う。

    sensorManager.unregisterListener(sensorEventListener);            // (8)


(4) 光センサーのリスナーを宣言する

    sensorEventListener = new LightSensorListener(lightTextView);    // (4)

光センサー・リスナーは、別クラスとして作っている。
別クラスとして作ることで、単体テストなどはしやすくなる。また、他のアプリケーショ
ンでの再利用もしやすくなる利点がある。


(5) for (Sensor sensor : sensorManager.getSensorList(Sensor.TYPE_LIGHT)) {// (5)

光センサーのディバイスを取得する。1台の実機に、複数の光センサーがついている可能
性があるので、戻り値はリストになっている。

List<Sensor> getSensorList(int type)

引数の type は、センサーの種類を表す定数で、Sensor クラスに、つぎのような定数が
定義されている。

センサー名           定数                        単位   値の次元  可
------------------- --------------------------- ------- -------- ------
加速度               TYPE_ACCELEROMETER          m/s^2     3      ○
重力                 TYPE_GRAVITY                m/s^2     3      ※ 
ジャイロスコープ     TYPE_GYROSCOPE              rad/s     3      × 
光                   TYPE_LIGHT                  lux       1      ○ 
線形加速             TYPE_LINEAR_ACCELERATION    m/s^2     3      ※ 
地磁気               TYPE_MAGNETIC_FIELD                   3      ○ 
圧力                 TYPE_PRESSURE               Pa        1      × 
近接                 TYPE_PROXIMITY              cm        1      ○ 
回転ベクトル         TYPE_ROTATION_VECTOR        -         4      ※ 
温度                 TYPE_TEMPERATURE            ℃        1      ○ 
傾き                 TYPE_ORIENTATION            deg       3     非推奨
(○あり、×なし、※Android2.2では対象外)

また、TYPE_ALL という定数があり、これですべてのセンサーのオブジェクトを取得でき
る。


Sensor クラス

・クラス階層
java.lang.Object
   + android.hardware.Sensor

・主なメソッド
---
float   getMaximumRange()
int     getMinDelay()
String  getName()
float   getPower()
float   getResolution()
int     getType()
String  getVendor()
int     getVersion()
---


(6) センサーのリスナーをセンサーに登録する

    sensorManager.registerListener(sensorEventListener, sensor,
                SensorManager.SENSOR_DELAY_NORMAL);                    // (6)

つぎのメソッドがある。
---
boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
                         Handler handler)
boolean registerListener(SensorEventListener listener, Sensor sensor, int rate)
---

今回は、光センサーに、光センサーのリスナーを登録する。登録は、紐付けしていること
になる。

rate は、SensorManager クラスでつぎの 4 つの定数が定義されている。

SENSOR_DELAY_NORMAL, SENSOR_DELAY_UI, SENSOR_DELAY_GAME, SENSOR_DELAY_FASTEST

これらは、定数の値としては 3, 2, 1, 0 なので、直接遅延時間になっているわけではな
い。

実際の遅延時間の値を確認するには、Sensor クラスに 
    int     getMinDelay()
があるが、これは API レベル 9 なので、Android2.2 では使えない。
SensorManager クラスのソースで見てみると、次のような値になっている(実際はμ秒
値)。

    SENSOR_DELAY_FASTEST      0 ms (     0 μs)
    SENSOR_DELAY_GAME        20 ms ( 20000 μs)
    SENSOR_DELAY_UI          60 ms ( 60000 μs)
    SENSOR_DELAY_NORMAL     200 ms (200000 μs)

そして、0~3 以外の値であれば、その値が直接遅延時間として採用される。


(7)(8) アクティビティがバックグランドに隠れる直前に、センサーのリスナーを解除す
る

    public void onPause() {                                            // (7)

onPause() メソッドは、アクティビティがバックグランドに隠れる直前(他のアクティビ
ティが起動、たとえば、着信があった、電話をかける)に呼ばれる。
センサーが動いていれば、その分、電池が消耗するので、隠れるときにはセンサーをいっ
たん停止させる。

    sensorManager.unregisterListener(sensorEventListener);            // (8)

解除には、
void unregisterListener(SensorEventListener listener, Sensor sensor)
void unregisterListener(SensorEventListener listener)
を使う。sensor の指定がなければ、リスナーが登録されているすべてのセンサーから解
除する。


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

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.widget.TextView;

/**
 * 光センサーのリスナー
 * @author maruno
 * @version 1.0, 2011-06-23
 * @since 1.0
 */
public class LightSensorListener implements SensorEventListener {       // (1)

    protected TextView textView;

    public LightSensorListener(TextView textView) {
        this.textView = textView;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {                    // (2)
        // 値を取得する
        float x = event.values[0];                                      // (3)

        // TextViewに表示する
        textView.setText(String.format(
                textView.getResources().getString(R.string.sensor_value_format),
 
                textView.getResources().getString(R.string.light), 
                event.sensor.getName(), 
                x, 
                textView.getResources().getString(R.string.lightUnit)
        ));
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {        // (4)
        assert true; // 何もしない
    }
}
---

(1) 光センサーのリスナーの定義

public class LightSensorListener implements SensorEventListener {       // (1)


センサーに変化があったときに使うリスナー・クラスを定義する。

SensorEventListener

センサーの値が変わったときに呼び出されるリスナーのインターフェース

・メソッド
---
abstract void onAccuracyChanged(Sensor sensor, int accuracy)    
    センサーの精度が変わったとき
abstract void onSensorChanged(SensorEvent event)                
    センサーからの値が変わったとき
---


(2)(3) センサーの値が変化したときに呼ばれるハンドラー・メソッドとそのときの値の取
得

    public void onSensorChanged(SensorEvent event) {                // (2)
    float x = event.values[0];                                      // (3)

引数の event から、値を取得する。
光センサーは、1次元の値を取得するので、values は、要素数 1 である。

SensorEvent

センサーから生成されたイベントを表すクラス。

・クラス階層
java.lang.Object
   + android.hardware.SensorEvent

・public フィールド
---
public       int      accuracy     精度
public       Sensor   sensor       このイベントを生成したセンサー
public       long     timestamp    イベントが起きたタイムスタンプ
public final float[]  values       値。配列の長さは、値の次元数
---


(4) センサーの精度が変更されると呼ばれるハンドラー・メソッド

    public void onAccuracyChanged(Sensor sensor, int accuracy) {        // (4)

通常は、このメソッドが使われることはない(らしい)。


□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
<!-- センサー値 -->    
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/light"
    android:id="@+id/sensor_value"
    />
    
</LinearLayout>
---


□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">LightSensor01</string>
    <string name="light">光</string>
    <string name="lightUnit">lux</string>
    <string name="sensor_value_format">%1$s: %2$s = %3$.2f (%4$s)</string>
</resources>
---

センサーの値は、次の形式で表示する。
    センサー名: オブジェクトの情報 = 値 (単位)


■ 加速度センサーの値を表示する

加速度センサーの値を取得して、表示する。

加速度センサーは、3 次元の値として取得する。単位は、m/s^2。
値は、SensorEvent の次のフィールドを使って取得する。
    values[0]: X 軸方向の加速度
    values[1]: Y 軸方向の加速度
    values[2]: Z 軸方向の加速度

    X 軸: 実機の横方向(右方向が正)
    Y 軸: 実機のたて方向(上方向が正)
    Z 軸: 実機の前面方向(前面方向が正)

なお、SensorEvent クラスの API に図入りで説明がある。


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

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;

/**
 * 加速度センサーの値を表示する。
 * 
 * @author marunomaruno
 * @version 1.0, 2011-06-25
 */
public class AcceleraterSensor01Activity extends Activity {

    // TextView
    private TextView acceleraterTextView; // 加速度センサー

    private SensorManager sensorManager;
    private SensorEventListener sensorEventListener; 
                // onPause()が呼ばれたときに、すべてのリスナーを解除するため

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        acceleraterTextView = (TextView) findViewById(R.id.sensor_value);
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // 加速度センサーを取得して登録する
        sensorEventListener = new AcceleraterSensorListener(
                                                    acceleraterTextView); // (1)

        for (Sensor sensor : sensorManager.getSensorList(
                    Sensor.TYPE_ACCELEROMETER)) {                         // (2)
            sensorManager.registerListener(sensorEventListener, sensor,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    @Override
    public void onPause() {
        super.onPause();

        // すべてのリスナーを解除する
        sensorManager.unregisterListener(sensorEventListener);
    }
}
---

(1)(2) 加速度センサーを取得して登録

    sensorEventListener = new AcceleraterSensorListener(acceleraterTextView);
 // (1)

加速度センサーのオブジェクトを生成する。

    for (Sensor sensor : sensorManager.getSensorList(
                            Sensor.TYPE_ACCELEROMETER)) { // (2)

加速度センサーのデバイスのオブジェクトを取得する。これには、定数 
TYPE_ACCELEROMETER を使う。


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

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.widget.TextView;

/**
 * 加速度センサーのリスナー
 * @author maruno
 * @version 1.0, 2011-06-23
 * @since 1.0
 */
public class AcceleraterSensorListener implements SensorEventListener {

    protected TextView textView;

    public AcceleraterSensorListener(TextView textView) {
        this.textView = textView;
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        // 値を取得する
        float x = event.values[0];                            // (1)
        float y = event.values[1];                            // (2)
        float z = event.values[2];                            // (3)

        // TextViewに表示する
        textView.setText(String.format(
                textView.getResources().getString(R.string.sensor_value_format),
 
                textView.getResources().getString(R.string.accelerater), 
                event.sensor.getName(), 
                x, y, z, 
                textView.getResources().getString(R.string.acceleraterUnit)
                ));
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        assert true; // 何もしない
    }
}
---

(1)(2)(3) センサーの値を取得

        float x = event.values[0];                            // (1)
        float y = event.values[1];                            // (2)
        float z = event.values[2];                            // (3)

加速度センサーは 3 次元の値を持つので、values は要素数 3 の配列になる。それぞれ、
x, y, z として取得する。


□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
<!-- センサー値 -->    
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/accelerater"
    android:id="@+id/sensor_value"
    />
    
</LinearLayout>
---


□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">AcceleraterSensor01</string>
    <string name="accelerater">加速度</string>
    <string name="acceleraterUnit">m/s^2</string>
    <string name="sensor_value_format">%1$s: %2$s = (X, Y, Z) = (%3$.2f, %4$.2f,
 %5$.2f) (%6$s)</string>
</resources>
---



コンストラクターで例外をキャッチする

2011年08月23日 | C / C++

基本クラスのコンストラクターでスローされた例外を派生クラスのコンストラクターの初
期化リストでもキャッチする
================================================================================

g++ではできるが、bcc32、VC++6では、コンパイルエラーになる。

つぎのように、派生クラスのコンストラクター部分を、初期化リストも含めて try-catch
で囲む。
この場合、初期化リストでも、コンストラクターの中身でも例外がキャッチされる。

□ ConstractorExceptionSample.cpp
---
#include <iostream>
#include <exception>
using namespace std;

class A {
public:
A() {
cout << "*A" << endl;
throw exception(); // 基本クラスで例外をスロー
}
};

class B : public A {
public:
B() try // 派生クラスのコンストラクターをtry-catchで囲む
: A() // 初期化リスト
{
cout << "*B" << endl; // コンストラクターの中身

} catch(exception &amp;e) {
throw e;
}

};

int main()
{
try {
cout << "*1" << endl;
B b;
cout << "*2" << endl;

} catch(exception &amp;e) {
cerr << e.what() << endl;
}
cout << "*3" << endl;

}
---

□ 実行結果(exception をスローした場合)
---
*1
*A
*3
St9exception <-- cerr なので、出る場所はこことは限らない
---


□ 実行結果(exception をスローしなかった場合)
---
*1
*A
*B
*2
*3
---

</pre>

Android ダイアログ (6) 日付と時間の選択ダイアログ その2/2

2011年08月22日 | Android
ダイアログ (5) 日付と時間の選択ダイアログ その2
================================================================================

■時刻の選択ダイアログ

時刻を選択するダイアログは、TimePickerDialog を使う。
とくに何もしなくても、タイトルや選択するボタンなどは、日本語で表示してくれる。
また、DatePickerDialog と違って、時刻のタイトルは、mm:dd の形式なので、とくに変
更する必要はない。


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

import java.util.Calendar;

import android.app.Activity;
import android.app.TimePickerDialog;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.TimePicker;

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

        TextView date = (TextView) findViewById(R.id.date);

        Calendar calendar = Calendar.getInstance();
        int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);

        TimePickerDialog dialog = new TimePickerDialog(this,
                new DateSetHandler(date), hourOfDay, minute, true);    // (1)
        dialog.show();
    }

    private class DateSetHandler implements TimePickerDialog.OnTimeSetListener { // (2)
        private TextView date;

        public DateSetHandler(TextView date) {
            this.date = date;
        }

        @Override
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) { // (3)
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
            calendar.set(Calendar.MINUTE, minute);

            date.setText(String.format(getString(R.string.time_format), calendar));
        }
    }
}
---

□ TimePickerDialog dialog = new TimePickerDialog(this,
                new DateSetHandler(date), hourOfDay, minute, true);    // (1)

時刻を設定する TimePickerDialog オブジェクトを生成する。
このとき、時刻が設定されたときのリスナー、最初に表示される時・分を指定する。

・クラス階層
java.lang.Object
   + android.app.Dialog
         + android.app.AlertDialog
               + android.app.TimePickerDialog

・主なコンストラクター
---
TimePickerDialog(Context context, TimePickerDialog.OnTimeSetListener callBack, 
                    int hourOfDay, int minute, boolean is24HourView)
TimePickerDialog(Context context, int theme, 
                    TimePickerDialog.OnTimeSetListener callBack, 
                    int hourOfDay, int minute, boolean is24HourView)
---

時刻が設定されたときのリスナーのオブジェクトや、最初に表示される時刻、24時間表示
にするかどうかを引数として渡す。


□ private class DateSetHandler implements TimePickerDialog.OnTimeSetListener { // (2)

時刻が設定されたときのリスナー TimePickerDialog.OnTimeSetListener の実装クラスを
定義する。


□ TimePickerDialog.OnTimeSetListener

時刻が設定されたときのリスナーのインターフェース。

実装すべきメソッドは以下のメソッド。
abstract void  onTimeSet(TimePicker view, int hourOfDay, int minute)


□ public void onTimeSet(TimePicker view, int hourOfDay, int minute) {    // (3)

時刻が設定されたときのハンドラー・メソッド。
変更された時刻は、引数の hourOfDay, minute に入っている。

※
---
onDateSet() もそうだが、view から、設定されている時刻を取得できるので、引数の 
hourOfDay, minute はいらないと思うが、どうなんだろう。
単に使いやすさのためかな。
---

□ values/strings.xml
---

<resources>
    <string name="hello">Hello World, Dialog08Activity!</string>
    <string name="app_name">Dialog08</string>
    <string name="time_format">%tR</string>
</resources>
---


■日付と時刻の選択ダイアログ

このサンプルは、日時を指定するのではなく、日付と日時をそれぞれ、別のダイアログで
指定するもの。べつべつに指定したものをリスナーでまとめている。

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

import java.util.Calendar;

import android.app.Activity;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.os.Bundle;
import android.widget.DatePicker;
import android.widget.TextView;
import android.widget.TimePicker;

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

        TextView date = (TextView) findViewById(R.id.date);

        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int monthOfYear = calendar.get(Calendar.MONTH);
        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);

        TimePickerDialog timePickerDialog = new TimePickerDialog(this,
                new DateSetHandler(date), hourOfDay, minute, true);     // (1)
        timePickerDialog.show();

        DatePickerDialog datePickerDialog = new DatePickerDialog(this,
                new DateSetHandler(date), year, monthOfYear, dayOfMonth); // (2)
        datePickerDialog.show();

    }

    private class DateSetHandler implements DatePickerDialog.OnDateSetListener,
            TimePickerDialog.OnTimeSetListener {                        // (3)
        private TextView date;
        private Calendar calendar = Calendar.getInstance();             // (4)

        public DateSetHandler(TextView date) {
            this.date = date;
        }

        @Override
        public void onDateSet(DatePicker view, int year, int monthOfYear,
                int dayOfMonth) {                                       // (5)
            calendar.set(year, monthOfYear, dayOfMonth);                // (6)

            date.setText(String.format(getString(R.string.date_time_format),
                    calendar));                                         // (7)
        }

        @Override
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) { // (8)
            calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);              // (9)
            calendar.set(Calendar.MINUTE, minute);                      // (10)

            date.setText(String.format(getString(R.string.date_time_format),
                    calendar));                                         // (11)
        }
    }
}
---

□ TimePickerDialog timePickerDialog = new TimePickerDialog(this,
                new DateSetHandler(date), hourOfDay, minute, true);       // (1)
□ DatePickerDialog datePickerDialog = new DatePickerDialog(this,
                new DateSetHandler(date), year, monthOfYear, dayOfMonth); // (2)

日付、時刻の順で設定するので、ダイアログは、TimePickerDialog、DatePickerDialog 
の順番に表示する。
表示するのが時刻、日付の順番でよいので、実は生成の順番はこのとおりでなくてもよい。


□ private class DateSetHandler implements DatePickerDialog.OnDateSetListener,
            TimePickerDialog.OnTimeSetListener {                         // (3)

日時の変更に対処するために、DatePickerDialog.OnDateSetListener と、TimePickerDia
log.OnTimeSetListener の 2 つのインターフェースを実装するリスナーのクラスをつく
る。


□ private Calendar calendar = Calendar.getInstance();                  // (4)

onDateSet() メソッドと、onTimeSet() メソッドの2つで使うので、インスタンス変数と
して定義する。


□ public void onDateSet(DatePicker view, int year, int monthOfYear,
                int dayOfMonth) {                                      // (5)
□ calendar.set(year, monthOfYear, dayOfMonth);                        // (6)

日付が設定されたときのメソッド。ここで、calendar に日付部分を設定する。


□ date.setText(String.format(getString(R.string.date_time_format), calendar)); 
                                                                         // (7)
□ date.setText(String.format(getString(R.string.date_time_format), calendar)); 
                                                                         // (11)

設定された日時をテキストビューに設定する。フォーマットは以下のとおり。
    <string name="date_time_format">%1$tF %1$tR</string>
すなわち、
    yyyy-mm-dd hh:mm

※補足
---
今回のサンプルでは使用していないが、2 つ以上の書式指示子を指定する場合、引数の位
置(argument_index$)を指定する必要がある。そうしないと、つぎの strings.xml では、
次のエラーになり、プロジェクトのビルドができない。
Multiple annotations found at this line:
    - error: Multiple substitutions specified in non-positional format; did you 
      mean to add the formatted="false" attribute?
    - error: Unexpected end tag string

また、直接、Java ソースで指定すると、実行時のエラーとなる。

このことからも、文字列リテラルは直接ソース上でハードコーディングせずに、
strings.xml などのリソースとして定義しておいたほうがよい。

なお、この仕様は Android の仕様で、Java の仕様ではない。Java の場合は、引数の位
置を指定しないときは、引数が順番に割り振られる。
---


□ public void onTimeSet(TimePicker view, int hourOfDay, int minute) { // (8)
□ calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);                      // (9)
□ calendar.set(Calendar.MINUTE, minute);                              // (10)

時刻が設定されたときのメソッド。ここで、calendar に時刻部分を設定する。


□ values/strings.xml
---

<resources>
    <string name="hello">Hello World, Dialog09Activity!</string>
    <string name="app_name">Dialog09</string>
    <string name="date_time_format">%1$tF %1$tR</string>
</resources>
---


■ 日時を同時に指定するダイアログ

日付と日時をべつべつのダイアログで設定するのではなく、ひとつのダイアログにまとめ
て設定する。これも、カスタムダイアログになるので、ダイアログのクラスは 
AlertDialog を使う。
カスタムビューとしては、DatePicker と TimePicker を LinearLayout で組み合わせた
ものを使う。

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

import java.util.Calendar;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.DatePicker;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TimePicker;

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

        // 結果を設定するビューを取得する
        TextView date = (TextView) findViewById(R.id.date);

        // ダイアログに表示する日時を取得する
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int monthOfYear = calendar.get(Calendar.MONTH);
        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);

        // 2つのpickerを併せたビューを作る
        DatePicker datePicker = new DatePicker(this);                   // (1)
        TimePicker timePicker = new TimePicker(this);                   // (2)

        LinearLayout view = new LinearLayout(this);                     // (3)
        view.setOrientation(LinearLayout.VERTICAL);                     // (4)
        view.addView(datePicker);                                       // (5)
        view.addView(timePicker);                                       // (6)

        // 日時を設定するビューを使ったダイアログを生成する
        AlertDialog dialog = new AlertDialog.Builder(this)              // (7)
                .setView(view)                                          // (8)
                .setTitle(
                        String.format(getString(R.string.date_time_format),
                                calendar, calendar))
                .setPositiveButton(R.string.set_title, new DateSetHandler(view,
                                date))                                  // (9)
                .setNegativeButton(R.string.cancel_title, null).show();

        // pickerに値が変化したことを検知するハンドラーを設定する
        DateChangedHandler handler = new DateChangedHandler(dialog);    // (10)

        datePicker.init(year, monthOfYear, dayOfMonth, handler);        // (11)
        timePicker.setCurrentHour(hourOfDay);                           // (12)
        timePicker.setCurrentMinute(minute);                            // (13)
        timePicker.setOnTimeChangedListener(handler);                   // (14)
        timePicker.setIs24HourView(true);                               // (15)
    }

    /**
     * 日時が変更されたときの処理をするハンドラー
     */
    private class DateChangedHandler implements
            DatePicker.OnDateChangedListener, TimePicker.OnTimeChangedListener {
        private AlertDialog dialog;
        private Calendar calendar = Calendar.getInstance();

        public DateChangedHandler(AlertDialog dialog) {
            this.dialog = dialog;
        }

        @Override
        public void onDateChanged(DatePicker view, int year, int monthOfYear,
                int dayOfMonth) {
            calendar.set(year, monthOfYear, dayOfMonth);

            Log.d("TEST", String.format("d: %tc", calendar));

            dialog.setTitle(String.format(getString(R.string.date_time_format),
                    calendar));
        }

        @Override
        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
            calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
            calendar.set(Calendar.MINUTE, minute);

            Log.d("TEST", String.format("t: %tc", calendar));

            dialog.setTitle(String.format(getString(R.string.date_time_format),
                    calendar));
        }
    }

    /**
     * 日時が設定されたときの処理をするハンドラー
     */
    private class DateSetHandler implements DialogInterface.OnClickListener { // (16)
        private ViewGroup view;
        private TextView date;

        public DateSetHandler(ViewGroup view, TextView date) {
            this.view = view;
            this.date = date;
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {        // (17)
            DatePicker datePicker = (DatePicker) view.getChildAt(0);    // (18)
            TimePicker timePicker = (TimePicker) view.getChildAt(1);    // (19)
            
            Calendar calendar = Calendar.getInstance();
            calendar.set(datePicker.getYear(), datePicker.getMonth(),
                    datePicker.getDayOfMonth(), timePicker.getCurrentHour(),
                    timePicker.getCurrentMinute());

            Log.d("TEST", String.format("s: %tc", calendar));

            date.setText(String.format(getString(R.string.date_time_format),
                    calendar));
        }
    }
}
---

□ DatePicker datePicker = new DatePicker(this);                   // (1)
□ TimePicker timePicker = new TimePicker(this);                   // (2)

カスタムビューとして作るために、DatePicker、TimePicker のオブジェクトを生成する。
2 つのビューなので、これを、つぎの LinearLayout にまとめて、たてに 2 つのピッ
カーが表示されるようにする。

DatePicker については既出。

TimePicker は、日時を設定できるビューのクラス。

・クラス階層
java.lang.Object
   + android.view.View
         + android.view.ViewGroup
               + android.widget.FrameLayout
                     + android.widget.TimePicker

・コンストラクター
---
TimePicker(Context context)
TimePicker(Context context, AttributeSet attrs)
TimePicker(Context context, AttributeSet attrs, int defStyle)
---


□ LinearLayout view = new LinearLayout(this);                     // (3)
□ view.setOrientation(LinearLayout.VERTICAL);                     // (4)
□ view.addView(datePicker);                                       // (5)
□ view.addView(timePicker);                                       // (6)

DatePicker、TimePicker のオブジェクトを 2 つ縦に並べるレイアウトとして、LinearLa
yout オブジェクトを生成し、たてに設定して、DatePicker、TimePicker のオブジェクト
を追加する。
なお、LinearLayout は、ViewGroup のサブクラスになっているので、つぎのメソッドで、
ビューを追加できる。

ViewGroup の addView() メソッド
---
void     addView(View child, int index, ViewGroup.LayoutParams params)
void     addView(View child, ViewGroup.LayoutParams params)
void     addView(View child, int index)
void     addView(View child)
void     addView(View child, int width, int height)
---

引数 index を指定しないものでは、(おそらく) 0 から順番にindex が振られて追加され
る。


□ AlertDialog dialog = new AlertDialog.Builder(this)           // (7)
□ .setView(view)                                               // (8)

カスタムダイアログとして、AlertDialog オブジェクトを生成し、LinearLayout のオブ
ジェクトをカスタムビューとして設定する。


□ .setPositiveButton(R.string.set_title, new DateSetHandler(view, date)) // (9)

設定ボタンを設定する。リスナーのオブジェクトは、DialogInterface.OnClickListener 
を実装する。


□ DateChangedHandler handler = new DateChangedHandler(dialog);    // (10)

日時を変えることに対するリスナーのオブジェクトを生成する。
このオブジェクトは、DatePicker と TimePicker オブジェクトの初期化のところで使用
する。


□ datePicker.init(year, monthOfYear, dayOfMonth, handler);        // (11)
□ timePicker.setCurrentHour(hourOfDay);                           // (12)
□ timePicker.setCurrentMinute(minute);                            // (13)
□ timePicker.setOnTimeChangedListener(handler);                   // (14)
□ timePicker.setIs24HourView(true);                               // (15)

現在日時と、(10) で作った日時を変えることに対するリスナーのオブジェクトを使って、
DatePicker と TimePicker オブジェクトを初期化する。
初期化には、DatePicker クラスでは、次のメソッドを使う。
void  init(int year, int monthOfYear, int dayOfMonth, 
                DatePicker.OnDateChangedListener onDateChangedListener)

また、TimePicker クラスでは、次のメソッド。なぜか、init メソッドがない。ちょっと
不思議な感じ。
void  setCurrentHour(Integer currentHour)
void  setCurrentMinute(Integer currentMinute)
void  setOnTimeChangedListener(TimePicker.OnTimeChangedListener 
                                                        onTimeChangedListener)
void  setIs24HourView(Boolean is24HourView)

TimePicker のデフォルトの表示形式は、12 時間表示になっているので、
setIs24HourView(true) を指定しないと、24 時間表示にはならない。


□ private class DateSetHandler implements DialogInterface.OnClickListener { // (16)
□ public void onClick(DialogInterface dialog, int which) {        // (17)

設定ボタンに対するリスナーの実装クラスと、そのハンドラーメソッド。


□ DatePicker datePicker = (DatePicker) view.getChildAt(0);    // (18)
□ TimePicker timePicker = (TimePicker) view.getChildAt(1);    // (19)

このクラスには、日時の設定ダイアログで使ったビュー(ViewGroup の型で、実際のオブ
ジェクトは LinearLayout)を渡している。
そのビューのオブジェクトから、そこに設定しているそれぞれのピッカーのオブジェクト
を取得する。

・ViewGroup で、子ビューを取得するメソッド
---
View  getChildAt(int index)
---

index は、addView() メソッドで指定した順番。


□ values/strings.xml
---

<resources>
    <string name="hello">Hello World, Dialog091Activity!</string>
    <string name="app_name">Dialog091</string>
    <string name="date_format">%tF</string>
    <string name="time_format">%tR</string>
    <string name="date_time_format">%1$tF %1$tR</string>
</resources>
---

                                                                        以上


Android ダイアログ (5) 日付と時刻の選択ダイアログ その1

2011年08月21日 | Android
ダイアログ (5) 日付の選択ダイアログ
================================================================================

日付と時刻を選択するダイアログ。
それぞれ、DatePickerDialog と TimePickerDialog を使う。
また、日時を同時に指定するには、自分で、日付と時刻を選択するダイアログを作る必要
がある。これは、カスタムダイアログとして、AlertDialog を使う。このとき、カスタム
ビューとしては、日付と時刻を選択するための DatePicker と TimePicker を組み合わせ
て使う。

それぞれのサンプルは以下のとおり。

Dialog07  日付の選択ダイアログ
Dialog071 日付の選択ダイアログで、タイトルの形式をカスタマイズ
Dialog08  時刻の選択ダイアログ ※
Dialog09  日付と時刻の選択ダイアログが連続して設定 ※
Dialog091 日時の選択ダイアログ(カスタムダイアログ) ※

※(6)で紹介

■ 日付の選択ダイアログ

日付を選択するダイアログは、DatePickerDialog を使う。
とくに何もしなくても、タイトルや設定ボタン、キャンセル・ボタンなどは、日本語で表
示してくれる。
ただ、タイトルの形式は、次の形式で表示される。
    土曜日, 8月 20, 2011
この形式の変更は、次のサンプル Dialog071 で補足する。


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

import java.util.Calendar;

import android.app.Activity;
import android.app.DatePickerDialog;
import android.os.Bundle;
import android.widget.DatePicker;
import android.widget.TextView;

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

        TextView date = (TextView) findViewById(R.id.date);

        Calendar calendar = Calendar.getInstance();                       // (1)
        int year = calendar.get(Calendar.YEAR);
        int monthOfYear = calendar.get(Calendar.MONTH);
        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);

        DatePickerDialog dialog = new DatePickerDialog(this,
                new DateSetHandler(date), year, monthOfYear, dayOfMonth); // (2)
//        dialog.setTitle(String.format(getString(R.string.date_format), calendar)); // (3)
        dialog.show();
    }

    private class DateSetHandler implements DatePickerDialog.OnDateSetListener { // (4)
        private TextView date;

        public DateSetHandler(TextView date) {
            this.date = date;
        }

        @Override
        public void onDateSet(DatePicker view, int year, int monthOfYear,
                int dayOfMonth) {                                         // (5)
            Calendar calendar = Calendar.getInstance();
            calendar.set(year, monthOfYear, dayOfMonth);

            date.setText(String.format(getString(R.string.date_format), calenda
r)); // (6)
        }
    }
}
---

□ Calendar calendar = Calendar.getInstance();                            // (1)

ダイアログには、デフォルトで今日の日付を表示するので、Calendar.getInstance() で、
カレンダー日付のオブジェクトを設定する。


□ DatePickerDialog dialog = new DatePickerDialog(this,
             new DateSetHandler(date), year, monthOfYear, dayOfMonth);    // (2)

DatePickerDialog オブジェクトを生成する。
このとき、日付が設定されたときのリスナー、最初に表示される年・月・日を指定する。

・クラス階層
java.lang.Object
   + android.app.Dialog
         + android.app.AlertDialog
               + android.app.DatePickerDialog

・コンストラクター
---
DatePickerDialog(Context context, DatePickerDialog.OnDateSetListener callBack, 
                    int year, int monthOfYear, int dayOfMonth)
DatePickerDialog(Context context, int theme, 
                    DatePickerDialog.OnDateSetListener callBack, 
                    int year, int monthOfYear, int dayOfMonth)
---


□ // dialog.setTitle(String.format(getString(R.string.date_format), calendar));
 // (3)

コメントアウトされているが、setTitle() を使うことで、タイトルを設定する。ただし、
日付が変わったら、ここで指定した文字列は壊れ、デフォルトのタイトルの形式になる。
デフォルトのタイトルは、次の形式で表示される。
    土曜日, 8月 20, 2011
これの形式を変更するのは、次に示すサンプル Dialog071 で示す。


□ private class DateSetHandler implements DatePickerDialog.OnDateSetListener {  // (4)
□ public void onDateSet(DatePicker view, int year, int monthOfYear, 
                            int dayOfMonth) {                             // (5)

ダイアログで日付を設定したときに呼び出されるリスナー DatePickerDialog.OnDateSetL
istener を実装するクラスの定義。

DatePickerDialog.OnDateSetListener

日付を設定し終えて、設定ボタンをクリックしたときに呼ばれるリスナー・インターフ
ェース。
つぎのメソッドを実装する。
abstract void  onDateSet(DatePicker view, int year, int monthOfYear, 
                            int dayOfMonth)

最終的に確定した日付が引数で受け取れる。ただし、monthOfYear は、Calendar クラス
と同じで、0~11 の値でくる。

今回は、受け取ったテキストビューに、この日付を組み立てて設定している。

※
---
このメソッドでは、引数 view から、設定されている日付を取得できるので、引数の 
year, monthOfYear, dayOfMonth はいらないと思うが、どうなんだろう。
単に使いやすさのためかな。
---


□ date.setText(String.format(getString(R.string.date_format), calendar));// (6)

最終的な日付をテキストビューに設定している。
形式は、リソース values/strings.xml で定義している。
内容は、以下のとおり。
    <string name="date_format">%tF</string>

これは、ISO 8601 format 形式で、以下の書式になる。
    yyyy-mm-dd


□ layout/main.xml
---

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:id="@+id/date"
    />
</LinearLayout>
---

□ values/strings.xml
---

<resources>
    <string name="hello">Hello World, Dialog07Activity!</string>
    <string name="app_name">Dialog07</string>
    <string name="date_format">%tF</string>
</resources>
---


■タイトルの形式をカスタマイズする

先のサンプル Dialog07 では、ダイアログのタイトルを表示する前に設定しても、日付を
変えると、タイトルの形式も変わってしまった。
タイトルの形式を変えないようにするには、DatePickerDialog を直接使うのではなく、
DatePicker をビューとしたカスタマイズのダイアログ(AlertDialog)として定義して使
う。

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

import java.util.Calendar;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.DatePicker;
import android.widget.TextView;

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

        TextView date = (TextView) findViewById(R.id.date);

        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int monthOfYear = calendar.get(Calendar.MONTH);
        int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);

        DatePicker picker = new DatePicker(this);                       // (1)

        AlertDialog dialog = new AlertDialog.Builder(this)              // (2)
                .setView(picker)                                        // (3)
                .setTitle(String.format(getString(R.string.date_format),
                                        calendar))                      // (4)
                .setPositiveButton(R.string.set_title, new DateSetHandler(picker,
                        date))                                          // (5)
                .setNegativeButton(R.string.cancel_title, null)         // (6)
                .show();
        picker.init(year, monthOfYear, dayOfMonth, new DateChangedHandler(dialog)); // (7)
        Log.d("TEST", String.format("AlertDialog: %s", dialog.toString()));
    }

    private class DateChangedHandler implements DatePicker.OnDateChangedListener { // (8)
        private AlertDialog dialog;

        public DateChangedHandler(AlertDialog dialog) {
            this.dialog = dialog;
        }

        @Override
        public void onDateChanged(DatePicker view, int year, int monthOfYear,
                int dayOfMonth) {                                       // (9)
            Calendar calendar = Calendar.getInstance();
            calendar.set(year, monthOfYear, dayOfMonth);

            Log.d("TEST", String.format("d: %tc", calendar));

            dialog.setTitle(String.format(getString(R.string.date_format),
                    calendar));                                         // (10)
        }
    }

    private class DateSetHandler implements DialogInterface.OnClickListener { // (11)
        private DatePicker picker;
        private TextView date;

        public DateSetHandler(DatePicker picker, TextView date) {
            this.picker = picker;
            this.date = date;
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {        // (12)
            Calendar calendar = Calendar.getInstance();
            calendar.set(picker.getYear(), picker.getMonth(), 
                    picker.getDayOfMonth());                            // (13)

            Log.d("TEST", String.format("c: %tc", calendar));

            date.setText(String.format(getString(R.string.date_format),
                    calendar));
        }
    }
}
---

□ DatePicker picker = new DatePicker(this);                        // (1)

日付を選択するためのビューとして、DatePicker クラスを用いる。

・クラス階層
java.lang.Object
   + android.view.View
         + android.view.ViewGroup
               + android.widget.FrameLayout
                     + android.widget.DatePicker

・コンストラクター
---
DatePicker(Context context)
DatePicker(Context context, AttributeSet attrs)
DatePicker(Context context, AttributeSet attrs, int defStyle)
---

自分自身の画面で使用するので、context は、自分自身(this)を用いる。

・主なメソッド
---
void  init(int year, int monthOfYear, int dayOfMonth, 
                DatePicker.OnDateChangedListener onDateChangedListener)
---


□ AlertDialog dialog = new AlertDialog.Builder(this)                // (2)

DatePicker オブジェクトを乗せるための AlertDialog オブジェクトを生成する。


□ .setView(picker)                                                 // (3)

カスタムビューとして、DatePicker オブジェクトを設定する。


□ .setTitle(String.format(getString(R.string.date_format),calendar))  // (4)

ダイアログのタイトルを ISO 形式で設定する。リソースファイルで定義されているのは、
以下の形式である。
    %tF


□ .setPositiveButton(R.string.set_title, new DateSetHandler(picker, date)) // (5)

「設定ボタン」を設置する。ここでは、DialogInterface.OnClickListener を実装してい
るオブジェクトをリスナーとして、ダイアログで設定した日付を表示するためのテキスト
ビューを引数として渡している。


□ .setNegativeButton(R.string.cancel_title, null)                  // (6)

「キャンセル・ボタン」を設定する。
リスナー・オブジェクトは null になっているので、ボタンを押されたときは何もしない。


□ picker.init(year, monthOfYear, dayOfMonth, new DateChangedHandler(dialog)); // (7)

DatePicker オブジェクトを初期化する。
引数は、最初に出したい日付(年・月・日)、日付が変化したときに動作するリスナーのオ
ブジェクト。
今回は、日付が変わったときに、ダイアログのタイトルも変更したいので、リスナーには、
ダイアログのオブジェクトを渡している。


□ private class DateChangedHandler implements DatePicker.OnDateChangedListener { // (8)

引数は、最初に出したい日付(年・月・日)、日付が変化したときに動作するリスナーを定
義。このオブジェクトは、DatePicker.OnDateChangedListener インターフェースを実装
する。

DatePicker.OnDateChangedListener

日付の選択で、日付が変わったことに対するバンドラーを定義するリスナー・インターフ
ェース。

・メソッド
---
abstract void  onDateChanged(DatePicker view, int year, int monthOfYear, 
                                int dayOfMonth)
---

設定ボタンが押されたときは、これは動作しない。したがって、まったく日付を変える必
要がない場合は、設定ボタンのリスナーで、日付を設定する必要がある。


□ public void onDateChanged(DatePicker view, int year, int monthOfYear, 
                                int dayOfMonth) {            // (9)

ダイアログで日付が変わったことに対するハンドラー・メソッド。


□ dialog.setTitle(String.format(getString(R.string.date_format), calendar)); // (10)

ダイアログで日付が変わったら、その日付をタイトルに設定している。
タイトルの形式は、ISO 形式として、リソースに設定している。


□ private class DateSetHandler implements DialogInterface.OnClickListener { // (11)
□ public void onClick(DialogInterface dialog, int which) {  // (12)

設定ボタンがクリックされたことに対するリスナーのクラスとハンドラー・メソッドを定
義する。(「ダイアログ (1)」を参照)


□ calendar.set(picker.getYear(), picker.getMonth(), picker.getDayOfMonth()); // (13)

DatePicker オブジェクトを受け取っているので、そこから日付を取得して設定する。


□ values/strings.xml
---

<resources>
    <string name="hello">Hello World, Dialog071Activity!</string>
    <string name="app_name">Dialog071</string>
    <string name="set_title">設定</string>
    <string name="cancel_title">キャンセル</string>
    <string name="date_format">%tF</string>
</resources>
---
                                                                        以上


JavaScript 練習問題(20110815版)解答例

2011年08月17日 | Weblog

練習問題(20110815版)解答例
================================================================================
■Q1. 身長(m)と体重(kg)を入力してBMIを求めて表示する。
BMIは次の式で求まる。
  体重 / 身長^2

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>


Q1. 身長(m)と体重(kg)を入力してBMIを求めて表示する。
BMIは次の式で求まる。
  体重 / 身長2






<script type="text/javascript">
var height = Number(prompt("身長入力(m)", 0));
var weight = Number(prompt("体重入力(kg)", 0));
document.write(weight / (height * height) + "
");
</script>



</body>
</html>
---

■Q2. 2つの数値を入力する。
入力した2つの数値を、小さい順に並べて表示する。

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>

Q2. 2つの数値を入力する。
入力した2つの数値を、小さい順に並べて表示する。



<script type="text/javascript">
var i1 = Number(prompt("1番目の数値を入力してください。", "0"));
var i2 = Number(prompt("2番目の数値を入力してください。", "0"));
document.write("入力した数値は " + i1 + ", " + i2);
document.write("
");
document.write("小さい順に並べると ... ");
if (i1
");
</script>

</body>
</html>
---

■Q3. 3つの数値を入力する。
入力した3つの数値の中の最小値を表示する。

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>

Q3. 3つの数値を入力する。
入力した3つの数値の中の最小値を表示する。




<script type="text/javascript">
var i1 = Number(prompt("1番目の数値を入力してください。", "0"));
var i2 = Number(prompt("2番目の数値を入力してください。", "0"));
var i3 = Number(prompt("3番目の数値を入力してください。", "0"));
document.write("入力した数値は " + i1 + ", " + i2 + ", " + i3);
document.write("
");
document.write("最小値は ... ");

var min = i1;
if (min > i2) {
min = i2;
}
if (min > i3) {
min = i3;
}
document.write(min);
document.write("
");
</script>

</body>
</html>
---

■Q4. 数値を入力する。
入力した数値の符号を表示する。
表示は、「-」、「0」、「+」でかまわない。

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>

Q4. 数値を入力する。
入力した数値の符号を表示する。
表示は、「-」、「0」、「+」でかまわない。




<script type="text/javascript">
var i1 = Number(prompt("数値を入力してください。", "0"));
document.write("入力した数値は " + i1);
document.write("
");
document.write("符号は ... ");

if (i1 < 0) {
document.write("「-」");
} else if (i1 == 0) {
document.write("「0」");
} else {
document.write("「+」");
}
document.write("<br/>");
</script>

</body>
</html>
---

■Q5. 数値を入力する。
入力した数値が、1~12の範囲に入っているかどうかを確認する。
表示は、「入っている」、「入っていない」でかまわない。
(ヒント) 範囲に入っていないものからチェックするとよい

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>


Q5. 数値を入力する。



入力した数値が、1~12の範囲に入っているかどうかを確認する。
表示は、「入っている」、「入っていない」でかまわない。



(ヒント) 範囲に入っていないものからチェックするとよい






<script type="text/javascript">
var i1 = Number(prompt("数値を入力してください。", "0"));
document.write("入力した数値は " + i1);
document.write("
");
document.write("1~12の範囲に ... ");

if (i1 < 1) {
document.write("入っていない");
} else if (i1 > 12) {
document.write("入っていない");
} else {
document.write("入っている");
}
document.write("
");
</script>

</body>
</html>
---

■Q6. 3-for2.html で、年も選択できるようにする。
年の範囲は、1990年~2020年とする。

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>


Q6. 3-for2.html で、年も選択できるようにする。

年の範囲は、1990年~2020年とする。






<form>
誕生日:
<select>
<script type="text/javascript">
for(var i = 1990; i <= 2020; i++){
document.write("<option>" + i + "</option>");
}
</script>
</select>

<select>
<script type="text/javascript">
for(var i = 1; i <= 12; i++){
document.write("<option>" + i + "</option>");
}
</script>
</select>

<select>
<script type="text/javascript">
for(var i = 1; i <= 31; i++){
document.write("<option>" + i + "</option>");
}
</script>
</select>

</form>
</script>

</body>
</html>
---

■Q7. 数値を入力する。
入力された数分、★を横に並べて表示する。
(ヒント) 1個の★を表示する文を繰り返す

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>


Q7. 数値を入力する。
入力された数分、★を横に並べて表示する。



(ヒント) 1個の★を表示する文を繰り返す






<script type="text/javascript">
var i1 = Number(prompt("数値を入力してください。", "0"));
document.write("★を " + i1 + " 個並べる ...
");
for (var i = 1; i <= i1; i++) {
document.write("★");
}
</script>

</body>
</html>
---

■Q8. 1~100の間にある3の倍数を表示する。
(結果) 3 6 9 12 ... 99
(ヒント) 
for文で、カウンターを更新する式を+3ずつにする
または
if文で、カウンターの値が3の倍数かどうかを判断して、3の倍数のときだけ表示する
なお、カウンターを3で割ったときの余りが0のとき、3の倍数である。
(応用)
3の倍数だけでなく、3のつく数字も表示する

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<body>


Q8. 1~100の間にある3の倍数を表示する。



(結果) 3 6 9 12 ... 99



(ヒント)

for文で、カウンターを更新する式を+3ずつにする

または

if文で、カウンターの値が3の倍数かどうかを判断して、3の倍数のときだけ表示する
なお、カウンターを3で割ったときの余りが0のとき、3の倍数である。



(応用)
3の倍数だけでなく、3のつく数字も表示する






<script type="text/javascript">
for (var i = 3; i <= 100; i += 3) {
document.write(i);
document.write(" ");
}
</script>


別解

<script type="text/javascript">
for (var i = 1; i <= 100; i++) {
if ((i % 3) == 0) {
document.write(i);
document.write(" ");
}
}
</script>


応用

<script type="text/javascript">
for (var i = 1; i <= 100; i++) {
if ((i % 3) == 0) { // 3の倍数
document.write(i);
document.write(" ");

} else if ((i % 10) == 3) { // 1桁目が3
document.write(i);
document.write(" ");

} else if ((30 <= i) && (i <= 39)) { // 30台の数字
document.write(i);
document.write(" ");
}
}
</script>

</body>
</html>
---

■Q9. 「指定された数分、★を横に並べて表示する」関数 print_star(num) を作る。
この数は、promptを使って入力する。
(応用1)
この関数を利用して、★を正方形に並べる。
(結果例) 3つの場合
★★★
★★★
★★★
(応用2)
この関数を利用して、★を直角三角形に並べる。
(結果例) 3つの場合

★★
★★★

---
<html>
<head>
<title>JavaScript</title>
<style>
.question {background-color: #CCFFFF}
.answer {background-color: #CCFF99}
</style>
</head>
<script type="text/javascript">
function print_star(num) {
for (var i = 1; i <= num; i++) {
document.write("★");
}
}
</script>
<body>


Q9. 「指定された数分、★を横に並べて表示する」関数 print_star(num) を作る。
この数は、promptを使って入力する。



(応用1)
この関数を利用して、★を正方形に並べる。



(結果例) 3つの場合
★★★
★★★
★★★



(応用2)
この関数を利用して、★を直角三角形に並べる。



(結果例) 3つの場合

★★
★★★






<script type="text/javascript">
var i1 = Number(prompt("数値を入力してください。", "0"));
document.write("★を " + i1 + " 個並べる ...
");
print_star(i1);
</script>



(応用1)

<script type="text/javascript">
for (var i = 1; i
");
}
</script>



(応用2)

<script type="text/javascript">
for (var i = 1; i
");
}
</script>

</body>
</html>
---


JavaScript 練習問題(20110815版)

2011年08月16日 | Weblog

練習問題(20110815版)
================================================================================
Q1. 身長(m)と体重(kg)を入力してBMIを求めて表示する。
BMIは次の式で求まる。
体重 / 身長^2

Q2. 2つの数値を入力する。
入力した2つの数値を、小さい順に並べて表示する。

Q3. 3つの数値を入力する。
入力した3つの数値の中の最小値を表示する。

Q4. 数値を入力する。
入力した数値の符号を表示する。
表示は、「-」、「0」、「+」でかまわない。

Q5. 数値を入力する。
入力した数値が、1~12の範囲に入っているかどうかを確認する。
表示は、「入っている」、「入っていない」でかまわない。
(ヒント) 範囲に入っていないものからチェックするとよい

Q6. 3-for2.html で、年も選択できるようにする。
年の範囲は、1990年~2020年とする。

Q7. 数値を入力する。
入力された数分、★を横に並べて表示する。
(ヒント) 1個の★を表示する文を繰り返す

Q8. 1~100の間にある3の倍数を表示する。
(結果) 3 6 9 12 ... 99
(ヒント)
for文で、カウンターを更新する式を+3ずつにする
または
if文で、カウンターの値が3の倍数かどうかを判断して、3の倍数のときだけ表示する
なお、カウンターを3で割ったときの余りが0のとき、3の倍数である。
(応用)
3の倍数だけでなく、3のつく数字も表示する

Q9. 「指定された数分、★を横に並べて表示する」関数 print_star(num) を作る。
この数は、promptを使って入力する。
(応用1)
この関数を利用して、★を正方形に並べる。
(結果例) 3つの場合
★★★
★★★
★★★
(応用2)
この関数を利用して、★を直角三角形に並べる。
(結果例) 3つの場合

★★
★★★


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() でかまわない。

                                                                            以上


Android ダイアログ (3) テキスト入力

2011年08月06日 | Android
ダイアログ (3) テキスト入力
================================================================================

■ テキスト入力

ダイアログを使って、テキストを入力することもできる。
これも、AlertDialogを使う。実際には、テキスト入力だけではなく、カスタムダイアロ
グとして、いろいろと使える。


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

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.TextView;

public class Dialog04Activity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final TextView textView = (TextView) findViewById(R.id.name);
        final EditText editText = new EditText(this);                    // (1)

        new AlertDialog.Builder(this)
                .setTitle(R.string.alertDialogTitle)
                .setMessage(R.string.alertDialogMessage)
                .setPositiveButton(R.string.alertDialogPositiveButton,
                        new OKButtonClickHandler(textView, editText))
                .setView(editText)                                       // (2)
                .show();
    }

    /**
     * OKボタンをクリックしたときのハンドラー
     */
    private class OKButtonClickHandler implements OnClickListener {
        TextView textView;
        EditText editText;

        public OKButtonClickHandler(TextView textView, EditText editText) {
            this.textView = textView;
            this.editText = editText;
        }

        @Override
        public void onClick(DialogInterface dialog,
                int which) {
            textView.setText(String
                    .format(getString(R.string.alertDialogResultFormat), // (3)
                            editText.getText()));
        }
    }
}
---

(1)      android.widget.EditText

テキストを入力するビューのクラス。

主なメソッド
CharSequence getText()

void  setText(int resid)
void  setText(char[] text, int start, int len)
void  setText(int resid, TextView.BufferType type)
void  setText(CharSequence text)
void  setText(CharSequence text, TextView.BufferType type)


今回は使っていないが、テキストの範囲選択関係のメソッド。これらは、
android.text.Selection クラスのメソッドを使いやすくしたものとして定義されている。

void  extendSelection(int index)         カーソルの現在位置からindexまで選択
void  selectAll()                        すべて選択
void  setSelection(int start, int stop)  startからstopまで選択

カーソルの移動メソッド
void  setSelection(int index)            indexに移動


android.widget.TextView.BufferType
enumとして定義されている。

TextView.BufferType  EDITABLE
TextView.BufferType  NORMAL       
TextView.BufferType  SPANNABLE       


(2) setView(editText)

編集ビューをダイアログに設定する。


(3) OKボタンをクリックしたら、編集ビューの値(テキスト)を取得して、それをもとにテ
キスト・ビューにその値を設定する。

実際には、String.format()メソッドのフォーマットは、リソースで定義している "%s さ
ん、こんにちは!" という形式。%s のところに、editText.getText() で値を持ってきて
いる。


□ res/layout/main.xml
---

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
<TextView  
    android:id="@+id/name"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    />
</LinearLayout>
---

□ res/values/strings.xml
---

<resources>
    <string name="alertDialogTitle">名前入力</string>
    <string name="alertDialogMessage">名前を入力してください</string>
    <string name="alertDialogPositiveButton">OK</string>
    <string name="alertDialogResultFormat">%s さん、こんにちは!</string>
    <string name="app_name">Dialog04</string>
</resources>
---


■ 最初からソフトキーボードを表示

Dialog04のサンプルでは、入力エリアをクリックしないと、ソフトキーボードは表示され
ませんでした。
最初からソフトキーボードが表示されいた方がよいこともあります。
このサンプルでは、最初からソフトキーボードを表示するようにします。


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

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnFocusChangeListener;
import android.widget.EditText;
import android.widget.TextView;

public class Dialog041Activity extends Activity {

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

        final TextView textView = (TextView) findViewById(R.id.name);
        final EditText editText = new EditText(this);

        final AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setTitle(R.string.alertDialogTitle)
                .setMessage(R.string.alertDialogMessage)
                .setPositiveButton(R.string.alertDialogPositiveButton,
                        new OKButtonClickHandler(textView, editText))
                .setView(editText)
                .create();
        
        editText.setOnFocusChangeListener(new ForcusChangeHandler(alertDialog));// (1)

        alertDialog.show();
    }

    /**
     * フォーカスが変化したときのハンドラー
     */
    private class ForcusChangeHandler implements OnFocusChangeListener {        // (2)
        AlertDialog dialog;
        
        public ForcusChangeHandler(AlertDialog dialog) {
            this.dialog = dialog;
        }

        @Override
        public void onFocusChange(View v, boolean hasFocus) {                   // (3)
            if (hasFocus) {
                dialog.getWindow().setSoftInputMode(                            // (4)
                    WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
            }
        }
    }
    
    /**
     * OKボタンをクリックしたときのハンドラー
     */
    private class OKButtonClickHandler implements OnClickListener {
        TextView textView;
        EditText editText;

        public OKButtonClickHandler(TextView textView, EditText editText) {
            this.textView = textView;
            this.editText = editText;
        }

        @Override
        public void onClick(DialogInterface dialog,
                int which) {
            textView.setText(String
                    .format(getString(R.string.alertDialogResultFormat),
                            editText.getText()));
        }
    }
}
---

(1) setOnFocusChangeListener

View クラスのメソッド。

public void setOnFocusChangeListener (View.OnFocusChangeListener l)


(2) android.view.View.OnFocusChangeListener
(3) void onFocusChange(View v, boolean hasFocus)

ビューのフォーカスが変わったことを検知するリスナーのインターフェースと、そのハン
ドラーのメソッド。


(4) dialog.getWindow().setSoftInputMode(
                    WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);

ダイアログのウィンドウに対して、softInputModeを設定する。設定する値は、
WindowManager.LayoutParamsクラスのSOFT_INPUT_STATE_xxx定数で指定する。
今回は、ダイアログがフォーカスを受けたときには常にソフトキーボ-ドを表示したいの
で、SOFT_INPUT_STATE_ALWAYS_VISIBLE を指定する。

これが、SOFT_INPUT_STATE_VISIBLE だと、ダイアログがたとえ表示されていたとしても、
自分でフォーカスを当てない限り、ソフトキーボードが表示されない。


WindowManager.LayoutParams

ソフトウェア入力の状態を示す定数には以下のものがある。

int SOFT_INPUT_STATE_ALWAYS_HIDDEN   
        ウィンドウがフォーカスを受けたときに常に soft input area を隠す
int SOFT_INPUT_STATE_ALWAYS_VISIBLE  
        ウィンドウがフォーカスを受けたときに常に soft input area を表示する
int SOFT_INPUT_STATE_HIDDEN          
        通常、適切なとき(ユーザがウィンドウを前面に持ってきたとき)に soft 
        input area を隠す
int SOFT_INPUT_STATE_UNCHANGED       
        soft input area の状態を変更しない
int SOFT_INPUT_STATE_UNSPECIFIED     
        状態は指定されていない
int SOFT_INPUT_STATE_VISIBLE         
        通常、適切なとき(ユーザがウィンドウを前面に持ってきたとき)に soft 
        input area を表示する 

上記以外にも、いろいろな種類の定数が定義されている。また、これらは次のフィールド
の値として管理されている。

public int softInputMode

関連するものとしては、
    SOFT_INPUT_ADJUST_UNSPECIFIED
    SOFT_INPUT_ADJUST_RESIZE
    SOFT_INPUT_ADJUST_PAN
などがあり、値を取り出すための
    SOFT_INPUT_IS_FORWARD_NAVIGATION
    SOFT_INPUT_MASK_ADJUST
    SOFT_INPUT_MASK_STATE
    SOFT_INPUT_MODE_CHANGED
などのマスクが用意されている。