marunomaruno-memo

marunomaruno-memo

[Android] インテント (2) - アクティビティからの結果の受け取り

2012年03月09日 | Android
[Android] インテント (2) - アクティビティからの結果の受け取り
================================================================================

インテントを使って呼び出したアクティビティから、結果を受け取ることができる。

呼び出す側:

・startActivityForResult() メソッドを使ってアクティビティを起動する

・onActivityResult() メソッドをオーバーライドして、結果を受け取ったときの処理を記述する

呼び出された側:

・必要に応じて、結果を戻すためのインテント・オブジェクトを生成して、データを設定する

・setResult() メソッドで、結果コードや戻すデータを設定する

・finish() メソッドで、アクティビティを終了する


■ 呼び出す側

▲ アクティビティ

インテント起動ボタンを押下したら、startActivityForResult() メソッドを使ってアクティビティを起動する。

onActivityResult() メソッドをオーバーライドして、結果を受け取ったときの処理を記述する。このとき、単語が指定されていれば、この単語を逆順にした文字列を表示する。
単語が指定されていなければ、その旨のメッセージを表示する。


□ Intent02Activity.java
---
package jp.marunomaruno.android.intent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class Intent02Activity extends Activity {
    private static final int REQUEST_REVERSE_ACTIVITY = 1;    // (1)

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

    /**
     * 暗黙的にインテントを起動する。
     * 
     * @param view
     */
    public void onClickImplisitButton(View view) {
        EditText edit1 = (EditText) findViewById(R.id.editText1);

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.putExtra("message", edit1.getText());
        startActivityForResult(intent, REQUEST_REVERSE_ACTIVITY);    // (2)
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {    // (3)
        super.onActivityResult(requestCode, resultCode, data);    // (4)

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

        switch (requestCode) {    // (5)
        case REQUEST_REVERSE_ACTIVITY:     // (6)
            switch (resultCode) {    // (7)
            case Activity.RESULT_OK:     // (8)
                String reverse = data.getStringExtra("reverse");
                text1.setText(reverse);
                break;
                
            case RESULT_FIRST_USER + OtherActivity.NO_WORDS:     // (9)
                text1.setText(R.string.noWordsMessage);
                break;
                
            default:
                text1.setText(String.format("onActivityResult() resultCode=%d", 
											resultCode));
                break;
            }
            break;
            
        default:
            text1.setText(String.format("onActivityResult() requestCode=%d", 
											requestCode));
            break;
        }
    }
}
---

(1) 逆順文字列作成のためのインテント要求コードを定義する

    private static final int REQUEST_REVERSE_ACTIVITY = 1;    // (1)

これは、startActivityForResult() メソッドで指定する。これによって、アクティビティから結果が戻ってきたら、この値を確認することで、どのインテントを使ってアクティビティを呼び出したかがわかる。


(2) 結果を受け取るためのインテントを起動する

要求コードを指定する。

    startActivityForResult(intent, REQUEST_REVERSE_ACTIVITY);    // (2)


(3)(4) インテントの結果を受け取る

Activity クラスのこのメソッドをオーバーライドする。

    protected void onActivityResult(int requestCode, int resultCode,
            Intent data) {    // (3)

requestCode: startActivityForResult() で指定した要求コード
resultCode:  戻りコード
data:        データ

スーパークラスの同メソッドを呼び出す。

    super.onActivityResult(requestCode, resultCode, data);    // (4)


(5) 要求コードによって処理を切り分ける

    switch (requestCode) {    // (5)


(6) 逆順文字列作成の要求コード

    case REQUEST_REVERSE_ACTIVITY:     // (6)


(7) 結果の戻りコードによって処理を切り分ける

    switch (resultCode) {    // (7)

Activity クラスで定義している結果コードは次の 3 つ。
---
int  RESULT_CANCELED     0  キャンセルされた
int  RESULT_FIRST_USER   1  自分で定義する結果コードの最初の値
int  RESULT_OK          -1  結果成功
---

したがって、自分で定義する結果コードは 1 から順番に振る方がよい。


(8) 結果が OK のときの処理

    case RESULT_OK:     // (8)


(9) 単語が指定していなかったときの処理

    case RESULT_FIRST_USER + OtherActivity.NO_WORDS:     // (9)

値としては、RESULT_FIRST_USER は 1 で、OtherActivity.NO_WORDS は 1 にしているので、2 となっている。
(RESULT_FIRST_USER は加えなくても大丈夫)


▲ レイアウト

□ res/layout/main.xml

※前回のプロジェクトと同じ


■ 呼び出される側

▲ アクティビティ

受け取った文字列の逆順文字列を送る。
受け取った文字列が空でなければ、逆順文字列を作る。そのときの結果コードは RESULT_OK (値は -1)。
文字列が空だったら、結果コードとして、2 (= RESULT_FIRST_USER + NO_WORDS) を設定して返す。

□ OtherActivity.java
---
package jp.marunomaruno.android.intent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class OtherActivity extends Activity {
    public static final int NO_WORDS = 1;    // (1)
    
    private String message;

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

        Intent intent = getIntent();
        TextView text1 = (TextView) findViewById(R.id.otherTextView1);
        message = intent.getCharSequenceExtra("message").toString();
        text1.setText(message);
    }

    public void onClickBackButton(View view) {
        Intent returnIntent = new Intent();    // (2)
        returnIntent.putExtra("reverse", reverse(message));    // (3)
        if (message.length() > 0) {
            setResult(RESULT_OK, returnIntent);    // (4)
            
        } else {
            setResult(RESULT_FIRST_USER + NO_WORDS, returnIntent);    // (5)

        }
        finish();    // (6)
    }

    /**
     * 文字列を逆並び順にした文字列を返す。
     * @param s 文字列
     * @return 逆並び順にした文字列
     */
    private String reverse(String s) {
        return new StringBuilder(s).reverse().toString();
    }
}
---

(1) 文字列が空のときの結果コード

    public static final int NO_WORDS = 0;    // (1)


(2)(3) 戻すためのデータを設定する

そのためのインテント・オブジェクトを生成する。

    Intent returnIntent = new Intent();    // (2)

インテントにデータを設定する。このとき、逆並び順の文字列を作る。

    returnIntent.putExtra("reverse", reverse(message));    // (3)


(4) 空の文字列でなければ、結果コード RESULT_OK とする

    setResult(RESULT_OK, returnIntent);    // (4)


(5) 空の文字列のとき、その旨の結果コードを指定する

    setResult(RESULT_FIRST_USER + NO_WORDS, returnIntent);    // (5)


(6) アクティビティを終わる

    finish();    // (6)


▲ レイアウト

呼び出し元のアクティビティに戻るボタンをつけた。

□ res/layout/other.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/otherTextView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <Button
        android:id="@+id/backButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickBackButton"
        android:text="@string/backButtonLabel" />

</LinearLayout>
---


■ マニュフェスト

※前回のプロジェクトと同じ


■ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Intent02</string>
    <string name="implicitButtonLabel">逆並び順文字列を作る</string>
    <string name="backButtonLabel">確認</string>
    <string name="noWordsMessage">単語が指定されていません。</string>
</resources>
---

                                                                            以上


コメント (0) |  トラックバック (0) | 

[Android] インテント (1) - インテントを使ったアクティビティの呼び出し

2012年03月08日 | Android
[Android] インテント (1) - インテントを使ったアクティビティの呼び出し
================================================================================

インテントは、プログラムの実行に関する情報を管理するメッセージである。
プログラムの実行時に、実行に関する情報がインテントというメッセージにまとめられる。
このインテントを発行することで、プログラムを実行させることができる。また、逆に、
プログラムには、自身が受け取れるインテントの種類を記述できる。

インテントは、つぎの主要な情報によって構成している。
・アクション        動作
・カテゴリー        インテントに対する追加のメタデータ
・データ            インテントに与えるデータで、URI として表現
・エクストラ        Bundle 型のインテントに渡されるデータ
・フラグ            アクティビティの起動方法
・タイプ            MIME タイプ(操作したいリソースの種類)
・コンポーネント    インテントを利用するパッケージやクラスを明示的に指定

アクションとカテゴリーは単なる文字列で表現している。
データは Uri オブジェクトで定義する。Uri オブジェクトは、RFC3986 で定義された 
URI である。


インテントには、メッセージの送信先の指定方法によって、次の 2 つの種類がある。

・明示的インテント

メッセージを送る相手のプログラムのクラス名を直接指定する

・暗示的(暗黙的)インテント

相手のプログラムにさせたいこと(アクション)、そのときに必要なデータを指定すること
で、システムが自動的にプログラムを指定する。暗黙的インテントで、アクティビティが
インテントを受け取るためには、つぎの暗黙的なルーティング 3 つの条件をすべて満た
している必要がある。

コンストラクターとしては、以下のようなものがある。
 Intent(String action) 
 Intent(String action, Uri uri) 

また、メソッドで設定するには、以下のものがある。

Intent  setAction(String action) 

Intent  setData(Uri data) 
Intent  setDataAndType(Uri data, String type) 

Intent  setType(String type) 
Intent  putExtra(String name, 型 value) 
Intent  putExtra(String name, 型[] value) 


暗黙的なルーティング
・アクティビティは指定されたアクションをサポートする
・MIME タイプが指定されている場合、アクティビティはそれをサポートする
・アクティビティはインテントで指定されたカテゴリーをすべてサポートする


インテントの起動は、Activity クラスの
    startActivity(インテント)
を使う。また、結果を受け取りたい場合は、
    startActivityForResult(インテント, リクエスト・コード)
を使う。また、結果は、
    onActivityResult(int requestCode, int resultCode, Intent data)
をオーバーライドする。
呼び出された側のアクティビティでは、戻りコードは、
    setResult(戻りコード, インテント);
を使って設定する。


アクションとデータの例
------------------- --------------------------- --------------------------------
アクション          データ                      動作
------------------- --------------------------- --------------------------------
ACTION_VIEW         http://アドレス             ブラウザーで指定するURL
                    content://contacts/people/  内臓の電話帳を表示
                    geo:軽度,緯度               地図を表示
                    geo:0,0?q=住所              地図を表示
ACTION_CALL         tel://電話番号              電話をかける
ACTION_DIAL         tel://電話番号              ダイヤル画面を表示
ACTION_EDIT         URI                         URIで示されるアドレス長を編集
ACTION_WEB_SEARCH   検索文字列                  ブラウザーを開き、Googleで検索
------------------- --------------------------- --------------------------------

Intents and Intent Filters
http://developer.android.com/intl/ja/guide/topics/intents/intents-filters.html

ソフトウェア技術ドキュメントを勝手に翻訳 
Android 開発ガイド > フレームワークトピック > 4. インテントとインテントフィルタ
https://sites.google.com/a/techdoctranslator.com/jp/android/guide/intents-filters


■ 呼び出す側

同じアプリケーション内で、画面を切り替えるサンプル。
画面はアクティビティ・オブジェクトなので、ある画面から別の画面に切り替えるのに、
このインテントを使って行う。


▲ アクティビティ

テキスト・フィールドから取得した文字列を、つぎの画面に渡す。
このとき、つぎの画面の起動方法として、明示的にインテントを指定する方法と、暗黙的
にインテントを指定する 2 つの方法を使っている。

□ Intent01Activity.java
---
package jp.marunomaruno.android.intent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

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

    /**
     * 明示的にインテントを起動する。
     * 
     * @param view
     */
    public void onClickExplisitButton(View view) {
        EditText edit1 = (EditText) findViewById(R.id.editText1);

        Intent intent = new Intent(this, OtherActivity.class);    // (1)
        intent.putExtra("message", edit1.getText());    // (2)
        startActivity(intent);    // (3)
    }

    /**
     * 暗黙的にインテントを起動する。
     * 
     * @param view
     */
    public void onClickImplisitButton(View view) {
        EditText edit1 = (EditText) findViewById(R.id.editText1);

        Intent intent = new Intent(Intent.ACTION_SEND);    // (4)
        intent.putExtra("message", edit1.getText());
        startActivity(intent);
    }
}
---

(1) 明示的にインテント・オブジェクトを生成する

コンテキストとクラスを明示的に指定してインテント・オブジェクトを生成する。

    Intent intent = new Intent(this, OtherActivity.class);    // (1)


明示的にインテント・オブジェクトを生成するコンストラクター
---
Intent(Context packageContext, Class<?> cls) 
---

または、空のインテント・オブジェクトを作って、つぎのメソッドでクラスを設定する。
---
Intent  setClass(Context packageContext, Class<?> cls) 
Intent  setClassName(Context packageContext, String className) 
Intent  setClassName(String packageName, String className) 
---

自分のアプリケーションでないものなどは、3 番目のメソッドを利用して指定することも
できる。たとえば、ブラウザーなどはつぎのように指定することができる。
    setClassName("com.android.browser", "com.android.browser.BrowserActivity")


(2) 渡すデータを設定する

Uri オブジェクト以外のデータは、つぎのように、putExtra() メソッドを利用して、つ
ぎのアクティビティに渡すことができる。

putExtra() の第 1 引数には、渡すデータを意味する文字列。

    intent.putExtra("message", edit1.getText());    // (2)

データの取得側では、この "message" をキーとして、データを取得する。
なお、汎用のキーとして、Intent クラスの定数に、EXTRA_xxx という形でキーが用意さ
れているので、それを使うこともできる。


・Extra データ設定関係のメソッド(スカラーのもののみ)
---
Intent     putExtra(String name, boolean value)
Intent     putExtra(String name, byte value)
Intent     putExtra(String name, double value)
Intent     putExtra(String name, char value)
Intent     putExtra(String name, int value)
Intent     putExtra(String name, float value)
Intent     putExtra(String name, long value)
Intent     putExtra(String name, short value)
Intent     putExtra(String name, CharSequence value)
Intent     putExtra(String name, String value)
Intent     putExtra(String name, Serializable value)
---


(3) アクティビティを起動する

startActivity() メソッドにより、つぎのアクティビティを起動する。

    startActivity(intent);    // (3)

なお、指定されたアクティビティがない場合、
android.content.ActivityNotFoundException
がスローされる。


(4) 暗黙的にインテント・オブジェクトを生成する

何をするべきものなのかというアクションを指定して、インテント・オブジェクトを生成
する。

    Intent intent = new Intent(Intent.ACTION_SEND);    // (4)


暗黙的にインテント・オブジェクトを生成するコンストラクター
---
Intent(String action) 
Intent(String action, Uri uri) 
---


▲ レイアウト

明示的と暗黙的にインテントを起動するボタン。

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <requestFocus />
    </EditText>

    <Button
        android:id="@+id/explisitButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickExplisitButton"
        android:text="@string/explicitButtonLabel" />

    <Button
        android:id="@+id/implisitButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickImplisitButton"
        android:text="@string/implicitButtonLabel" />
</LinearLayout>
---


■ 呼び出される側

▲ 呼び出されるアクティビティのクラス

これは、単に渡されたデータをテキスト・ビューに設定して表示するだけのプログラム。

□ OtherActivity.java
---
package jp.marunomaruno.android.intent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class OtherActivity extends Activity {

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

        Intent intent = getIntent();        // (1)
        TextView text1 = (TextView) findViewById(R.id.otherTextView1);
        text1.setText(intent.getCharSequenceExtra("message").toString()); // (2)
    }
}
---

(1) インテントを取得する

    Intent intent = getIntent();        // (1)


(2) データを取得する

putExtra(String, CharSequence) で設定したデータは、getCharSequenceExtra(String) 
で取得する。

    text1.setText(intent.getCharSequenceExtra("message").toString());    // (2)


・Extra データ設定関係のメソッド(スカラーのもののみ)
---
boolean       getBooleanExtra(String name, boolean defaultValue)
byte          getByteExtra(String name, byte defaultValue)
char          getCharExtra(String name, char defaultValue)
double        getDoubleExtra(String name, double defaultValue)
float         getFloatExtra(String name, float defaultValue)
int           getIntExtra(String name, int defaultValue)
long          getLongExtra(String name, long defaultValue)
short         getShortExtra(String name, short defaultValue)
CharSequence  getCharSequenceExtra(String name)
String        getStringExtra(String name)
Serializable  getSerializableExtra(String name)
---


▲ レイアウト

main のアクティビティから受け取った文字列を表示するだけのレイアウト。

□ res/layout/other.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/otherTextView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>
---


■ Intent クラス

java.lang.Object 
   +  android.content.Intent 

プログラムの実行に関する情報を管理するメッセージのクラス。

・アクティビティ関係の標準アクション
---
ACTION_MAIN           メインプログラムの起動
ACTION_VIEW           ユーザーデータの表示
ACTION_ATTACH_DATA    添付データがついた
ACTION_EDIT           データの編集
ACTION_PICK           データからの取り出し
ACTION_CHOOSER        チューザーの表示
ACTION_GET_CONTENT    コンテントの取得
ACTION_DIAL           データをもとにダイアルする
ACTION_CALL           電話をかける
ACTION_SEND           データの送信
ACTION_SENDTO         メッセージの送信
ACTION_ANSWER         電話がかかってきた
ACTION_INSERT         指定のコンテナにデータを追加
ACTION_DELETE         データの削除
ACTION_RUN            データの実行
ACTION_SYNC           同期の開始
ACTION_PICK_ACTIVITY  アクティビティの選択
ACTION_SEARCH         検索
ACTION_WEB_SEARCH     Web 検索
ACTION_FACTORY_TEST   ファクトリーテスト用
---

・ブロードキャストの標準アクション
---
ACTION_TIME_TICK             現在の時刻が変更された
ACTION_TIME_CHANGED          時間が変更された
ACTION_TIMEZONE_CHANGED      タイムゾーンが変更された
ACTION_BOOT_COMPLETED        起動の完了
ACTION_PACKAGE_ADDED         パッケージの追加
ACTION_PACKAGE_CHANGED       パッケージの変更
ACTION_PACKAGE_REMOVED       パッケージの削除
ACTION_PACKAGE_RESTARTED     パッケージのリストア
ACTION_PACKAGE_DATA_CLEARED  パッケージ・データの初期化
ACTION_UID_REMOVED           ユーザーID の削除
ACTION_BATTERY_CHANGED       バッテリー状況の変更
ACTION_POWER_CONNECTED       電源接続
ACTION_POWER_DISCONNECTED    バッテリー起動への切替
ACTION_SHUTDOWN              シャットダウン
---

・主なカテゴリー
---
CATEGORY_DEFAULT     標準カテゴリ
CATEGORY_BROWSABLE   ブラウザから安全に起動することが可能
CATEGORY_TAB         TabActivity 内のタブ
CATEGORY_LAUNCHER    ホーム画面のアイコンから起動可能
CATEGORY_INFO        パッケージ情報が提供されている
CATEGORY_HOME        ホームスクリーンを表示する
CATEGORY_PREFERENCE  プリファレンスパネルがターゲット
CATEGORY_TEST        テストとして使用
---

・拡張データ
---
EXTRA_ALARM_COUNT 
EXTRA_BCC 
EXTRA_CC 
EXTRA_CHANGED_COMPONENT_NAME 
EXTRA_DATA_REMOVED 
EXTRA_DOCK_STATE 
EXTRA_DOCK_STATE_HE_DESK 
EXTRA_DOCK_STATE_LE_DESK 
EXTRA_DOCK_STATE_CAR 
EXTRA_DOCK_STATE_DESK 
EXTRA_DOCK_STATE_UNDOCKED 
EXTRA_DONT_KILL_APP 
EXTRA_EMAIL 
EXTRA_INITIAL_INTENTS 
EXTRA_INTENT 
EXTRA_KEY_EVENT 
EXTRA_PHONE_NUMBER 
EXTRA_REMOTE_INTENT_TOKEN 
EXTRA_REPLACING 
EXTRA_SHORTCUT_ICON 
EXTRA_SHORTCUT_ICON_RESOURCE 
EXTRA_SHORTCUT_INTENT 
EXTRA_STREAM 
EXTRA_SHORTCUT_NAME 
EXTRA_SUBJECT 
EXTRA_TEMPLATE 
EXTRA_TEXT 
EXTRA_TITLE 
EXTRA_UID 
---

・フラグ
---
FLAG_ACTIVITY_BROUGHT_TO_FRONT
FLAG_ACTIVITY_CLEAR_TASK 
FLAG_ACTIVITY_CLEAR_TOP 
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 
FLAG_ACTIVITY_FORWARD_RESULT 
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 
FLAG_ACTIVITY_MULTIPLE_TASK 
FLAG_ACTIVITY_NEW_TASK 
FLAG_ACTIVITY_NO_ANIMATION 
FLAG_ACTIVITY_NO_HISTORY 
FLAG_ACTIVITY_NO_USER_ACTION 
FLAG_ACTIVITY_PREVIOUS_IS_TOP  
FLAG_ACTIVITY_REORDER_TO_FRONT 
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 
FLAG_ACTIVITY_SINGLE_TOP 
FLAG_ACTIVITY_TASK_ON_HOME 
FLAG_DEBUG_LOG_RESOLUTION 
FLAG_EXCLUDE_STOPPED_PACKAGES 
FLAG_FROM_BACKGROUND 
FLAG_GRANT_READ_URI_PERMISSION 
FLAG_GRANT_WRITE_URI_PERMISSION 
FLAG_INCLUDE_STOPPED_PACKAGES 
FLAG_RECEIVER_REGISTERED_ONLY 
FLAG_RECEIVER_REPLACE_PENDING 
---


・コンストラクタ-
---
Intent() 
Intent(Intent o) 
Intent(String action) 
Intent(String action, Uri uri) 
Intent(Context packageContext, Class<?> cls) 
Intent(String action, Uri uri, Context packageContext, Class<?> cls)  
---

・主なメソッド
---
Intent         addCategory(String category) 
Intent         addFlags(int flags) 
boolean        filterEquals(Intent other) 
int            filterHashCode() 
String         getAction() 

Set<String>    getCategories() 

ComponentName  getComponent() 
Uri            getData() 
String         getDataString() 

int            getFlags() 

static Intent  getIntent(String uri) 
static Intent  getIntentOld(String uri)  

String         getPackage() 

String         getScheme() 
Intent         getSelector() 

Rect           getSourceBounds() 
String         getType() 
boolean        hasCategory(String category) 
boolean        hasFileDescriptors() 
void           removeCategory(String category) 
void           removeExtra(String name) 
Intent         setAction(String action) 
Intent         setClass(Context packageContext, Class<?> cls) 
Intent         setClassName(Context packageContext, String className) 
Intent         setClassName(String packageName, String className) 
Intent         setComponent(ComponentName component) 
Intent         setData(Uri data) 
Intent         setDataAndType(Uri data, String type) 
Intent         setFlags(int flags) 
Intent         setPackage(String packageName) 
Intent         setType(String type) 
String         toUri(int flags) 
---


・Extra 関係のメソッド
---
xxx[]           getXxxArrayExtra(String name) 
xxx             getXxxExtra(String name, xxx defaultValue) 
    xxx: boolean, byte, char, double, float, int, long, short

ArrayList<Integer>  getIntegerArrayListExtra(String name) 

Yyy[]           getYyyArrayExtra(String name) 
ArrayList<Yyy>  getYyyArrayListExtra(String name) 
Yyy             getYyyExtra(String name) 
    Yyy: CharSequence, String

Parcelable[]                         getParcelableArrayExtra(String name) 
<T extends Parcelable> ArrayList<T>  getParcelableArrayListExtra(String name) 
<T extends Parcelable> T             getParcelableExtra(String name) 

Serializable    getSerializableExtra(String name) 

Bundle          getBundleExtra(String name) 
Bundle          getExtras() 

boolean         hasExtra(String name) 

Intent  putExtra(String name, xxx[] value) 
Intent  putExtra(String name, xxx value) 
    xxx: boolean, byte, char, double, float, int, long, short

Intent  putExtra(String name, Yyy value) 
Intent  putExtra(String name, Yyy[] value) 
Intent  putYyyArrayListExtra(String name, ArrayList<Yyy> value) 
    Yyy: CharSequence, String, Parcelable

Intent  putExtra(String name, Bundle value) 
Intent  putExtra(String name, Serializable value) 

Intent  putExtras(Intent src) 
Intent  putExtras(Bundle extras) 

Intent  putIntegerArrayListExtra(String name, ArrayList<Integer> value) 

Intent  replaceExtras(Bundle extras) 
Intent  replaceExtras(Intent src) 
---


■ マニュフェスト

マニフェストには、2 つのアクティビティ要素が記される。

□ AndroidManifest.xml
---
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.marunomaruno.android.intent"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".Intent01Activity"
            android:label="@string/app_name" >    <!-- (1) -->
            <intent-filter>    <!-- (2) -->
                <action android:name="android.intent.action.MAIN" /> <!-- (3) -->
                <category android:name="android.intent.category.LAUNCHER" />
                                                                    <!-- (4) -->
            </intent-filter>
        </activity>
        <activity
            android:name=".OtherActivity"
            android:label="other activity" >
            <intent-filter>
                <action android:name="android.intent.action.SEND" /> <!-- (5) -->
                <category android:name="android.intent.category.DEFAULT" />    
                                                                    <!-- (6) -->
            </intent-filter>
        </activity>
    </application>
</manifest>
---

(1) アクティビティ

    <activity
        android:name=".Intent01Activity"
        android:label="@string/app_name" >    <!-- (1) -->


(2) インテント・フィルター

このクラスがサポートするアクションやカテゴリーについて指定する。

    <intent-filter>    <!-- (2) -->

この要素の子要素として、つぎの要素が指定できる。
    action
    category
    data


(3)(5) アクション

Intent01Activity のアクションを「メインプログラムの起動」にする。これは、アプリ
ケーションが最初に表示するアクティビティを意味する。

    <action android:name="android.intent.action.MAIN" />    <!-- (3) -->


OtherActivity のアクションを「データの送信」にする。

    <action android:name="android.intent.action.SEND" />    <!-- (5) -->

指定できるアクションについては、上記の Intent クラスの定数を参考。
定数 ACTION_xxx の xxx 部分が
android.intent.action.XXX
になる。


(4)(6) カテゴリー

Intent01Activity のカテゴリーを「ランチャー」にする。これは、このアクティビティ
をランチャーで選択できるようにする。

    <category android:name="android.intent.category.LAUNCHER" />    <!-- (4) -->


OtherActivity のカテゴリーを「デフォルト」にする。

    <category android:name="android.intent.category.DEFAULT" />    <!-- (6) -->
      
指定できるカテゴリーについては、上記の Intent クラスの定数を参考。
定数 CATEGORY_xxx の xxx 部分が
android.intent.category.XXX
になる。


■ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Intent01</string>
    <string name="explicitButtonLabel">明示的にインテントを起動</string>
    <string name="implicitButtonLabel">暗黙的にインテントを起動</string>
</resources>
---

                                                                            以上

コメント (0) |  トラックバック (0) | 

[Java] スッキリわかるJava入門

2012年03月06日 | Java
スッキリわかるJava入門
中山 清喬 (著), 国本 大悟 (著)
http://www.impressjapan.jp/support/aftercare/3086

¥2,730(本体 ¥2,600+税)
単行本(ソフトカバー): 640ページ
出版社: インプレスジャパン (2011/10/7)
言語 日本語
ISBN-10: 4844330861
ISBN-13: 978-4844330868
発売日: 2011/10/7
商品の寸法: 20.8 x 15 x 4.2 cm

第1章 Javaをはじめよう
ようこそJavaの世界へ
Java開発の基礎知識
Javaプログラムの基本構造

第2章 式と演算子
演算子、型の変換
命令実行の文

第3章 条件分岐と繰り返し
条件式の書き方、分岐構文のバリエーション
繰り返し構文のバリエーション

第4章 配列
配列の書き方
多次元の配列

第5章 メソッド
メソッドとは
引数と戻り値の利用
オーバーロード

付録A JDKのインストール

第6章 複数クラスを用いた開発
複数クラスで構成されるプログラム
パッケージに属したクラスの実行方法

第7章 オブジェクト指向をはじめよう
オブジェクト指向を学ぶ理由
オブジェクト指向の全体像と本質

第8章 インスタンスとクラス
クラス定義による効果
インスタンスの利用方法

第9章 さまざまなクラス機構
クラス型と参照
コンストラクタ
静的メンバ

第10章 カプセル化
カプセル化の目的とメリット

第11章 継承
継承の基礎
継承とコンストラクタ

第12章 高度な継承
抽象クラス
インタフェース

第13章 多態性
多態性とは
多態性のメリット

第14章 Javaを支える標準クラス

第15章 例外
エラーの種類と対応策

第16章 まだまだ広がるJavaの世界

付録B エラー解決・虎の巻

付録C JDKバージョンによる違い
コメント (0) |  トラックバック (0) | 

[Android] オプション・メニューとコンテキスト・メニュー

2012年03月05日 | Android
[Android] オプション・メニューとコンテキスト・メニュー
================================================================================

オプション・メニューは、実機によっては、画面とは別にある「メニュー」ボタン押下に
よって表示されるコンポーネント。タブレットなどでは、画面上に「メニュー」ボタンが
あったりする。

コンテキスト・メニューは、ビューの部品(たとえば、TextView など)を長押しすること
で、そのコンポーネント上に表示されるメニューである。

メニューは、その項目数が 6 つ以内のときはグリッド表示され、7 つ以上になったとき
は 6 つ目のメニュー項目が「その他」になり、それをクリックすることで、残りのメニ
ューが表示される。


つぎの Activity クラスのメソッドをオーバーライドすることで、オプション・メニュー
を設定する。
public boolean onCreateOptionsMenu(Menu menu)
                オプション・メニュー生成時のハンドラー
public boolean onMenuOpened(int featureId, Menu menu)
                オプション・メニューを開いたときのハンドラー
public void    onOptionsMenuClosed(Menu menu)
                オプション・メニューを閉じたときのハンドラー
public boolean onOptionsItemSelected(MenuItem item)
                オプション・メニューの項目選択時のハンドラー


つぎの Activity クラスのメソッドをオーバーライドすることで、コンテキスト・メニ
ューを設定する。
public void    onCreateContextMenu(ContextMenu menu, View v,
                                                ContextMenuInfo menuInfo)
                コンテキスト・メニュー生成時のハンドラー
public void    onContextMenuClosed(Menu menu)
                コンテキスト・メニューを閉じたときのハンドラー
public boolean onContextItemSelected(MenuItem item)
                コンテキスト・メニューの項目選択時のハンドラー

なお、コンテキスト(ビュー)とこのコンテキスト・メニューとを紐づけるのは、Activity.
registerForContextMenu() メソッドによる。

また、オプション・メニュー、コンテキスト・メニュー共通で、その項目選択時のハンド
ラーとして、次のメソッドがある。
public boolean onMenuItemSelected(int featureId, MenuItem item)


▲ アクティビティ

上記のメソッドをオーバーライドして、その動きを確認している。

□ Menu01Activity.java
---
package jp.marunomaruno.android.menu;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.TextView;
import android.widget.Toast;
import jp.marunomaruno.android.menu.R;

public class Menu01Activity extends Activity {
    private static final int TOAST_DURATION = Toast.LENGTH_SHORT;
    private Context context;

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

        // コンテキスト・メニューの設定
        TextView text1 = (TextView) findViewById(R.id.text1);
        registerForContextMenu(text1); // (1)
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) { // (2)
        super.onCreateOptionsMenu(menu); // (3)
        MenuItem item1 = menu.add("アイスクリーム"); // (4)
        MenuItem item2 = menu.add("あられ");
        SubMenu sub3 = menu.addSubMenu("あずき ..."); // (5)
        sub3.add("つぶあん"); // (6)
        sub3.add("こしあん");
        return true; // (7)
    }

    @Override
    public boolean onMenuOpened(int featureId, Menu menu) { // (8)
        String message = String.format(
                "onMenuOpened(): featureId: %d, menu: ¥"%s¥"", featureId, menu);
        Toast.makeText(context, message, TOAST_DURATION).show();
        System.out.println(message);
        return super.onMenuOpened(featureId, menu);
    }

    @Override
    public void onOptionsMenuClosed(Menu menu) { // (9)
        String message = String.format("onOptionsMenuClosed(): menu: ¥"%s¥"",
                menu);
        Toast.makeText(context, message, TOAST_DURATION).show();
        System.out.println(message);
        super.onOptionsMenuClosed(menu);
    }

    @Override
    public boolean onMenuItemSelected(int featureId, MenuItem item) { // (10)
        String message = String.format(
                "onMenuItemSelected(): featureId: %d, item: ¥"%s¥"", featureId,
                item);
        Toast.makeText(context, message, TOAST_DURATION).show();
        System.out.println(message);
        return super.onMenuItemSelected(featureId, item);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) { // (11)
        String message = String.format("onOptionsItemSelected(): item: ¥"%s¥"",
                item);
        Toast.makeText(context, message, TOAST_DURATION).show();
        System.out.println(message);
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
            ContextMenuInfo menuInfo) { // (12)
        menu.setHeaderTitle("Context menu"); // (13)
        MenuItem item1 = menu.add("Apple");
        MenuItem item2 = menu.add("Banana");
        SubMenu sub3 = menu.addSubMenu("Chocolate ...");
        sub3.add("Black");
        sub3.add("White");
    }

    @Override
    public void onContextMenuClosed(Menu menu) { // (14)
        String message = String.format("onContextMenuClosed(): menu: ¥"%s¥"",
                menu);
        Toast.makeText(context, message, TOAST_DURATION).show();
        System.out.println(message);
        super.onContextMenuClosed(menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) { // (15)
        String message = String.format("onContextItemSelected(): item: ¥"%s¥"",
                item);
        Toast.makeText(context, message, TOAST_DURATION).show();
        System.out.println(message);
        return super.onContextItemSelected(item);
    }
}
---

(1) ビューのコンポーネントにコンテキスト・メニューを登録する

テキスト・ビューに対し、コンテキスト・メニューを登録する。

    registerForContextMenu(text1); // (1)


・Activity クラスのメソッド
---
void     registerForContextMenu(View view)
---


(2)(3)(7) オプション・メニューを生成したときの処理

アプリケーション起動時に、オプション・メニューは生成される。

    public boolean onCreateOptionsMenu(Menu menu) { // (2)

onCreateOptionsMenu() の先頭で、スーパークラスのメソッドを実行する。

    super.onCreateOptionsMenu(menu); // (3)

メニューが完成したので、true を返す。

    return true; // (7)


(4) オプション・メニューに項目を追加する

onCreateOptionsMenu() メソッドの引数で、Menu オブジェクトが渡ってくるので、これ
に対し、add() メソッドを使って、メニューの項目を追加する。

    MenuItem item1 = menu.add("アイスクリーム"); // (4)


・Menu に項目を追加する主なメソッド(すべて abstract なので、その記述を省略)
---
MenuItem  add(CharSequence title) 
MenuItem  add(int groupId, int itemId, int order, int titleRes) 
MenuItem  add(int titleRes) 
MenuItem  add(int groupId, int itemId, int order, CharSequence title) 
---
groupId          通常は NONE でよい(グループ化するための項目)
itemId           項目 ID
order            メニュー項目を追加する位置(通常は NONE)
title, titleRes  メニュー項目のテキストの文字列またはリソース ID
---


(5)(6) オプション・メニューにサブ・メニューを追加する

サブ・メニューを追加するには、addSubMenu() メソッドを使う。この返り値は SubMenu 
クラスになる。

    SubMenu sub3 = menu.addSubMenu("あずき ..."); // (5)


・サブ・メニューを追加するメソッド(すべて abstract なので、その記述を省略)
---
SubMenu  addSubMenu(int groupId, int itemId, int order, CharSequence title) 
SubMenu  addSubMenu(int groupId, int itemId, int order, int titleRes) 
SubMenu  addSubMenu(CharSequence title) 
SubMenu  addSubMenu(int titleRes) 
---

addSubMenu() メソッドで作ったサブ・メニューに、add() メソッドを使って項目を追加す
る。
    sub3.add("つぶあん"); // (6)


・サブ・メニューに項目を追加するメソッド(すべて abstract なので、その記述を省略、
メニューに項目を追加するメソッドと同じ)
---
MenuItem  add(CharSequence title) 
MenuItem  add(int groupId, int itemId, int order, int titleRes) 
MenuItem  add(int titleRes) 
MenuItem  add(int groupId, int itemId, int order, CharSequence title) 
---


(8) オプション・メニューを開いたときの処理

onCreateOptionsMenu() メソッドは、アプリケーション起動時に実行されるだけなので、
オプション・メニューが開かれるたびに呼ばれるメソッドがある。

    public boolean onMenuOpened(int featureId, Menu menu) { // (8)


(9) オプション・メニューを閉じたときの処理

    public void onOptionsMenuClosed(Menu menu) { // (9)


(10) メニューの項目を選択したときの処理

このメソッドは、オプション・メニュー、コンテキスト・メニューで共通で使える。
まず、このメソッドが動き、その後、オプション・メニューのときは 
onOptionsItemSelected() メソッドが、コンテキスト・メニューのときは 
onContextItemSelected() メソッドが動く。

引数の item が選択したメニュー項目になる。

    public boolean onMenuItemSelected(int featureId, MenuItem item) { // (10)

featureId は、メニューのパネルの ID。


メニュー項目の情報は、以下のメソッドを使って取得する。

・取得関係の主なメソッド(すべて abstract なので、その記述を省略)
---
int          getItemId() 
int          getOrder() 
SubMenu      getSubMenu() 
CharSequence getTitle() 
CharSequence getTitleCondensed() 
---


(11) オプション・メニューの項目を選択したときの処理

    public boolean onOptionsItemSelected(MenuItem item) { // (11)

MenuItem オブジェクトは、オプション・メニュー項目でも、サブ・メニュー項目でも、コ
ンテキスト・メニュー項目でも同じである。


(12) コンテキスト・メニューを生成したときの処理

オプション・メニューと違い、コンテキスト・メニューを開くたびにこのメソッドが呼ばれ
る。

    public void onCreateContextMenu(ContextMenu menu, View v,
            ContextMenuInfo menuInfo) { // (12)


(13) コンテキスト・メニューのタイトルを設定する

    menu.setHeaderTitle("Context menu"); // (13)


(14) コンテキスト・メニューを閉じたときの処理

    public void onContextMenuClosed(Menu menu) { // (14)


(15) コンテキスト・メニューの項目を選択したときの処理

    public boolean onContextItemSelected(MenuItem item) { // (15)


△ Menu インターフェース

android.view.Menu

オプション・メニュー、コンテキスト・メニューで使うインターフェース。

・主なメソッド(すべて abstract なので、その記述を省略)
---
MenuItem add(CharSequence title) 
MenuItem add(int groupId, int itemId, int order, int titleRes) 
MenuItem add(int titleRes) 
MenuItem add(int groupId, int itemId, int order, CharSequence title) 

SubMenu  addSubMenu(int groupId, int itemId, int order, CharSequence title) 
SubMenu  addSubMenu(int groupId, int itemId, int order, int titleRes) 
SubMenu  addSubMenu(CharSequence title) 
SubMenu  addSubMenu(int titleRes) 

void     clear() 

void     close() 

MenuItem findItem(int id) 
MenuItem getItem(int index) 
boolean  hasVisibleItems() 

void     removeItem(int id) 

int      size()  
---


△ SubMenu インターフェース

android.view.SubMenu

サブメニュー用のインターフェース。サブメニューは、末端のメニューで、この下はメニ
ュー項目しか追加できない。


・主なメソッド(すべて abstract なので、その記述を省略)
---
void     clearHeader()
MenuItem getItem()
SubMenu  setHeaderIcon(Drawable icon)
SubMenu  setHeaderIcon(int iconRes)
SubMenu  setHeaderTitle(CharSequence title)
SubMenu  setHeaderTitle(int titleRes)
SubMenu  setHeaderView(View view)
SubMenu  setIcon(Drawable icon)
SubMenu  setIcon(int iconRes)
---


△ MenuItem インターフェース

android.view.MenuItem

メニュー項目のインターフェース。


・主なメソッド(すべて abstract なので、その記述を省略)
---
int      getItemId() 
ContextMenu.ContextMenuInfo  getMenuInfo() 
int      getOrder() 
SubMenu  getSubMenu() 
CharSequence getTitle() 
CharSequence getTitleCondensed() 
boolean  hasSubMenu() 
boolean  isCheckable() 
boolean  isChecked() 
boolean  isEnabled() 
boolean  isVisible() 
MenuItem setCheckable(boolean checkable) 
MenuItem setChecked(boolean checked) 
MenuItem setEnabled(boolean enabled) 
MenuItem setIcon(Drawable icon) 
MenuItem setIcon(int iconRes) 
MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener menuItemClickListener) 
MenuItem setTitle(CharSequence title) 
MenuItem setTitle(int title) 
MenuItem setTitleCondensed(CharSequence title) 
MenuItem setVisible(boolean visible)  
---


△ ContextMenu インターフェース

android.view.ContextMenu

コンテキスト・メニューを使うためには、つぎの手順が必要。
・ロング・クリックするクライアント・オブジェクトに対して
     registerForContextMenu(View)
を行う。
そして、つぎのメソッドをオーバーライドして、メニュー項目を追加する。
     onCreateContextMenu(ContextMenu, View, ContextMenu.ContextMenuInfo)


・主なメソッド(すべて abstract なので、その記述を省略)
---
void         clearHeader() 
ContextMenu  setHeaderIcon(Drawable icon) 
ContextMenu  setHeaderIcon(int iconRes) 
ContextMenu  setHeaderTitle(CharSequence title) 
ContextMenu  setHeaderTitle(int titleRes) 
ContextMenu  setHeaderView(View view)  
---


△ ContextMenuInfo インターフェース

android.view.ContextMenu.ContextMenuInfo

AdapterView などで使うマーカー・インターフェース。
メソッドなどは持っていない。


▲ レイアウト

コンテキスト・メニューを表示するための TextView を定義。

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#CCCC00"
        android:hint="@string/hint"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
---


▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hint">ここを長押しするとコンテキストメニューが出る</string>
    <string name="app_name">Menu01</string>
</resources>
---


▲ ログ

メニュー関係のハンドラーの動きを確認。

---
○ アプリケーション起動時
onCreateOptionsMenu(): menu: "MenuBuilder@46c02638"

○ オプション・メニュー選択時
onMenuOpened(): featureId: 0, menu: "MenuBuilder@46c02638"
onMenuItemSelected(): featureId: 0, item: "アイスクリーム"
onOptionsItemSelected(): item: "アイスクリーム"
onOptionsMenuClosed(): menu: "MenuBuilder@46c02638"

○ オプション・メニューで、サブ・メニュー選択時
onMenuOpened(): featureId: 0, menu: "MenuBuilder@46c02638"
onMenuItemSelected(): featureId: 0, item: "あずき ..."
onOptionsItemSelected(): item: "あずき ..."
onMenuItemSelected(): featureId: 0, item: "つぶあん"
onOptionsItemSelected(): item: "つぶあん"

○ コンテキスト・メニュー選択時
onCreateContextMenu(): menu: "ContextMenuBuilder@46c2f3d0"
onMenuItemSelected(): featureId: 6, item: "Banana"
onContextItemSelected(): item: "Banana"
onContextMenuClosed(): menu: "ContextMenuBuilder@46c2f3d0"

○ コンテキスト・メニューで、サブ・メニュー選択時
onCreateContextMenu(): menu: "ContextMenuBuilder@46c43598"
onMenuItemSelected(): featureId: 6, item: "Chocolate ..."
onContextItemSelected(): item: "Chocolate ..."
onMenuItemSelected(): featureId: 6, item: "White"
onContextItemSelected(): item: "White"
onContextMenuClosed(): menu: "SubMenuBuilder@46c44618"
---
※パッケージ名 "com.android.internal.view.menu." の部分は表示から割愛
                                                                            以上


コメント (0) |  トラックバック (0) | 

Eclipseで学ぶはじめてのJava 第2版(DVD付)

2012年03月04日 | Java
Eclipseで学ぶはじめてのJava 第2版(DVD付) [大型本]
木村 聡 (著)
http://www.sbcr.jp/products/4797359039.html

大型本: 472ページ
出版社: ソフトバンククリエイティブ; 第2版 (2010/4/30)
ISBN-10: 479735903X
ISBN-13: 978-4797359039
発売日: 2010/4/30
商品の寸法: 23 x 18.4 x 3 cm

SECTION 1 基礎編

第1章 プログラムについて
第2章 まずは、実行してみる
第3章 Eclipseを使う
第4章 いろいろ表示してみる

●SECTION 2 文法編

第5章 計算してみる
第6章 もし~だったら
第7章 まとまりを持ったデータを扱う
第8章 同じ処理を繰り返す
第9章 これまでのコードを改善する
第10章 クラスを利用する
第11章 コメントとコーディング規約

●SECTION 3 発展編

第12章 オブジェクト指向
第13章 クラスを拡張する
第14章 抽象クラス
第15章 インターフェース
第16章 クラスライブラリを使う
第17章 例外
第18章 アノテーション
第19章 テスト
第20章 デバッグ

付録A プログラミングの準備・セットアップ
付録B クイズの解答
付録C その他の情報
コメント (0) |  トラックバック (0) | 

[Android] 標準ウィジェット(1) EditText

2012年03月03日 | Android
                                                                2012-03-09 更新
                                                                2012-03-03 新規
[Android] 標準ウィジェット(1) EditText
================================================================================

Android で文字列データを入力するには、EditText を使う。

この EditText には、いろいろな入力形式が用意されている。
数字だけに限定することもできれば、email で使う文字だけに限定する、また、パスワー
ドの入力に使う、ということも可能。

入力した文字列は
    Editable   getText() 
メソッドを使うが、返り値は Editable 型。通常は toString() メソッドを使って、テキ
ストにして使う。

レイアウトの EditText 要素の android:inputType 属性を使ってこれらを制御する。

------------------- ------------------------------------------------------------
android:inputType 属性
------------------- ------------------------------------------------------------
指定値              動作
none                入力不可になります。
text                文字
textCapCharacters   すべて大文字
textCapWords        単語の先頭を大文字
textCapSentences    文章の先頭を大文字
textAutoCorrect     文字のスペルミスを自動で修正する
textAutoComplete    文字の補完入力する
textMultiLine       文字を複数行入力する
textImeMultiLine    通常の文字入力時は複数行入力を許可せず、
                    IMEによって複数行入力を設定
textUri             URL
textEmailAddress    メールアドレス
textEmailSubject    メールの件名
textShortMessage    ショートメッセージ
textLongMessage     ロングメッセージ
textPersonName      人名
textPostalAddress   住所
textPassword        パスワード入力
textVisiblePassword パスワード入力(パスワードは表示)
textWebEditText     HTML
textFilter          他のデータでフィルタされた文字
textPhonetic        発音表記
number              数値入力
numberSigned        符号付きの数値
numberDecimal       小数入力
phone               電話番号
datetime            日付時刻
date                日付
time                時刻
------------------- ------------------------------------------------------------

参考:
Android Wiki* UIコンポーネント/TextView
http://wikiwiki.jp/android/?UI%A5%B3%A5%F3%A5%DD%A1%BC%A5%CD%A5%F3%A5%C8%2FTextView#inputType


▲ レイアウト

上記のうちのつぎの属性値を確認する。
    textPassword
    textEmailAddress
    textMultiLine
    date
    number

なお、 android:inputType 属性を指定していない場合もあわせて確認する。

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <!-- (1) 通常のテキスト -->

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="文字列" >

        <requestFocus /> <!-- (7) -->
    </EditText>

    <!-- (2) パスワード -->

    <EditText
        android:id="@+id/password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="password"
        android:inputType="textPassword" />

    <!-- (3) email アドレス -->

    <EditText
        android:id="@+id/emailAddress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="email address"
        android:inputType="textEmailAddress" />

    <!-- (4) 複数行 -->

    <EditText
        android:id="@+id/multilineText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Multiline text"
        android:inputType="textMultiLine" />

    <!-- (5) 日付 -->

    <EditText
        android:id="@+id/date"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="date"
        android:inputType="date" />

    <!-- (6) 数値 -->

    <EditText
        android:id="@+id/number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="number"
        android:inputType="number" />

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickHandler"
        android:text="@string/submit" />

    <TextView
        android:id="@+id/text1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>
---

▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">TextField01</string>
    <string name="submit">クリック</string>
</resources>
---


▲ アクティビティ

□ TextField01Activity.java
---
package jp.marunomaruno.android.textfield;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import jp.marunomaruno.android.textfield.R;

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

    public void onClickHandler(View view) {
        EditText edit1 = (EditText) findViewById(R.id.editText1);
        EditText password = (EditText) findViewById(R.id.password);
        EditText emailAddress = (EditText) findViewById(R.id.emailAddress);
        EditText multilineText = (EditText) findViewById(R.id.multilineText);
        EditText date = (EditText) findViewById(R.id.date);
        EditText number = (EditText) findViewById(R.id.number);

        String text = String
                .format(" edit1=%s%n password=%s%n emailAddress=%s%n 
									multilineText=%s%n date=%s%n number=%s%n",
                        edit1.getText(), password.getText(), emailAddress
                                .getText(), multilineText.getText(), date
                                .getText(), number.getText());
        Toast.makeText(this, text, Toast.LENGTH_LONG).show();

        TextView text1 = (TextView) findViewById(R.id.text1);
        text1.setText(text);
    }
}
---

△ EditText クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.TextView 
       +  android.widget.EditText 

ユーザーからの入力をサポートするためのクラス。
レイアウトでは、android:inputType 属性をつけることで、入力値を制限することができ
る。
特徴としては、getText() メソッドの返り値の型が Editable 型である。
String 型として、入力した値を取得するには、この返り値のオブジェクトの toString()
 メソッドを使って取得する必要がある。

・主なメソッド
---
Editable   getText() 
void       setText(CharSequence text, TextView.BufferType type)  

void       selectAll() 
void       setEllipsize(TextUtils.TruncateAt ellipsis) 
void       extendSelection(int index) 
void       setSelection(int index) 
void       setSelection(int start, int stop) 

void       setFilters(InputFilter[] filters)
final void setHint(CharSequence hint)
final void setHint(int resid)
void       setKeyListener(KeyListener input)
void       setHorizontallyScrolling(boolean whether)
void       setHeight(int pixels)
void       setWidth(int pixels)
---


△ Editable インターフェース

android.text.Editable

テキスト操作に関するインターフェース。
実際に使うためには、toString() メソッドで、テキストにしてから使う場合が多い。


★疑問
---
EditText の getText() はなぜ Editable 型で返すんだろうか? スーパークラスの 
TextView は CharSequence なのに。CharSequence は、String でも StringBuilder でも
いいので納得できるが。。
---


■ EditView で、最初からソフトウェアキーボードを表示しておく方法 (2012-03-09 追記)

レイアウトか Activity クラスで、目的の EditText にフォーカスを当てておく。
マニフェストの activity 要素で、android:windowSoftInputMode="stateVisible" 属性をつける。


□ レイアウト

---
    <EditText
        ... >
        <requestFocus /> <!-- <= これを指定 -->
    </EditText>
---


□ Activity クラス

---
    EditText editText = new EditText(this);
    editText.requestFocus();    // <= フォーカスを当てる(XML での指定でもよい)
---


□ AndroidManifest.xml

activity 要素に android:windowSoftInputMode 属性を追加

---
    <activity android:name=".XxxActivity"
              android:label="@string/app_name"
              android:windowSoftInputMode="stateVisible">  // <= これで表示される
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
---

                                                                        以上

コメント (0) |  トラックバック (0) | 

[Java] UTC 時間と SimpleDateFormat の不思議

2012年03月02日 | Java
[Java] UTC 時間と SimpleDateFormat の不思議
================================================================================

UTC 時間を表示するときに、SimpleDateFormat を使って表示すると、JST 時間で表示さ
れる。System.out.printf()で %tT とすると、ちゃん と UTC として表示される。なぜ?
オブジェクトは、ただしく UTC として作られている。

□ UTCSample.java
---
package jp.rutles.sample;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

public class UTCSample {

    public static void main(String[] args) {
        SimpleDateFormat fmt =
                new SimpleDateFormat(
                    "yyyy 年 M 月 d 日 (E) HH:mm:ss");

        Calendar today = Calendar.getInstance();
        System.out.println(today);
        System.out.println(fmt.format(today.getTime()));
        System.out.printf("%1$tF %1$tT%n%n", today);

        TimeZone tz = TimeZone.getTimeZone("UTC");
        Calendar utc = Calendar.getInstance(tz);
        System.out.println(utc);
        System.out.println(fmt.format(utc.getTime()));
        System.out.printf("%1$tF %1$tT%n%n", utc);
    }

}
/* 実行結果
java.util.GregorianCalendar[time=1330561100890,areFieldsSet=true,areAllFieldsSet
=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Tokyo",offset=324000
00,dstSavings=0,useDaylight=false,transitions=10,lastRule=null],firstDayOfWeek=1,
minimalDaysInFirstWeek=1,ERA=1,YEAR=2012,MONTH=2,WEEK_OF_YEAR=9,WEEK_OF_MONTH=1,
DAY_OF_MONTH=1,DAY_OF_YEAR=61,DAY_OF_WEEK=5,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=
9,HOUR_OF_DAY=9,MINUTE=18,SECOND=20,MILLISECOND=890,ZONE_OFFSET=32400000,DST_OFF
SET=0]
2012 年 3 月 1 日 (木) 09:18:20
2012-03-01 09:18:20

java.util.GregorianCalendar[time=1330561100906,areFieldsSet=true,areAllFieldsSet
=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=
0,useDaylight=false,transitions=0,lastRule=null],firstDayOfWeek=1,minimalDaysInF
irstWeek=1,ERA=1,YEAR=2012,MONTH=2,WEEK_OF_YEAR=9,WEEK_OF_MONTH=1,DAY_OF_MONTH=1,
DAY_OF_YEAR=61,DAY_OF_WEEK=5,DAY_OF_WEEK_IN_MONTH=1,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,
MINUTE=18,SECOND=20,MILLISECOND=906,ZONE_OFFSET=0,DST_OFFSET=0]
2012 年 3 月 1 日 (木) 09:18:20
2012-03-01 00:18:20
*/
---

                                                                            以上


コメント (0) |  トラックバック (0) | 

[Android] フォームの標準ウィジェット(2) 静的な選択関連 - 数値の指定

2012年03月01日 | Android
[Android] フォームの標準ウィジェット(2) 静的な選択関連 - 数値の指定
================================================================================

※ 前回、記事サイズの関係で紹介できなかったシークバーとレーティングバーについて紹介する。

選択する以下の ウィジェット(Widget)とそのイベント処理。
    ・ボタン(前回紹介)
    ・チェックボックス(前回紹介)
    ・トグルボタン(前回紹介)
    ・ラジオボタン(前回紹介)
    ・スピナー(前回紹介)
    ・シークバー
    ・レーティングバー

上記のウィジェットは、それぞれつぎのクラスを使う。また、そのときに使われるリス
ナー・インターフェースも示す。

------------------ -------------- ----------------------------------------------
ウィジェット       クラス         リスナー・インターフェース
------------------ -------------- ----------------------------------------------
ボタン             Button         View.OnClickListener, View.OnLongClickListener
                                  View.OnTouchListener
チェックボックス   CheckBox       CompoundButton.OnCheckedChangeListener
                                  View.OnClickListener, View.OnLongClickListener
                                  View.OnTouchListener
トグルボタン       ToggleButton   CompoundButton.OnCheckedChangeListener
                                  View.OnClickListener, View.OnLongClickListener
                                  View.OnTouchListener
ラジオボタン       RadioGroup     RadioGroup.OnCheckedChangeListener
                   RadioButton
スピナー           Spinner        AdapterView.OnItemSelectedListener
                                  View.OnTouchListener
シークバー         SeekBar        SeekBar.OnSeekBarChangeListener
                                  View.OnTouchListener
レーティングバー   RatingBar      RatingBar.OnRatingBarChangeListener
                                  View.OnTouchListener
------------------ -------------- ----------------------------------------------

このサンプルは、アプリケーションの起動時に選択する値が決まっているパターンを示す。


▲ レイアウト

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <!-- (7) シークバー -->
    <SeekBar
        android:id="@+id/seekBar1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="50" />

    <!-- (8) レーティングバー -->
    <RatingBar
        android:id="@+id/ratingBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:rating="2.5" />

</LinearLayout>
---

(7) シークバー

シークバーは、バー(スライダー)を指でタッチして、伸ばしたり縮めたりして、数値を設
定するウィジェット。EditText と違い、一定の範囲内で数値を設定できる。

    <SeekBar
        android:id="@+id/seekBar1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="50" />

android:max        最大値
android:progress   初期状態での設定値


(8) レーティングバー

シークバーの一種で、5 段階評価のような感じの指定ができる。

    <RatingBar
        android:id="@+id/ratingBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:rating="2.5" />

android:rating    初期状態での設定値


▲リソース値

□ res/values/strings.xml
※ 前回と同じ


▲ アクティビティ

単に、Widget にあわせたリスナーを割り当てているだけである。
なお、リスナーはインナー・クラスを使っている。
どのハンドラーが使われたかは、トーストとログで表示している。

□ FormWidgets01Activity.java
package jp.marunomarun.android.formwidgets;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RatingBar;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.ToggleButton;

public class FormWidgets01Activity extends Activity {
    private static final int TOAST_DURATION = Toast.LENGTH_SHORT;

    private Context context; // (1)

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

        OnCheckAndLongClickListener clickListener = new OnCheckAndLongClickListener();
        OnTouchListener touchListener = new OnTouchListener();

        // シークバー
        SeekBar seekBar1 = (SeekBar) findViewById(R.id.seekBar1);
        seekBar1.setOnSeekBarChangeListener(new OnSeekBarListener());
        seekBar1.setOnClickListener(clickListener); // 動かないだけ
        seekBar1.setOnLongClickListener(clickListener); // 動かないだけ
        seekBar1.setOnTouchListener(touchListener);

        // レーティングバー
        RatingBar raitingBar1 = (RatingBar) findViewById(R.id.ratingBar1);
        raitingBar1.setOnRatingBarChangeListener(new OnRatingBarListener());
        raitingBar1.setOnClickListener(clickListener); // 動かないだけ
        raitingBar1.setOnLongClickListener(clickListener); // 動かないだけ
        raitingBar1.setOnTouchListener(touchListener);
    }

    /**
     * ボタンのクリック、長クリックに対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnCheckAndLongClickListener implements View.OnClickListener,
            View.OnLongClickListener {

        @Override
        public void onClick(View v) {
            String message = String.format(
                    "OnClickListener.onClick() view: %s", v.getTag());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }

        @Override
        public boolean onLongClick(View v) {
            String message = String.format(
                    "OnLongClickListener.onLongClick() view: %s", v.getTag());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
            return false;
        }
    }

    /**
     * タッチに対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnTouchListener implements View.OnTouchListener {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            String message = String.format(
                    "OnTouchListener.onTouch() view: %s, event: %s",
                    v.getTag(), event);
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
            return false;
        }
    }

    /**
     * シークバーの変更に対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnSeekBarListener implements SeekBar.OnSeekBarChangeListener {

        @Override
        public void onProgressChanged(SeekBar seekBar, int progress,
                boolean fromUser) {
            String message = String
                    .format("OnSeekBarChangeListener.onProgressChanged() view: 
                                                    %s, value: %d, fromUser: %b",
                            seekBar.getTag(), progress, fromUser);
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
            String message = String
                    .format("OnSeekBarChangeListener.onStartTrackingTouch() view: 
                                                                %s, value: %d",
                            seekBar.getTag(), seekBar.getProgress());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
            String message = String
                    .format("OnSeekBarChangeListener.onStopTrackingTouch() view:
                                                                 %s, value: %d",
                            seekBar.getTag(), seekBar.getProgress());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }
    }

    /**
     * レーティング・バーの変更に対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnRatingBarListener implements
            RatingBar.OnRatingBarChangeListener {

        @Override
        public void onRatingChanged(RatingBar ratingBar, float rating,
                boolean fromUser) {
            String message = String.format(
                    "RatingBar onRatingChanged() rate: %f, fromUser: %b",
                    rating, fromUser);
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }
    }
}
---

(1)(2) コンテキスト・オブジェクト

内部クラスでトーストを使っているので、このアクティビティ自身(this オブジェクト)
を持っていたほうが都合がよい。

    private Context context;    // (1)
    context = this;    // (2)


(●) シークバー

シークバーは、バー(スライダー)を指でタッチして、伸ばしたり縮めたりして、数値を設
定するウィジェット。EditText と違い、一定の範囲内で数値を設定できる。

    SeekBar seekBar1 = (SeekBar) findViewById(R.id.seekBar1);
    seekBar1.setOnSeekBarChangeListener(new OnSeekBarListener());
    seekBar1.setOnClickListener(clickListener); // 動かないだけ
    seekBar1.setOnLongClickListener(clickListener); // 動かないだけ
    seekBar1.setOnTouchListener(touchListener);

なお、setOnClickListener()、setOnTouchListener() を指定しても動かない。


クリック時の動き
まとめると、ACTION_DOWN > onStartTrackingTouch > onProgressChanged > ACTION_UP >
                                                         onStopTrackingTouch
---
OnTouchListener.onTouch() view: SeekBar, event: MotionEvent{46c41208 action=0 ...}
OnSeekBarChangeListener.onStartTrackingTouch() view: SeekBar, value: 50
OnSeekBarChangeListener.onProgressChanged() view: SeekBar, value: 86, fromUser: true
OnTouchListener.onTouch() view: SeekBar, event: MotionEvent{46c41208 action=1 ...}
OnSeekBarChangeListener.onStopTrackingTouch() view: SeekBar, value: 86
---

長押し時の動き
まとめると、ACTION_DOWN > onStartTrackingTouch > ACTION_MOVE > onProgressChanged
                                             > ACTION_UP > onStopTrackingTouch
---
OnTouchListener.onTouch() view: SeekBar, event: MotionEvent{46c1fb60 action=0 ...}
OnSeekBarChangeListener.onStartTrackingTouch() view: SeekBar, value: 50
OnSeekBarChangeListener.onProgressChanged() view: SeekBar, value: 80, fromUser: true
OnTouchListener.onTouch() view: SeekBar, event: MotionEvent{46c1fb60 action=2 ...}
OnSeekBarChangeListener.onProgressChanged() view: SeekBar, value: 79, fromUser: true
OnTouchListener.onTouch() view: SeekBar, event: MotionEvent{46c1fb60 action=2 ...}
OnTouchListener.onTouch() view: SeekBar, event: MotionEvent{46c1fb60 action=1 ...}
OnSeekBarChangeListener.onStopTrackingTouch() view: SeekBar, value: 79
---


△ SeekBar クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.ProgressBar 
       +  android.widget.AbsSeekBar 
         +  android.widget.SeekBar 

シークバーを管理するクラス。
setMax() で最大値を指定しなければ、0 ~ 100 の範囲での指定になる。


・メソッド
---
void  setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener l) 
synchronized void  setMax(int max)
synchronized int   getProgress()
synchronized void  setProgress(int progress)    
---


△ SeekBar.OnSeekBarChangeListener インターフェース

android.widget.SeekBar.OnSeekBarChangeListener

・メソッド
---
abstract void  onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) 
abstract void  onStartTrackingTouch(SeekBar seekBar) 
abstract void  onStopTrackingTouch(SeekBar seekBar)  
---


(●) レーティングバー

シークバーの一種で、5 段階評価のような感じの指定ができる。

    RatingBar raitingBar1 = (RatingBar) findViewById(R.id.ratingBar1);
    raitingBar1.setOnRatingBarChangeListener(new OnRatingBarListener());
    raitingBar1.setOnClickListener(clickListener); // 動かないだけ
    raitingBar1.setOnLongClickListener(clickListener); // 動かないだけ
    raitingBar1.setOnTouchListener(touchListener);


クック時の動き
まとめると、ACTION_DOWN > ACTION_UP > onRatingChanged
---
OnTouchListener.onTouch() view: RatingBar, event: MotionEvent{46c41208 action=0 ...}
OnTouchListener.onTouch() view: RatingBar, event: MotionEvent{46c1fc70 action=1 ...}
RatingBar onRatingChanged() rate: 5.000000, fromUser: true
---


長押し時の動き
まとめると、ACTION_DOWN > ACTION_MOVE > ACTION_UP > onClick
---
OnTouchListener.onTouch() view: RatingBar, event: MotionEvent{46c1fb60 action=0 ...]
OnTouchListener.onTouch() view: RatingBar, event: MotionEvent{46c1fb60 action=2 ...}
OnTouchListener.onTouch() view: RatingBar, event: MotionEvent{46c1fb60 action=2 ...]
OnTouchListener.onTouch() view: RatingBar, event: MotionEvent{46c20bd0 action=1 ...]
RatingBar onRatingChanged() rate: 1.000000, fromUser: true
---


△ RatingBar クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.ProgressBar 
       +  android.widget.AbsSeekBar 
         +  android.widget.RatingBar 

レーティング・バーを管理するクラス。


・主なメソッド
---
int      getNumStars() 
float    getRating() 
float    getStepSize() 
boolean  isIndicator()  
void     setIsIndicator(boolean isIndicator) 
synchronized void  setMax(int max) 
void     setNumStars(int numStars) 
void     setOnRatingBarChangeListener(RatingBar.OnRatingBarChangeListener listener)
void     setRating(float rating) 
void     setStepSize(float stepSize)  
---


△ RatingBar.OnRatingBarChangeListener インターフェース

android.widget.RatingBar.OnRatingBarChangeListener

レーティング・バーが変化したときのリスナー・インターフェース。


・メソッド
---
abstract void  onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser) 
---

                                                                            以上

コメント (0) |  トラックバック (0) | 

[Android] フォームの標準ウィジェット(1) 静的な選択関連

2012年02月29日 | Android
[Android] フォームの標準ウィジェット(1) 静的な選択関連
================================================================================

ウィジェットは、ホーム画面に追加される小さな機能。
Android では、標準で、いくつかのウィジェットを用意している。これらを使って、デー
タを表示したり、入力したり、選択したりすることができる。

選択する以下の ウィジェット(Widget)とそのイベント処理。
    ・ボタン
    ・チェックボックス
    ・トグルボタン
    ・ラジオボタン
    ・スピナー
    ・シークバー(別記)
    ・レーティングバー(別記)

上記のウィジェットは、それぞれつぎのクラスを使う。また、そのときに使われるリス
ナー・インターフェースも示す。

------------------ -------------- ----------------------------------------------
ウィジェット       クラス         リスナー・インターフェース
------------------ -------------- ----------------------------------------------
ボタン             Button         View.OnClickListener, View.OnLongClickListener
                                  View.OnTouchListener
チェックボックス   CheckBox       CompoundButton.OnCheckedChangeListener
                                  View.OnClickListener, View.OnLongClickListener
                                  View.OnTouchListener
トグルボタン       ToggleButton   CompoundButton.OnCheckedChangeListener
                                  View.OnClickListener, View.OnLongClickListener
                                  View.OnTouchListener
ラジオボタン       RadioGroup     RadioGroup.OnCheckedChangeListener
                   RadioButton
スピナー           Spinner        AdapterView.OnItemSelectedListener
                                  View.OnTouchListener
シークバー         SeekBar        SeekBar.OnSeekBarChangeListener
                                  View.OnTouchListener
レーティングバー   RatingBar      RatingBar.OnRatingBarChangeListener
                                  View.OnTouchListener
------------------ -------------- ----------------------------------------------

このサンプルは、アプリケーションの起動時に選択する値が決まっているパターンを示す。


▲ レイアウト

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <!-- (1) ボタン -->
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

    <!-- (2) チェックボックス -->
    <CheckBox
        android:id="@+id/checkBox1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />

    <!-- (3) トグルボタン -->
    <ToggleButton
        android:id="@+id/toggleButton1"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:text="ToggleButton" />

    <!-- (4) ラジオボタン -->
    <RadioGroup
        android:id="@+id/radioGroup1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <RadioButton
            android:id="@+id/radio0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="@string/listItem0" />    <!-- (5) -->

        <RadioButton
            android:id="@+id/radio1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/listItem1" />

        <RadioButton
            android:id="@+id/radio2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/listItem2" />
    </RadioGroup>

    <!-- (6) スピナー -->
    <Spinner
        android:id="@+id/spinner1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/list1" />

</LinearLayout>
---

(1) ボタン

クリックする、を実現している基本的なユーザー・インターフェースになっているビュー。
クリック時、長いクリック(長押し)時の処理をハンドラーとしてプログラムする。また、
タッチ時の処理もハンドリングできる。

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />


ボタンに限らず、GUI 部品の属性には、android:onClick 属性があり、クリックされたと
きに動くハンドラー・メソッドを指定することができる。
今回は、リスナー・クラスを用意して、それぞれの部品の setOnXxxListener() メソッド
でリスナーを登録するので、ここでは指定はしていない。


(2) チェックボックス

チェックしている状態とチェックしていない状態の 2 つの状態を持つビュー。
チェックボックスをクリックするたびに、この 2 つの状態が入れ替わる。

    <CheckBox
        android:id="@+id/checkBox1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />

android:checked 属性が "true" のとき、最初からチェックがついている状態になる。


(3) トグルボタン

トグルボタンは、クリックするたびに状態が OFF/ON を繰り返す種類のボタンである。

    <ToggleButton
        android:id="@+id/toggleButton1"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:text="ToggleButton" />

android:checked 属性が "true" のとき、最初から ON の状態になる。

android:text 要素に値を設定しても表示としては変わらない。表示の文字列をデフォル
トの「OFF」「ON」から変更するには、android:textOff 属性と、android:textOn 属性を
使う。


 (4)(5) ラジオボタン

チェックボックスのように、チェックしている状態とチェックしていない状態の 2 つの
状態を持つビュー。
ただし、グループ化することで、そのグループの中ではひとつだけがチェックしている状
態になる。
同じグループは、RadioGroup 要素で囲む。

    <RadioGroup
        android:id="@+id/radioGroup1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <RadioButton
            android:id="@+id/radio0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="@string/listItem0" />    <!-- (5) -->

android:checked 属性が "true" の項目があれば、それが最初からチェックがついている
状態になる。


(6) スピナー

選択リスト。複数の項目からひとつを選択できるのは、ラジオボタンと同じ。
アプリ起動時に、このリスナーのハンドラーが動く。

    <Spinner
        android:id="@+id/spinner1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:entries="@array/list1" />

なにも設定しないと、リストの先頭のものが選択された状態になる。

android:entries 属性に、表示する項目のリストを設定する。
この例のように静的な場合は、リソースの <string-array> 要素で設定している。

※ なお、最初に選択する項目の指定は、プログラムで指定するしかなさそう。
Spinner.setSelection() に相当する XML 属性がない。


▲リソース値

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">FormWidgets01</string>
    <string name="listItem0">アイスクリーム</string>
    <string name="listItem1">かりんとう</string>
    <string name="listItem2">さつまいも</string>

    <string-array name="list1">
        <item>@string/listItem0</item>
        <item>@string/listItem1</item>
        <item>@string/listItem2</item>
    </string-array>

</resources>
---

string-array 要素を使って、Spinner で使う項目を設定している。


▲ アクティビティ

単に、Widget にあわせたリスナーを割り当てているだけである。
なお、リスナーはインナー・クラスを使っている。
どのハンドラーが使われたかは、トーストとログで表示している。

□ FormWidgets01Activity.java
package jp.marunomarun.android.formwidgets;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RatingBar;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.ToggleButton;

public class FormWidgets01Activity extends Activity {
    private static final int TOAST_DURATION = Toast.LENGTH_SHORT;

    private Context context; // (1)

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

        // ボタン
        Button button1 = (Button) findViewById(R.id.button1);
        OnCheckAndLongClickListener clickListener = new OnCheckAndLongClickListener();
        OnTouchListener touchListener = new OnTouchListener();
        button1.setOnClickListener(clickListener);
        button1.setOnLongClickListener(clickListener);
        button1.setOnTouchListener(touchListener);

        // チェックボックス
        CheckBox check1 = (CheckBox) findViewById(R.id.checkBox1);
        check1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener());
        check1.setOnClickListener(clickListener);
        check1.setOnLongClickListener(clickListener);
        check1.setOnTouchListener(touchListener);

        // トグルボタン
        ToggleButton toggle1 = (ToggleButton) findViewById(R.id.toggleButton1);
        toggle1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener());
        toggle1.setOnClickListener(clickListener);
        toggle1.setOnLongClickListener(clickListener);
        toggle1.setOnTouchListener(touchListener);

        // ラジオボタン
        RadioGroup group1 = (RadioGroup) findViewById(R.id.radioGroup1);
        group1.setOnCheckedChangeListener(new OnCheckRadioGroupListener());
        group1.setOnClickListener(clickListener); // 動かないだけ 
        group1.setOnLongClickListener(clickListener); // 動かないだけ
        group1.setOnTouchListener(touchListener); // 動かないだけ

        // スピナー
        Spinner spinner1 = (Spinner) findViewById(R.id.spinner1);
        spinner1.setOnItemSelectedListener(new OnItemSelectedSpinnerListener());
        // spinner1.setOnClickListener(listener); // RuntimeException)
        // spinner1.setOnLongClickListener(listener); // RuntimeException
        spinner1.setOnTouchListener(touchListener);
    }

    /**
     * ボタンのクリック、長クリックに対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnCheckAndLongClickListener implements View.OnClickListener,
            View.OnLongClickListener {

        @Override
        public void onClick(View v) {
            String message = String.format(
                    "OnClickListener.onClick() view: %s", v.getTag());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }

        @Override
        public boolean onLongClick(View v) {
            String message = String.format(
                    "OnLongClickListener.onLongClick() view: %s", v.getTag());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
            return false;
        }
    }

    /**
     * タッチに対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnTouchListener implements View.OnTouchListener {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            String message = String.format(
                    "OnTouchListener.onTouch() view: %s, event: %s",
                    v.getTag(), event);
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
            return false;
        }
    }

    /**
     * チェックボックス、トグルボタンのクリックに対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnCheckCompoundButtonListener implements
            CompoundButton.OnCheckedChangeListener {

        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
            String message = String.format(
                    "OnCheckedChangeListener.onCheckedChanged(): view: %s, %b",
                    buttonView.getTag(), isChecked);
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }
    }

    /**
     * ラジオボタンのクリックに対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnCheckRadioGroupListener implements
            RadioGroup.OnCheckedChangeListener {

        @Override
        public void onCheckedChanged(RadioGroup group, int checkedId) {
            RadioButton button = (RadioButton) findViewById(checkedId);

            String message = String.format(
                    "OnCheckedChangeListener.onCheckedChanged(): %s", button
                            .getText());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }
    }

    /**
     * スピナーの選択に対するリスナー
     * 
     * @author marunomaruno
     */
    private class OnItemSelectedSpinnerListener implements
            AdapterView.OnItemSelectedListener {

        @Override
        public void onItemSelected(AdapterView<?> parent, View view,
                int position, long id) {
            Spinner spinner = (Spinner) parent;
            String item = (String) spinner.getSelectedItem();

            String message = String
                    .format("OnItemSelectedListener.onItemSelected() parent: 
                                                        %s, view: %s, %d: %s",
                            parent.getTag(), view.getTag(), position, item);
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            String message = String.format(
                    "OnItemSelectedListener.onNothingSelected() parent: %s",
                    parent.getTag());
            System.out.println(message);
            Toast.makeText(context, message, TOAST_DURATION).show();
        }
    }

---

(1)(2) コンテキスト・オブジェクト

内部クラスでトーストを使っているので、このアクティビティ自身(this オブジェクト)
を持っていたほうが都合がよい。

    private Context context;    // (1)
    context = this;    // (2)


(●) ボタン

クリックする、を実現している基本的なユーザー・インターフェースになっているビュー。
クリック、長押し時の処理をハンドラーとしてプログラムする。また、タッチ時の処理も
ハンドリングできる。

    Button button1 = (Button) findViewById(R.id.button1);
    button1.setOnClickListener(clickListener);
    button1.setOnLongClickListener(clickListener);
    button1.setOnTouchListener(touchListener);

クリックは setOnClickListener()、長押しは setOnLongClickListener()、タッチは 
setOnTouchListener() を使ってそれぞれのリスナー・オブジェクトを設定する。

クリックのときは、OnTouchListener.onTouch() の ACTION_DOWN イベント、そして 
ACTION_UP が動く。
この戻り値が false のとき、OnClickListener.onClick()。
まとめると、ACTION_DOWN > ACTION_UP > onClick
---
OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fc70 action=0 ...}
OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fc70 action=1 ...}
OnClickListener.onClick() view: Button
---

長押しのときは、クリックに加えて、ACTION_MOVE も動く。
まとめると、ACTION_DOWN > ACTION_MOVE > onLongClick > ACTION_UP > onClick
---
OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fb60 action=0 ...}
OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fb60 action=2 ...}
OnLongClickListener.onLongClick() view: Button
OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fb60 action=1 ...}
OnClickListener.onClick() view: Button
---


△ Button クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.TextView 
       +  android.widget.Button 

クリックして何らかの動作をさせるためのウィジェット。
Button クラス独自のメソッドはなく、スーパークラスである TextView クラスのメソッ
ドをそのまま利用している。

クリックに関しては、通常のクリックと、長クリック(長押し)の 2 つの種類がある。
---
void     setOnClickListener(View.OnClickListener l)
void     setOnLongClickListener(View.OnLongClickListener l)
void     setOnTouchListener(View.OnTouchListener l)
---
(*1) TextView のメソッド


△ View.OnClickListener インターフェース

android.view.View.OnClickListener

クリックに関するリスナー・インターフェース。
このハンドラー・メソッドの onClick() の指定は、ウィジェット上で頻繁に使うことが
多いので、レイアウトの XML ファイルでも、android:onClick 属性として用意されてい
る。

・メソッド
---
abstract void  onClick(View v)  
---


△ View.OnLongClickListener インターフェース

android.view.View.OnLongClickListener

長押しに関するリスナー・インターフェース。

・メソッド
---
abstract boolean  onLongClick(View v)  
---


(●) チェックボックス

チェックしている状態とチェックしていない状態の 2 つの状態を持つビュー。
チェックボックスをクリックするたびに、この 2 つの状態が入れ替わる。

    CheckBox check1 = (CheckBox) findViewById(R.id.checkBox1);
    check1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener());
    check1.setOnClickListener(clickListener);
    check1.setOnLongClickListener(clickListener);
    check1.setOnTouchListener(touchListener);


クリック時の動き
まとめると、ACTION_DOWN > ACTION_UP > onCheckedChanged > onClick
---
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fc70 action=0 ...}
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fc70 action=1 ...}
OnCheckedChangeListener.onCheckedChanged(): view: CheckBox, false
OnClickListener.onClick() view: CheckBox
---


長押し時の動き
まとめると、ACTION_DOWN > ACTION_MOVE > onLongClick > ACTION_UP > 
                                                    onCheckedChanged > onClick
---
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=0 ...]
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=2 ...}
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=2 ...}
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=2 ...}
OnLongClickListener.onLongClick() view: CheckBox
OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=1 ...}
OnCheckedChangeListener.onCheckedChanged(): view: CheckBox, false
OnClickListener.onClick() view: CheckBox
---


△ CheckBox クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.TextView 
       +  android.widget.Button 
         +  android.widget.CompoundButton 
           +  android.widget.CheckBox 


チェックする・外すことができるウィジェット。

・メソッドなどは、スーパークラスである CompoundButton クラスのメソッドを利用して
いる。CheckBox として使うのは、以下のものが主。
---
boolean  isChecked()
void     setChecked(boolean checked)
void     setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener)
void     toggle()    
---


△ CompoundButton クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.TextView 
       +  android.widget.Button 
         +  android.widget.CompoundButton 

CheckBox や RadioButton、ToggleButton クラスなどの、ボタンをクリックするとそのボ
タンの状態も変化する種類のクラスのスーパークラス。

サブクラスとして、次のクラスがある。
    CheckBox, 
    RadioButton, 
    Switch, 
    ToggleButton 


・主なメソッド
---
boolean  isChecked()  
void     setButtonDrawable(int resid) 
void     setButtonDrawable(Drawable d) 
void     setChecked(boolean checked) 
void     setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) 
void     toggle()  
---


△ CompoundButton.OnCheckedChangeListener インターフェース

android.widget.CompoundButton.OnCheckedChangeListener

ボタンの状態が変化したときのリスナー・インターフェース。

・メソッド
---
abstract void  onCheckedChanged(CompoundButton buttonView, boolean isChecked)  
---
isChecked:    チェックされていれば true


(●) トグルボタン

トグルボタンは、クリックするたびに状態が OFF/ON を繰り返す種類のボタンである。

    ToggleButton toggle1 = (ToggleButton) findViewById(R.id.toggleButton1);
    toggle1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener());
    toggle1.setOnClickListener(clickListener);
    toggle1.setOnLongClickListener(clickListener);
    toggle1.setOnTouchListener(touchListener);


クリック時の動き
まとめると、ACTION_DOWN > ACTION_UP > onCheckedChanged > onClick
---
OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fc70 action=0 ...}
OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fc70 action=1 ...}
OnCheckedChangeListener.onCheckedChanged(): view: ToggleButton, false
OnClickListener.onClick() view: ToggleButton
---


長押し時の動き
まとめると、ACTION_DOWN > ACTION_MOVE > onLongClick > ACTION_UP > 
                                                    onCheckedChanged > onClick
---
OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fb60 action=0 ...}
OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fb60 action=2 ...]
OnLongClickListener.onLongClick() view: ToggleButton
OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fb60 action=1 ...]
OnCheckedChangeListener.onCheckedChanged(): view: ToggleButton, false
OnClickListener.onClick() view: ToggleButton
---


△ ToggleButton クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.TextView 
       +  android.widget.Button 
         +  android.widget.CompoundButton 
           +  android.widget.ToggleButton 

トグル・ボタンをあらわすクラス。何も指定しなければ、ボタンに表示されるテキストは
「OFF」と「ON」で、図形としてはライトが消えている状態とついている状態。


・主なメソッド
---
CharSequence  getTextOff() 
CharSequence  getTextOn() 
void  setChecked(boolean checked) 
void  setTextOff(CharSequence textOff) 
void  setTextOn(CharSequence textOn)  
void  setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener)
---


(●) ラジオボタン

チェックボックスのように、チェックしている状態とチェックしていない状態の 2 つの
状態を持つビュー。
ただし、グループ化することで、そのグループの中ではひとつだけがチェックしている状
態になる。
グループ化は RadioGroup クラスを使う。

    RadioGroup group1 = (RadioGroup) findViewById(R.id.radioGroup1);
    group1.setOnCheckedChangeListener(new OnCheckRadioGroupListener());
    group1.setOnClickListener(clickListener); // 動かないだけ 
    group1.setOnLongClickListener(clickListener); // 動かないだけ
    group1.setOnTouchListener(touchListener); // 動かないだけ


選択したときには、とくに OnClickListener.onClick()、OnLongClickListener
.onLongClick()、OnTouchListener.onTouch() が動いていない。これは、RadioGroup で
はなく、RadioButton オブジェクトで設定するもの。

選択時の動き
---
OnCheckedChangeListener.onCheckedChanged(): かりんとう
OnCheckedChangeListener.onCheckedChanged(): さつまいも
---


△ RadioGroup クラス

java.lang.Object 
   +  android.view.View 
     +  android.view.ViewGroup 
       +  android.widget.LinearLayout 
         +  android.widget.RadioGroup 

RadioButton オブジェクトを持つクラス。


・主なメソッド
---
void  check(int id) 
void  clearCheck() 
int   getCheckedRadioButtonId()    チェックされているボタンがなければ -1 が返る
void  setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener listener)
---


△ RadioButton クラス

java.lang.Object 
   +  android.view.View 
     +  android.widget.TextView 
       +  android.widget.Button 
         +  android.widget.CompoundButton 
           +  android.widget.RadioButton 

個個のラジオボタンを管理するクラス。

・主なメソッド
---
void  toggle()  
---


△ RadioGroup.OnCheckedChangeListener インターフェース

android.widget.RadioGroup.OnCheckedChangeListener

ラジオボタンのチェック状態が変化したときのリスナー。


・メソッド
---
abstract void  onCheckedChanged(RadioGroup group, int checkedId)  
---
checkedId: チェックされた ラジオボタンのリソース ID


(●) スピナー

選択リスト。複数の項目からひとつを選択できるのは、ラジオボタンと同じ。
アプリ起動時に、このリスナーのハンドラーが動く。
---
OnItemSelectedListener.onItemSelected() parent: Spinner, view: null, 0: アイスクリーム
---

    Spinner spinner1 = (Spinner) findViewById(R.id.spinner1);
    spinner1.setOnItemSelectedListener(new OnItemSelectedSpinnerListener());
    // spinner1.setOnClickListener(clickListener); // RuntimeException)
    // spinner1.setOnLongClickListener(clickListener); // RuntimeException
    spinner1.setOnTouchListener(touchListener);


上のサンプルコードにあるように、setOnClickListener()、setOnLongClickListener() 
を設定すると、実行時に RuntimeException がスローされる。


選択時の動き
まとめると、ACTION_DOWN > ACTION_UP > onItemSelected
---
OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c1fc70 action=0 ...}
OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c41208 action=1 ...}
OnItemSelectedListener.onItemSelected() parent: Spinner, view: null, 1: かりんとう
OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c41208 action=0 ...}
OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c41208 action=1 ...}
OnItemSelectedListener.onItemSelected() parent: Spinner, view: null, 2: さつまいも
---


△ Spinner クラス

java.lang.Object 
   +  android.view.View 
     +  android.view.ViewGroup 
       +  android.widget.AdapterView<T extends android.widget.Adapter> 
         +  android.widget.AbsSpinner 
           +  android.widget.Spinner 

選択リストを管理するクラス。


・主なメソッド
---
int     getBaseline() 
CharSequence  getPrompt()  
void    onClick(DialogInterface dialog, int which) 
boolean performClick() 
void    setAdapter(SpinnerAdapter adapter) 
void    setEnabled(boolean enabled) 
void    setGravity(int gravity) 
void    setOnItemClickListener(AdapterView.OnItemClickListener l) 
void    setPrompt(CharSequence prompt) 
void    setPromptId(int promptId) 
---

△ AdapterView クラス

java.lang.Object 
   +  android.view.View 
     +  android.view.ViewGroup 
       +  android.widget.AdapterView<T extends android.widget.Adapter> 

配列やリストからの値を管理できるタイプのビューのスーパークラス。

直接のサブクラスではないが、つぎのクラスがこのクラスを継承している。

GridView
ListView 
Spinner 


・項目選択関係のメソッド
---
Object  getSelectedItem()  
long  getSelectedItemId()  
int  getSelectedItemPosition()  
---

なお、このビューには、setOnClickListener() や setOnLongClickListener() で設定す
るリスナーは設定できない。実行すると、つぎのメッセージの例外がスロー。
    java.lang.RuntimeException: Don't call setOnClickListener for an AdapterView.
 You probably want setOnItemClickListener instead


△ AdapterView.OnItemSelectedListener インターフェース

項目を選択したときのリスナー・インターフェース。
アプリケーション起動時に最初に表示されている項目に対して、まず、このリスナーが動
く。

android.widget.AdapterView.OnItemSelectedListener

・メソッド
---
abstract void  onItemSelected(AdapterView<?> parent, View view, int position, long id) 
abstract void  onNothingSelected(AdapterView<?> parent)  
---

                                                                            以上

コメント (0) |  トラックバック (1) | 

[Android] データの取り扱い - ArrayAdapter

2012年02月21日 | Android
[Android] データの取り扱い - ArrayAdapter
================================================================================

データベースから取得したデータを、ListView を使って表示する。
このとき、ArrayAdapter クラスをカスタマイズすることで、オブジェクトの配列やリス
ト (List) から、自動的に値を ListView に展開してリスト表示することができる。

基本的な設定・手順は以下のとおり。

・表示したいレイアウト(main.xml)で、ListView を設定

・1 行分のレイアウト(row.xml)を設定

・オブジェクトを設定するためのクラス(ArrayAdapter を継承)を作成
    ・コンストラクターで、オブジェクト配列(リスト)を指定
    ・getView() メソッドをオーバーライドして、1 行分の項目を紐付け

・ListView.setAdapter() メソッドで、アダプターを ListView に設定


▲ アクティビティ

変更点は、以下の 2 箇所のみ。

・ArrayAdapter のサブクラス(ItemListAdapter)のコンストラクターで、row.xml とオブ
ジェクトのリストを紐付け

    ItemListAdapter<Item> adapter = new ItemListAdapter<Item>(this,
            R.layout.row, itemList);    // (1)

・ListView.setAdapter() メソッドで、アダプターを ListView に設定

    ListView listView = (ListView) findViewById(R.id.listView1);
    listView.setAdapter(adapter);    // (2)


□ Database02Activity.java
---
package jp.marunomaruno.android.database;

import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;

public class Database02Activity extends Activity {
    private EditText name = null;
    private EditText comment = null;
    private DatabaseHelper dbHelper = null;
    private int TOAST_DURATION = Toast.LENGTH_SHORT;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        name = (EditText) findViewById(R.id.name);
        comment = (EditText) findViewById(R.id.comment);
        dbHelper = new DatabaseHelper(this);

    }

    @Override
    public void onPause() {
        super.onPause();
        if (dbHelper != null) {
//            dbHelper.close();
        }
    }

    public void onFindButtonClick(View view) {
        List<Item> itemList = dbHelper.select(name.getText());
        if (itemList.size() <= 0) {
            Toast.makeText(this, getString(R.string.notFoundMessage),
                    TOAST_DURATION).show();
            return;
        }

        name.setText(itemList.get(0).getName());
        comment.setText(itemList.get(0).getComment());

        ItemListAdapter adapter = new ItemListAdapter(this, itemList);    // (1)
        ListView listView = (ListView) findViewById(R.id.listView1);
        listView.setAdapter(adapter);    // (2)

        Toast.makeText(
                this,
                String.format(getString(R.string.findMultipleMessage), itemList
                        .size()), TOAST_DURATION).show();
    }

    public void onSaveButtonClick(View view) {

        long id = dbHelper.insert(name.getText(), comment.getText());

        name.setText("");
        comment.setText("");

        if (id > 0) {
            Toast.makeText(this,
                    String.format(getString(R.string.saveMessage), id),
                    TOAST_DURATION).show();

        } else {
            Toast.makeText(this, getString(R.string.notSaveMessage),
                    TOAST_DURATION).show();

        }
    }

    public void onUpdateButtonClick(View view) {
        int count = dbHelper.update(name.getText(), comment.getText());

        if (count > 0) {
            Toast.makeText(this,
                    String.format(getString(R.string.updateMessage), count),
                    TOAST_DURATION).show();

        } else {
            Toast.makeText(this, getString(R.string.notUpdateMessage),
                    TOAST_DURATION).show();

        }
    }

    public void onRemoveButtonClick(View view) {
        int count = dbHelper.delete(name.getText());

        name.setText("");
        comment.setText("");
        if (count > 0) {
            Toast.makeText(this,
                    String.format(getString(R.string.removeMessage), count),
                    TOAST_DURATION).show();

        } else {
            Toast.makeText(this, getString(R.string.notRemoveMessage),
                    TOAST_DURATION).show();

        }
    }
}
---

(1) ArrayAdapter のサブクラス(ItemListAdapter)のコンストラクターで、オブジェクト
のリストを設定

    ItemListAdapter adapter = new ItemListAdapter(this, itemList);    // (1)


(2) ListView.setAdapter() メソッドで、アダプターを ListView に設定

    ListView listView = (ListView) findViewById(R.id.listView1);
    listView.setAdapter(adapter);    // (2)


・ListView クラスのアダプターの設定メソッド
---
void  setAdapter(ListAdapter adapter)  
---


▲ アダプター

ListView の各行に、リストや配列の要素を割り当てるためのアダプター。
項目が複数あるような場合は、ArrayAdapter クラスを継承して、カスタマイズしたアダ
プター・クラスを作って対応する。

手順としては、getView() メソッドをオーバーライドして、そこに、各項目のデータを割
り当てることになる。

□ ItemListAdapter
---
package jp.marunomaruno.android.database;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class ItemListAdapter extends ArrayAdapter<Item> {    // (1)

    public ItemListAdapter(Context context, List<Item> objects) {    // (2)
        super(context, 0, objects);    // (3)
    }

    public ItemListAdapter(Context context, Item[] objects) {    // (4)
        super(context, 0, objects);
    }

    public ItemListAdapter(Context context, int resource,
            int textViewResourceId, List<Item> objects) {
        super(context, resource, textViewResourceId, objects);
    }

    public ItemListAdapter(Context context, int resource,
            int textViewResourceId, Item[] objects) {
        super(context, resource, textViewResourceId, objects);
    }

    public ItemListAdapter(Context context, int resource, int textViewResourceId) {
        super(context, resource, textViewResourceId);
    }

    public ItemListAdapter(Context context, int textViewResourceId,
            List<Item> objects) {
        super(context, textViewResourceId, objects);
    }

    public ItemListAdapter(Context context, int textViewResourceId, Item[] objects) {
        super(context, textViewResourceId, objects);
    }

    public ItemListAdapter(Context context, int textViewResourceId) {
        super(context, textViewResourceId);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) { // (5)
        if (convertView == null) {
            LayoutInflater layoutInflater = (LayoutInflater) getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);    // (6)
            convertView = layoutInflater.inflate(R.layout.row, null);    // (7)
        }

        Item item = getItem(position);    // (8)
        
        TextView id = (TextView) convertView.findViewById(R.id.idInList); // (9)
        id.setText(String.valueOf(item.getId()));    // (10)

        TextView name = (TextView) convertView.findViewById(R.id.nameInList);
        name.setText(item.getName());

        TextView comment = (TextView) convertView
                .findViewById(R.id.commentInList);
        comment.setText(item.getComment());

        return convertView;    // (11)
    }
}
---

public class ItemListAdapter extends ArrayAdapter<Item> {    // (1)

ArrayAdapter クラスを継承して作る。
ArrayAdapter クラスは、総称型となっているので、今回は、Item 型に限定して作る。


・ArrayAdapter クラス

オブジェクトの配列やリストと、行のレイアウトの項目を紐付けるためのアダプター・ク
ラス。

java.lang.Object
   -   android.widget.BaseAdapter
        -   android.widget.ArrayAdapter<T>

・主なコンストラクター
---
ArrayAdapter(Context context, int textViewResourceId, T[] objects)
ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)
ArrayAdapter(Context context, int textViewResourceId, List<T> objects)
ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> obje
cts)
---

・主なメソッド
---
void     add(T object)
void     addAll(Collection<? extends T> collection)
void     addAll(T... items)
void     clear()
int      getCount()
T        getItem(int position)
long     getItemId(int position)
int      getPosition(T item)
void     insert(T object, int index)
void     remove(T object)
---


(2)(3)(4) 独自コンストラクターの定義

ArrayAdapter クラスのコンストラクターで、使わない引数(textViewResourceId)の分
を除いたコンストラクターを作っておく。基本的に、オブジェクトのリストか、オブジェ
クトの配列を受け取れるようにすればよい。

    public ItemListAdapter(Context context, List<Item> objects) {    // (2)
    public ItemListAdapter(Context context, Item[] objects) {    // (4)

ArrayAdapter クラスのコンストラクターを呼び出す。このとき、textViewResourceId は
指定されていないので、0 にしておく。

    super(context, 0, objects);    // (3)


(5) 行項目を設定するメソッドのオーバーライド

    public View getView(int position, View convertView, ViewGroup parent) { // (5)


(6)(7) ビューを取得

アプリケーションが起動した直後の状態なんかでは、convertView が null になっている。
このため、LayoutInflater の inflate() メソッドを使って、ビューのオブジェクトを取
得する必要がある。

    LayoutInflater layoutInflater = (LayoutInflater) getContext()
        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);    // (6)
    convertView = layoutInflater.inflate(R.layout.row, null);    // (7)


Context.LAYOUT_INFLATER_SERVICE は、レイアウトの動的な描画のために Inflater オブ
ジェクトを生成するのに使う定数。

---
View     inflate(int resource, ViewGroup root)
---


・LayoutInflater クラス

動的にレイアウトの XML ファイルのオブジェクトを読み込むためのクラス

java.lang.Object
   -   android.view.LayoutInflater

・主なメソッド
---
View     inflate(int resource, ViewGroup root)
View     inflate(XmlPullParser parser, ViewGroup root)
View     inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
View     inflate(int resource, ViewGroup root, boolean attachToRoot)
---


(8) position 番目のオブジェクトを取得

getItem() メソッドを使って、リストや配列の position 番目のオブジェクトを取得する。

    Item item = getItem(position);    // (8)


(9)(10) テキストに設定する

    TextView id = (TextView) convertView.findViewById(R.id.idInList);    // (9)
    id.setText(String.valueOf(item.getId()));    // (10)


(11) ビューを返す

    return convertView;    // (11)


▲ データベース関係

□ DatabaseHelper.java
※前回のサンプルと同じ


▲エンティティ

□ Item.java
※前回のサンプルと同じ


▲レイアウト

テーブルの行の一覧を表示するための ListView 要素が追加されている。

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="1" >

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/nameHint" >

        <requestFocus >
        </requestFocus>
    </EditText>

    <EditText
        android:id="@+id/comment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/commentHint"
        android:inputType="textMultiLine" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/findButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onFindButtonClick"
            android:text="@string/findButton" />

        <Button
            android:id="@+id/saveButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onSaveButtonClick"
            android:text="@string/saveButton" />

        <Button
            android:id="@+id/updateButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onUpdateButtonClick"
            android:text="@string/updateButton" />

        <Button
            android:id="@+id/removeButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onRemoveButtonClick"
            android:text="@string/removeButton" />
    </LinearLayout>

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />    <!-- (1) -->

</LinearLayout>
---

(1) リスト・ビュー

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />    <!-- (1) -->


1 行分のデータは、つぎのファイルで定義している。

□ res/layout/row.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/idInList"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/nameInList"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/commentInList"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>
---


▲ 文字列
※前回のサンプルと同じ

                                                                            以上


コメント (0) |  トラックバック (0) | 

[Android] データの取り扱い - データベース(SQLite)

2012年02月20日 | Android
[Android] データの取り扱い - データベース(SQLite)
================================================================================

データベース(SQLite)を使って、データの照会・保存・変更・削除を行う。

データ(列)は、つぎの 2 つ。
    ・名前
    ・コメント

なお、データベースのテーブルを使うにあたり、このデータを区別するための主キーとし
て、ID 列をつける。なお、SQLite では、この「PRIMARY KEY AUTOINCREMENT」の属性を
持った列は、「_id」という列名にする。こうしておくことで、CursorAdapter クラスを
使って ListView にデータを表示させることができる。
※ただし、CursorAdapter を使うのは、V3.x 以降では推奨されていないようだ。

データベースのテーブル生成文は以下のとおり。
        CREATE TABLE item (
            _id INTEGER PRIMARY KEY AUTOINCREMENT
            , name TEXT
            , comment TEXT
        )

このサンプル・アプリケーションではつぎのことができる。

・照会
    「名前」欄に記述されたものと一致するものをすべて取得するが、表示するのは最初
    に取得したものだけ。

・保存
    「名前」「コメント」欄に記述された内容をデータベースのテーブルに保存する。

・変更
    「名前」欄に記述されたものと一致するものすべての「コメント」を、すべて変更す
    る。

・削除
    「名前」欄に記述されたものと一致するものすべてを削除する。


データベースを扱うクラスとして、DatabaseHelper クラスを作る。これは、
SQLiteOpenHelper クラス(抽象クラス)を継承して作る。
次のメソッドのオーバーライドが必要。
abstract void     onCreate(SQLiteDatabase db)
abstract void     onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)


今回のクラス
---
Database01Activity  アクティビティ
DatabaseHelper      データベース操作
Item                エンティティ
---


▲ アクティビティ

データベースに関する操作はすべて DatabaseHelper オブジェクトで行っている。

□ Database01Activity.java
---
package jp.marunomaruno.android.database;

import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class Database01Activity extends Activity {
    private EditText name = null;
    private EditText comment = null;
    private DatabaseHelper dbHelper = null;    // (1)
    private int TOAST_DURATION = Toast.LENGTH_SHORT;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        name = (EditText) findViewById(R.id.name);
        comment = (EditText) findViewById(R.id.comment);
        dbHelper = new DatabaseHelper(this);    // (2)
    }

    @Override
    public void onPause() {
        super.onPause();
        if (dbHelper != null) {
            // dbHelper.close();    // 呼ばないほうがよい    // (3)
        }
    }

    public void onFindButtonClick(View view) {
        List<Item> itemList = dbHelper.select(name.getText());    // (4)
        if (itemList.size() <= 0) {
            Toast.makeText(this, getString(R.string.notFoundMessage),
                    TOAST_DURATION).show();
            return;
        }

        name.setText(itemList.get(0).getName());
        comment.setText(itemList.get(0).getComment());
        Toast.makeText(
                this,
                (itemList.size() == 1 ? String
                        .format(getString(R.string.findMessage)) : String
                        .format(getString(R.string.findMultipleMessage),
                                itemList.size())), TOAST_DURATION).show();
    }

    public void onSaveButtonClick(View view) {

        long id = dbHelper.insert(name.getText(), comment.getText());    // (5)

        name.setText("");
        comment.setText("");
        if (id > 0) {
            Toast.makeText(this,
                    String.format(getString(R.string.saveMessage), id),
                    TOAST_DURATION).show();

        } else {
            Toast.makeText(this, getString(R.string.notSaveMessage),
                    TOAST_DURATION).show();

        }
    }

    public void onUpdateButtonClick(View view) {
        int count = dbHelper.update(name.getText(), comment.getText()); // (6)

        if (count > 0) {
            Toast.makeText(this,
                    String.format(getString(R.string.updateMessage), count),
                    TOAST_DURATION).show();

        } else {
            Toast.makeText(this, getString(R.string.notUpdateMessage),
                    TOAST_DURATION).show();
        }
    }

    public void onRemoveButtonClick(View view) {
        int count = dbHelper.delete(name.getText());    // (7)

        name.setText("");
        comment.setText("");
        if (count > 0) {
            Toast.makeText(this,
                    String.format(getString(R.string.removeMessage), count),
                    TOAST_DURATION).show();

        } else {
            Toast.makeText(this, getString(R.string.notRemoveMessage),
                    TOAST_DURATION).show();
        }
    }
}
---

(1)(2) データベースにアクセスするオブジェクト

    private DatabaseHelper dbHelper = null;    // (1)

DatabaseHelper オブジェクトで、データベースを取得しようとしたときに、データベー
スがない場合、onCreate() メソッドが動く。
また、データベースのバージョンが変わったときは、onUpgrade() メソッドが動く。
データベースのバージョンは、SQLiteOpenHelper のコンストラクターの引数 
int version で指定する。この値が、現在のバージョンが変わったら、このonUpgrade()
 メソッドが動くことになる。

    dbHelper = new DatabaseHelper(this);    // (2)


(3) データベースを閉じる

ただし、これは自分では閉じない方がよいらしい。

    // dbHelper.close();    // 呼ばないほうがよい    // (3)

参考
「SQLiteDatabase.closeは明示で呼ぶな、Cursor.closeは明示で呼べ」
SQLiteを使う場合の注意点
http://d.hatena.ne.jp/ukiki999/20100524/p1


(4) name で指定されたデータを取得(照会)する

結果は、List オブジェクトで戻る。

    List<Item> itemList = dbHelper.select(name.getText());    // (4)


(5) name, comment データを挿入する

結果は、主キーの値が戻る。挿入できなかったときは、-1.

    long id = dbHelper.insert(name.getText(), comment.getText());    // (5)


(6) name で指定されたデータを更新する

結果は、更新した行数。

    int count = dbHelper.update(name.getText(), comment.getText());    // (6)


(7) name で指定されたデータを削除する

結果は、削除した行数。

    int count = dbHelper.delete(name.getText());    // (7)


▲ データベース関係

データベースを操作するためのクラス。

SQLiteOpenHelper クラスを継承して、その抽象メソッドであるつぎのメソッドをオー
バーライドする。そして、必要に応じて CRUD メソッドを記しておく。
     onCreate(SQLiteDatabase db)
     onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)

データベースの CRUD 操作は、getReadableDatabase() メソッド、
getWritableDatabase() メソッドによって、SQLiteDatabase オブジェクトが取得できる
ので、このオブジェクトの中のメソッドによる。

□ DatabaseHelper.java
---
package jp.marunomaruno.android.database;

import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.Editable;

/**
 * データベースを扱うためのヘルパー・クラス。
 * 
 * @author marunomaruno
 */
public class DatabaseHelper extends SQLiteOpenHelper {    // (1)
    private Context context;
    private static final String DATABASE_NAME = "item.db";
    private static final int DATABASE_VERSION = 1;    // (2)
    public static final String TABLE_NAME = "item";
    public static final String ID = "_id";
    public static final String NAME = "name";
    public static final String COMMENT = "comment";
    public static final String[] COLUMN_NAMES = { ID, NAME, COMMENT, };

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);    // (3)
        this.context = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {    // (4)
        final String SQL = String.format(context
                .getString(R.string.createTableFormat), TABLE_NAME, ID, NAME,
                COMMENT);    // (5)
        System.out.println("DatabaseHelper.onCreate() SQL = " + SQL);
        db.execSQL(SQL);    // (6)
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
  // (7)
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);    // (8)
        onCreate(db);
    }

    /**
     * 指定されたデータをデータベースに挿入する。
     * 
     * @param name
     *            名前
     * @param comment
     *            コメント
     * @return 主キーの値
     */
    public long insert(String name, String comment) {
        String nullColumnHack = null; 
                        // NOT NULL列に値が指定されていない場合のデフォルト値
        ContentValues values = new ContentValues(); // 挿入する列名と値 // (9)
        values.put(NAME, name);    // (10)
        values.put(COMMENT, comment);
        return getWritableDatabase().insert(TABLE_NAME, nullColumnHack, values); // (11)
    }

    /**
     * 指定されたデータをデータベースに挿入する。
     * 
     * @param name
     *            名前
     * @param comment
     *            コメント
     * @return 主キーの値
     */
    public long insert(Editable name, Editable comment) {
        return insert(name.toString(), comment.toString());
    }

    /**
     * 名前で指定されたデータを変更する。
     * 
     * @param name
     *            名前
     * @param comment
     *            コメント
     * @return 変更した行数
     */
    public int update(String name, String comment) {
        ContentValues values = new ContentValues();    // 更新する列名と値
        values.put(COMMENT, comment);
        String whereClause = NAME + " = ?";    // 選択条件    // (12)
        String[] whereArgs = new String[] { name }; // パラメーター・マーカーの値 // (13)
        return getWritableDatabase().update(TABLE_NAME, values, whereClause,
                whereArgs);    // (14)
    }

    /**
     * 名前で指定されたデータを変更する。
     * 
     * @param name
     *            名前
     * @param comment
     *            コメント
     * @return 変更した行数
     */
    public int update(Editable name, Editable comment) {
        return update(name.toString(), comment.toString());
    }

    /**
     * 名前で指定されたデータを削除する。
     * 
     * @param name
     *            名前
     * @return 削除した行数
     */
    public int delete(String name) {
        String whereClause = NAME + " = ?";    // 選択条件
        String[] whereArgs = new String[] { name };  // パラメーター・マーカーの
値
        return getWritableDatabase().delete(TABLE_NAME, whereClause, whereArgs);
 // (15)
    }

    /**
     * 名前で指定されたデータを削除する。
     * 
     * @param name
     *            名前
     * @return 削除した行数
     */
    public int delete(Editable name) {
        return delete(name.toString());
    }

    /**
     * 名前で指定されたデータを照会する。
     * 
     * @param name
     *            名前
     * @return 照会したデータのリスト
     */
    public List<Item> select(String name) {
        String selection = NAME + " = ?";    // 選択条件
        String[] selectionArgs = { name };    // パラメーター・マーカーの値
        String groupBy = null;    // GROUP BY 句
        String having = null;     // HAVING 句
        String orderBy = null;    // ORDER BY 句
        Cursor cursor = getReadableDatabase().query(TABLE_NAME, COLUMN_NAMES,
                selection, selectionArgs, groupBy, having, orderBy);    // (16)

        List<Item> itemList = new ArrayList<Item>();

        if (!cursor.moveToFirst()) {    // 最初の行をさす    // (17)
            cursor.close();
            return itemList;
        }

        do {
            itemList.add(new Item(cursor.getLong(cursor.getColumnIndex(ID)),
                    cursor.getString(cursor.getColumnIndex(NAME)), cursor
                            .getString(cursor.getColumnIndex(COMMENT)))); // (18
)
        } while (cursor.moveToNext());    // (19)

        cursor.close();    // (20)
        return itemList;
    }

    /**
     * 名前で指定されたデータを照会する。
     * 
     * @param name
     *            名前
     * @return 照会したデータのリスト
     */
    public List<Item> select(Editable name) {
        return select(name.toString());
    }
}
---

(1) データベースにアクセスするクラス

SQLiteOpenHelper クラスを継承する。

    public class DatabaseHelper extends SQLiteOpenHelper {    // (1)


・SQLiteOpenHelper クラス

データベースと接続するためのクラス。
SQLite では、JDBC と違い、Connection オブジェクトを作るわけではなく、データベー
ス自体の管理は、この SQLiteOpenHelper オブジェクトを通して、生成・バージョンアッ
プ・接続などを行う。

java.lang.Object
   -   android.database.sqlite.SQLiteOpenHelper

・コンストラクター
---
SQLiteOpenHelper(Context context, String name, 
                    SQLiteDatabase.CursorFactory factory, int version)
SQLiteOpenHelper(Context context, String name, 
                    SQLiteDatabase.CursorFactory factory, int version, 
                    DatabaseErrorHandler errorHandler)
---

name はデータベース名。データベースは、「/data/data/パッケージ名/database/」に構
築される。
factory は、独自カーソルを定義した場合に使う。標準でよければ null。
version は、データベースのバージョン。既存のデータベースのバージョンと違う場合、
旧バージョンとの値の差によって、
    onDowngrade()
    onUpgrade()
メソッドが動く。なお、onUpgrade() メソッドは抽象メソッドなので、オーバーライドが
必要であるが、onDowngrade() メソッドは具象メソッドなので、必要なときにオーバーラ
イドすればよい。


・メソッド
---
synchronized void close()
String            getDatabaseName()
synchronized SQLiteDatabase     getReadableDatabase()
synchronized SQLiteDatabase     getWritableDatabase()
abstract void     onCreate(SQLiteDatabase db)
void              onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)
void              onOpen(SQLiteDatabase db)
abstract void     onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
---

データベースへの接続は、getReadableDatabase() メソッド、getWritableDatabase() メ
ソッドで行う。


(2)(3) このデータベースのバージョン

今回は、バージョン 1 にしておく。データベースの構成が変わったときは、2 以上にし
て、データベースを作り直す。

    private static final int DATABASE_VERSION = 1;    // (2)

バージョンの指定は、スーパークラス SQLiteOpenHelper のコンストラクターの引数で指
定する。

    super(context, DATABASE_NAME, null, DATABASE_VERSION);    // (3)


(4) データベースがなかったときの処理

データベースに接続するときに、データベースがなかったときにこのメソッドが呼ばれる。
これは、SQLiteOpenHelper クラスの抽象メソッド。

    public void onCreate(SQLiteDatabase db) {    // (4)


(5)(6) データベースとテーブルを生成する

CREATE TABLE 文を記している。文の形式はリソースとして定義している。

    final String SQL = String.format(context
            .getString(R.string.createTableFormat), TABLE_NAME, ID, NAME,
            COMMENT);    // (5)

実際の文は以下のとおり。
    CREATE TABLE item (
        _id INTEGER PRIMARY KEY AUTOINCREMENT
        , name TEXT
        , comment TEXT
    )

SQL 文を実行する。

    db.execSQL(SQL);    // (6)


(7) データベースのバージョンがあがったときの処理

これは、SQLiteOpenHelper クラスの抽象メソッド。

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // (7)

(8) テーブルがあれば削除して作り直す

    db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);    // (8)
    onCreate(db);


(9)(10) 挿入する列名と値を設定する

テーブルに挿入や更新する場合、列名と値のペアで管理する。名前と値のペアというと、
Map オブジェクトがあるが、SQLite では、ContentValues オブジェクトを使う。使い方
は Map と同じで、put() メソッドを使って値を設定。get() メソッドで、値を取得する。
なお、値の型によって、getAsXxx() メソッドもある。

    ContentValues values = new ContentValues();    // 挿入する列名と値    // (9)
    values.put(NAME, name);    // (10)


□ ContentValues クラス

SQLite の DML を使いやすくするのに適した形のマップ(Map)。

・android.content.ContentValues

・取得・設定関係のメソッド
---
Object   get(String key)
Boolean  getAsBoolean(String key)
Byte     getAsByte(String key)
byte[]   getAsByteArray(String key)
Double   getAsDouble(String key)
Float    getAsFloat(String key)
Integer  getAsInteger(String key)
Long     getAsLong(String key)
Short    getAsShort(String key)
String   getAsString(String key)

void     put(String key, Byte value)
void     put(String key, Integer value)
void     put(String key, Float value)
void     put(String key, Short value)
void     put(String key, byte[] value)
void     put(String key, String value)
void     put(String key, Double value)
void     put(String key, Long value)
void     put(String key, Boolean value)
void     putAll(ContentValues other)
void     putNull(String key)
---


・その他のメソッド
---
void                        clear()
boolean                     containsKey(String key)
boolean                     equals(Object object)
int                         hashCode()
Set<String>                 keySet()
void                        remove(String key)
int                         size()
String                      toString()
Set<Entry<String, Object>>  valueSet()
---


(11) name, comment データを挿入する

データを挿入するので、getWritableDatabase() メソッドを使って、書き込みができる
モードで、データベースを開く。このメソッドの結果は、SQLiteDatabase オブジェクト。
SQLiteDatabase クラスには、CRUD 系のメソッドが用意されているので、それらを使って
テーブルを操作する。

    return getWritableDatabase().insert(TABLE_NAME, nullColumnHack, values); // (11)

---
long  insert(String table, String nullColumnHack, ContentValues values)
---
戻り値は、挿入したときの rowID。INTEGER 型の主キー制約列が定義されていればその値
(のはず)。挿入エラーのときは -1.
String table            テーブル名
String nullColumnHack   NOT NULL 列に値が指定されていない場合のデフォルト値。
ContentValues values    列名と値がマッピングされたオブジェクト


(12)(13)(14) name で指定されたデータを更新する

選択条件には、JDBC と同じように、パラメーター・マーカー「?」が使える。
パラメーター・マーカー「?」に対する値は、String 配列値として別に用意する。

    String whereClause = NAME + " = ?";    // 選択条件    // (12)
    String[] whereArgs = new String[] { name }; // パラメーター・マーカーの値 // (13)

テーブルの更新なので、getWritableDatabase() メソッドを使う。

    return getWritableDatabase().update(TABLE_NAME, values, whereClause,
            whereArgs);    // (14)

---
int   update(String table, ContentValues values, String whereClause, 
                                                    String[] whereArgs)
---
戻り値は、更新した行数


(15) name で指定されたデータを削除する

update() メソッドと同じように、選択条件にはパラメーター・マーカー「?」が使える。

    return getWritableDatabase().delete(TABLE_NAME, whereClause, whereArgs); // (15)

---
int   delete(String table, String whereClause, String[] whereArgs)
---
戻り値は、削除した行数


(16) name で指定されたデータを取得(照会)する

ここでは、照会だけなので、読み取り専用で、getReadableDatabase() メソッドを使えば
よい。戻り値は Cursor オブジェクト。

    Cursor cursor = getReadableDatabase().query(TABLE_NAME, COLUMN_NAMES,
            selection, selectionArgs, groupBy, having, orderBy);    // (16)

---
Cursor  query(String table, String[] columns, String selection, 
            String[] selectionArgs, String groupBy, String having, 
            String orderBy, String limit)

Cursor  query(String table, String[] columns, String selection, 
            String[] selectionArgs, String groupBy, String having, 
            String orderBy)

Cursor  query(boolean distinct, String table, String[] columns, String selection,
            String[] selectionArgs, String groupBy, String having, 
            String orderBy, String limit)

Cursor  rawQuery(String sql, String[] selectionArgs)

引数
    boolean distinct        DISTINCT(true)かALL(false)か
    String table            テーブル名
    String[] columns        射影される列名(nullのとき、すべての列)
    String selection        WHERE句(パラメーターマーカー(?)の指定も可能)
    String[] selectionArgs  パラメーターマーカー(?)を置き換える文字列
    String groupBy          GROUP BY句
    String having           HAVING句
    String orderBy          ORDER BY句
    String limit            LIMIT句
    String sql              SQL文(パラメーターマーカー含む)
---


(17) 取得したテーブルの最初の行をさす

カーソルを使って取得したテーブルを処理する。
まずは、最初の行にカーソルを移動する。

    if (!cursor.moveToFirst()) {    // 最初の行をさす    // (17)

JDBC と違って、単純に next() メソッドを使うだけでできるわけではないので注意が必
要。moveToFirst() メソッドで、先頭行に移動し、moveToNext() メソッドで、つぎの行
に移動する、というパターン。全体としては、つぎのような感じでロジックを組む。

---
    if (!cursor.moveToFirst()) {    // 最初の行をさす
        cursor.close();
        return ...;
    }

    do {
        // 行の処理
    } while (cursor.moveToNext());    // つぎの行に移動する

    cursor.close();
---

・行の移動関係のメソッド
---
boolean move(int offset) 
boolean moveToFirst() 
boolean moveToLast() 
boolean moveToNext() 
boolean moveToPosition(int position) 
boolean moveToPrevious()  
---


(18) 行のデータから Item オブジェクトを生成する

    itemList.add(new Item(cursor.getLong(cursor.getColumnIndex(ID)),
            cursor.getString(cursor.getColumnIndex(NAME)), cursor
                    .getString(cursor.getColumnIndex(COMMENT))));    // (18)

・ データ取得関係のメソッド
---
byte[]  getBlob(int columnIndex) 
double  getDouble(int columnIndex)
float   getFloat(int columnIndex) 
int     getInt(int columnIndex) 
long    getLong(int columnIndex) 
short   getShort(int columnIndex) 
String  getString(int columnIndex) 
---

引数としては列番号だけなので、列名から取得したいときは、
    getColumnIndex(String columnName)
と組み合わせる必要がある。


(19) つぎの行に移動する

    } while (cursor.moveToNext());    // (19)


(20) カーソルを閉じる

使い終わったカーソルは閉じる。

    cursor.close();    // (20)

閉じていない場合は、つぎのエラーが出る。
    E/Cursor(14725): Finalizing a Cursor that has not been deactivated or closed.


▲エンティティ

単純に、id、name、comment を持っているだけのクラス。
JavaBeans の要件は満たすようにしている。

□ Item.java
---
package jp.marunomaruno.android.database;

import java.io.Serializable;

/**
 * データベースの項目を管理するエンティティ・クラス。
 * 
 * @author marunomaruno
 */
public class Item implements Serializable {
    private static final long serialVersionUID = 1L;

    public static final int NONE_ID = -1;
    private long id;
    private String name;
    private String comment;

    public Item(long id, String name, String comment) {
        this.id = id;
        this.name = name;
        this.comment = comment;
    }

    public Item(String name, String comment) {
        this(NONE_ID, name, comment);
    }

    public Item() {
        assert true;    // 何もしない
    }
    
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    @Override
    public String toString() {
        return String.format("[%d, %s, %s]", id, name, comment);
    }
}
---


▲レイアウト


□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:weightSum="1" >

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/nameHint" >

        <requestFocus >
        </requestFocus>
    </EditText>

    <EditText
        android:id="@+id/comment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="@string/commentHint"
        android:inputType="textMultiLine" >
    </EditText>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/findButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onFindButtonClick"
            android:text="@string/findButton" >
        </Button>

        <Button
            android:id="@+id/saveButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onSaveButtonClick"
            android:text="@string/saveButton" >
        </Button>

        <Button
            android:id="@+id/updateButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onUpdateButtonClick"
            android:text="@string/updateButton" >
        </Button>

        <Button
            android:id="@+id/removeButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onRemoveButtonClick"
            android:text="@string/removeButton" >
        </Button>
    </LinearLayout>

</LinearLayout>
---


▲ 文字列

---
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Database01</string>
    <string name="nameHint">名前</string>
    <string name="commentHint">コメント</string>
    <string name="saveButton">保存</string>
    <string name="findButton">検索</string>
    <string name="removeButton">削除</string>
    <string name="updateButton">変更</string>
    <string name="saveMessage">ID %d で保存しました</string>
    <string name="findMessage">データがありました。</string>
    <string name="findMultipleMessage">
                %d 件のデータがありました。最初の1件を表示します</string>
    <string name="removeMessage">%d 件のデータを削除しました</string>
    <string name="updateMessage">%d 件のデータを変更しました</string>
    <string name="notSaveMessage">データを保存できませんでした</string>
    <string name="notFoundMessage">データはありませんでした</string>
    <string name="notRemoveMessage">データを削除できませんでした</string>
    <string name="notUpdateMessage">データを変更できませんでした</string>
    <string name="createTableFormat">
        CREATE TABLE %1$s (
            %2$s INTEGER PRIMARY KEY AUTOINCREMENT
            , %3$s TEXT
            , %4$s TEXT
        )
    </string>    <!-- (1) -->

</resources>
---

(1) テーブルを生成する SQL 文のフォーマット

    <string name="createTableFormat">
        CREATE TABLE %1$s (
            %2$s INTEGER PRIMARY KEY AUTOINCREMENT
            , %3$s TEXT
            , %4$s TEXT
        )
    </string>    <!-- (1) -->

※ この部分は、国際化とは意味が違うので、別の XML ファイルにした方がよいかもしれ
ない。

                                                                            以上


コメント (0) |  トラックバック (0) | 

[Android] JavaScript との連携 - WebView

2012年02月12日 | Android
[Android] JavaScript との連携 - WebView
================================================================================

WebView を使うことで、レイアウトの中で、HTML を表示することができる。
自分のアプリケーションから独立しているブラウザーでは、アプリケーションから HTML 
を制御することはできないが、アプリケーション内の WebView を使えば、アプリケーシ
ョンから HTML の制御は可能になる。たとえば、JavaScript と Java のプログラムとを
連携させることができる。

JavaScript との連携方法としては次の 2 つがある。
(1) JavaScript から Java のメソッド呼び出し
(2) Java から JavaScript の関数呼び出し


どちらも、以下の処理を行う必要がある。
・WebView オブジェクトを取得し、HTML の中で JavaScript を使えるようにする

上記を行ったのちの、それぞれの概要は以下のとおり。

(1) JavaScript から Java のメソッド呼び出し

・JavaScript から使える Java のメソッドのオブジェクトを登録
    WebView.addJavascriptInterface(オブジェクト, インターフェース名);


(2) Java から JavaScript の関数呼び出し

・つぎのメソッドを使う
    WebView.loadUrl("javascript:関数名(引数リスト)");


JavaScript を扱う上での注意点として、以下をあげる。

・alert や prompt を使うときには設定が必要
    WebView.setWebChromeClient(WebChromeClient オブジェクト);

・JavaScript インターフェースのメソッドで、アクティビティのビューを操作する場合
はハンドラー Handler オブジェクトが必要
    Handler.post(new Runnable() {
        public void run() {
            // ビューの操作
        }
    });

WebView を使う上での注意点

・マニフェストに次の権限を設定する
    <uses-permission android:name="android.permission.INTERNET"/>


▲ アクティビティ

上記の概要に基づいたクラス。

□ WebViewJavaScript01Activity.java
---
package jp.marunomaruno.android.webviewjavascript;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;

public class WebViewJavaScript01Activity extends Activity {
    private WebView webView1; // (1)

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

        Handler handler = new Handler(); // (2)

        webView1 = (WebView) findViewById(R.id.webView1);
        webView1.loadUrl("file:///android_asset/index.html");    // (3)

        WebSettings settings = webView1.getSettings();    // (4)
        settings.setJavaScriptEnabled(true);    // (5)

        webView1.addJavascriptInterface(new JavaScriptInterfaceFunctions(this,
                handler), "AndroidFunctions");    // (6)
        
        webView1.setWebChromeClient(new WebChromeClient());    // (7)
    }

    public void onClickHandler(View view) {
        webView1.loadUrl("javascript:showText()");    // (8)
    }
}
---

(1) WebView オブジェクト

    private WebView webView1; // (1)


△ WebView クラス

HTML をアプリケーション内から表示するためのクラス。

java.lang.Object
   +  android.view.View
        +  android.view.ViewGroup
             +  android.widget.AbsoluteLayout
                  +  android.webkit.WebView

・主なメソッド
---
void     addJavascriptInterface(Object obj, String interfaceName)
WebSettings   getSettings()
void     loadData(String data, String mimeType, String encoding)
void     loadDataWithBaseURL(String baseUrl, String data, String mimeType, 
                                String encoding, String historyUrl)
void     loadUrl(String url)
void     loadUrl(String url, Map<String, String> extraHeaders)
void     setDownloadListener(DownloadListener listener)
void     setWebChromeClient(WebChromeClient client)
void     setWebViewClient(WebViewClient client)
void     stopLoading()
---

・履歴関係のメソッド
---
boolean  canGoBack()
boolean  canGoBackOrForward(int steps)
boolean  canGoForward()
void     goBack()
void     goBackOrForward(int steps)
void     goForward()
---

・画面のズーム関係のメソッド
---
boolean  canZoomIn()
boolean  canZoomOut()
boolean  zoomIn()
boolean  zoomOut()
---


(2) ハンドラー・オブジェクトの生成

JavaScript から呼ばれたメソッドで、アクティビティのビューを操作できるようにする
ために、Handler クラスのオブジェクトを生成しておく。

    Handler handler = new Handler(); // (2)


△ Handler クラス

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

java.lang.Object
   + android.os.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)
---


(3) HTML を読み込む

    webView1.loadUrl("file:///android_asset/index.html");    // (3)

プロジェクトの assets フォルダは、「/android_asset」でアプリケーション内から参照
できる。


(5) HTML で JavaScript が使えるようにする

    WebSettings settings = webView1.getSettings();    // (4)
    settings.setJavaScriptEnabled(true);    // (5)


△ WebSettings クラス

WebView オブジェクトに関する設定関係のオブジェクト。

java.lang.Object
   +    android.webkit.WebSettings


・主なメソッド
---
synchronized void setJavaScriptEnabled(boolean flag)  JavaScript 有効無効の設定
void     setSaveFormData(boolean save)       フォームデータ保存の有効無効の設定
void     setSavePassword(boolean save)       パスワード保存の有効無効の設定
void     setSupportZoom(boolean support)     ズームの有効無効の設定
---


(6) JavaScript から Java のメソッドを使えるようにする

    webView1.addJavascriptInterface(new JavaScriptInterfaceFunctions(this,
            handler), "AndroidFunctions");    // (6)

形式
---
void  addJavascriptInterface(Object JavaScriptインターフェース・オブジェクト, 
                                            String インターフェース名)
---

JavaScript 側からは、インターフェース名.メソッド名() で呼び出せる。


(7) JavaScript で、alert や prompt が使えるようにする

    webView1.setWebChromeClient(new WebChromeClient());    // (7)


△ WebChromeClient クラス

JavaScript で使う prompt や alert は、Android のダイアログになる。これらをサポー
トするクラス。デフォルトで用意されているが、必要に応じて、このクラスのメソッドを
オーバーライドしてカスタマイズする。

java.lang.Object
   +    android.webkit.WebChromeClient

・JavaScript 関係の主なメソッド
---
boolean  onJsAlert(WebView view, String url, String message, JsResult result)
boolean  onJsConfirm(WebView view, String url, String message, JsResult result)
boolean  onJsPrompt(WebView view, String url, String message, 
                                    String defaultValue, JsPromptResult result)
boolean  onJsTimeout()
---


(8) JavaScript の関数を呼び出す

    webView1.loadUrl("javascript:showText()");    // (8)

・形式
---
webView1.loadUrl("javascript:関数名(引数リスト)");
---


▲ JavaScript から使う関数を定義したクラス

単なる Java のクラスとして作ればよい。
ただし、アクティビティのビューを操作するときは、ハンドラー・オブジェクトが必要。

□ JavaScriptInterfaceFunctions.java
---
package jp.marunomaruno.android.webviewjavascript;

import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.widget.TextView;
import android.widget.Toast;

public class JavaScriptInterfaceFunctions {

    private Context context;
    private Handler handler;    // (1)

    public JavaScriptInterfaceFunctions(Context context, Handler handler) {
        this.context = context;
        this.handler = handler;
    }

    public void showToast(String message) {
        Toast.makeText(context, getMessage(message), Toast.LENGTH_LONG).show();
    }

    public void setTextView(final String message) {
            handler.post(new Runnable() {    // (2)
                public void run() {    // (3)
                    TextView textView1 = (TextView) ((Activity) context)
                    .findViewById(R.id.textView1);
                    textView1.setText(getMessage(message));
                }
            });
    }

    public String getMessage(String message) {
        return String.format("<span class='html'>☆%s☆</span> %s", message, 
                                                            getMessage());
    }

    public String getMessage() {
        return String.format("<span class='java'>★%s★</span>", 
                                                        "これはJavaの文字列");
    }
}
---

(1) JavaScript から呼ばれたメソッドで、アクティビティのビューを操作できるように
する

コンストラクターの引数をそのまま設定する。

    private Handler handler;    // (1)


(2)(3) テキスト・ビューに値を設定する

ハンドラーの post() メソッドを使って実行する。

    handler.post(new Runnable() {    // (2)
        public void run() {    // (3)
            TextView textView1 = (TextView) ((Activity) context)
                .findViewById(R.id.textView1);
            textView1.setText(getMessage(message));
        }
    });


▲ HTML ファイル

通常の HTML ファイル。
JavaScript から、Android の Java メソッドを呼ぶには、インターフェース名が必要。
    インターフェース名.メソッド名(引数リスト)

□ assets/index.html
---
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
                                "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style TYPE="text/css">
#area { background-color: lightgreen; }
.html { color: red; }
.java { color: green; }
</style>
<script type="text/javascript">
// Android のトースト表示
function showToast(message){
    AndroidFunctions.showToast(message);    // (1)
}

// Android のテキストビューに設定
function setTextView(message){
    AndroidFunctions.setTextView(message);
}

// メッセージを組み立て
function makeMessage(message){
    var message = AndroidFunctions.getMessage(message);    // (2)
    document.getElementById("area").innerHTML = message; 
}

// メッセージ取得
function getMessage(){
    var message = AndroidFunctions.getMessage();    // (3)
    document.getElementById("area").innerHTML = message; 
}

// メッセージをプロンプトから取得する
function messageFromPrompt(){
    var message = prompt("文字列を入力してください。", "");    // (4)
    alert(message);     // (5)
}

// Android から呼ばれる
function showText(){
    document.getElementById("area").innerHTML = "Javaから呼ばれたJavaScriptの関
数"; 
}
   
</script>
<title>Insert title here</title>
</head>
<body>
<p>
<input type="button" value="Androidのトーストを表示"
    onclick="showToast('HTMLの文字列')"/>
</p>

<p>
<input type="button" value="Androidのテキストビューに表示"
    onclick="setTextView('HTMLの文字列')"/>
</p>

<p>
<input type="button" value="Androidからメッセージを組み合わせる"
    onclick="makeMessage('HTMLの文字列')"/>
</p>

<p>
<input type="button" value="プロンプトから文字列を読んでアラート表示"
    onclick="messageFromPrompt()"/>
</p>

<p>
<input type="button" value="Androidからメッセージを得る"
    onclick="getMessage()"/>
</p>

<p>
<div id="area">ここに文字列が入る</div>
</p>

</body>

</html>
---

(1) Java 側のメソッドの呼び出し

    AndroidFunctions.showToast(message);    // (1)

Java 側では以下のような設定をしている。
    webView1.addJavascriptInterface(new JavaScriptInterfaceFunctions(this,
            handler), "AndroidFunctions");


(2)(3) Java 側のメソッドはオーバーロード可能

    var message = AndroidFunctions.getMessage(message);    // (2)
    var message = AndroidFunctions.getMessage();    // (3)


(4)(5) prompt や alert を使う

    var message = prompt("文字列を入力してください。", "");    // (4)
    alert(message);     // (5)

Java 側では以下のような設定をしている。
    webView1.setWebChromeClient(new WebChromeClient());


▲レイアウト

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <WebView
        android:id="@+id/webView1"
        android:layout_width="match_parent"
        android:layout_height="400dp" />    <!-- (1) -->

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClickHandler"
        android:text="JavaScriptの呼び出し" />

</LinearLayout>
---

(1) WebView

    <WebView
        android:id="@+id/webView1"
        android:layout_width="match_parent"
        android:layout_height="400dp" />    <!-- (1) -->


▲ マニフェスト

---
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.marunomaruno.android.webviewjavascript"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.INTERNET"/>  <!-- (1) -->

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".WebViewJavaScript01Activity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
---

(1) WebView を使うときは以下の設定が必要

    <uses-permission android:name="android.permission.INTERNET"/>  <!-- (1) -->

                                                                        以上


コメント (0) |  トラックバック (0) | 

[Android] 15 パズル (3) - 経過時間(Chronometer)

2012年02月06日 | Android
                                                                    2012-02-06
[Android] 15 パズル (3) - 経過時間(Chronometer)
================================================================================

15 パズルに、パズルを解き始めてからの経過時間を表示する。
経過時間は、Chronometer クラスを使う。
経過時間測定の開始・終了は、Chronometer クラスの start()、stop() メソッドを使う。
また、表示は res/layout/main.xml で、Chronometer 要素を使う。

ベースになるプロジェクトは前回の
前回の「15 パズル (2) - カスタム・ボタン」
http://blog.goo.ne.jp/marunomarunogoo/d/20120204
である。

今回変更したクラスはつぎの 2 つ。
FifteenPuzzleActivity    アクティビティ。Chronometer の開始・終了を追加
FifteenPuzzleBoard       ボード。パズルの終了判定追加


■ アクティビティ

Chronometer の開始・終了の処理を追加する。
終了時は、アラート・ダイアログでその経過時間を表示する。

□ FifteenPuzzleActivity.java
---
package jp.marunomaruno.android.fifteenpuzzle;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.widget.Chronometer;

public class FifteenPuzzleActivity extends Activity {

    private FifteenPuzzleBoard board;    // 15パズルのボード
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        board = new FifteenPuzzleBoard(this);
    }

    /**
     * 数字ボタン・クリック時のハンドラー
     * @param v
     */
    public void onClickNumberButton(View v) {
        boolean isComplete = board.onClickNumberBlock((NumberButton) v); // (1)
        
        if (isComplete) {
            // 経過時間測定の終了
            Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer); // (2)
            chronometer.stop();    // (3)

            System.out
                    .println(String.format(getString(R.string.finishMessage),
                            (SystemClock.elapsedRealtime() - chronometer
                                    .getBase()) / 1000));
            
            // 完成のダイアログを表示
            new AlertDialog.Builder(this)
                .setMessage(
                    String.format(getString(R.string.finishMessage),
                            (SystemClock.elapsedRealtime() - chronometer
                                    .getBase()) / 1000))    // (4)
                .setPositiveButton("OK", null)
                .show();
        }
    }

    /**
     * 開始ボタン・クリック時のハンドラー
     * @param v
     */
    public void onClickStartButton(View v) {
        board.reset();
        
        // 経過時間測定の開始
        Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
        chronometer.setBase(SystemClock.elapsedRealtime());    // (5)
        chronometer.start();    // (6)
    }
}
---

(1) 数字ブロックをクリックしたときにパズルの完成も判断

    boolean isComplete = board.onClickNumberBlock((NumberButton) v); // (1)


(2) クロノメーターを取得する

    Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer); // (2)


・Chronometer クラス

シンプルなタイマーを実装する View クラスのサブクラス。

java.lang.Object
   +   android.view.View
         +   android.widget.TextView
               +   android.widget.Chronometer

基本的な使い方は、setBase() で、現在時刻を設定し、ここからクロノメーターを 
start() で開始する。
やめるときは stop() でやめて、やめた時点の現在時刻から getBase() で開始時点の時
刻を引けば、実行していた時間がわかる。

なお、現在時刻は、SystemClock クラスを使う。


・メソッド
---
long   getBase()            基の時間を返す
void   setBase(long base)   カウントアップする基になる時間を設定する

String getFormat()          書式文字列を返す
void   setFormat(String format) 
       書式文字列を設定する。%s は、"MM:SS" または "H:MM:SS" 形式になる

Chronometer.OnChronometerTickListener     getOnChronometerTickListener() 
       クロノメーターの変更のリスナーを取得する
void   setOnChronometerTickListener(Chronometer.OnChronometerTickListener listener)
       クロノメーターの変更のリスナーを設定する
void   start()              開始する
void   stop()               停止する
---


・SystemClock クラス

間隔または経過時間の測定に使うシステム時計のクラス。

java.lang.Object
   +   android.os.SystemClock

メソッド
---
static long     currentThreadTimeMillis()    現在のスレッドの実行時間(ミリ秒)

static long     elapsedRealtime()    
                CPU停止時間なども含めたシステム・ブートからの時間(ミリ秒)

static boolean  setCurrentTimeMillis(long millis)    現在の時間(ミリ秒)

static void     sleep(long ms)    指定されたミリ秒スリープ(例外をスローしない)

static long     uptimeMillis()    
                CPU停止時間などを含めないシステム・ブートからの時間(ミリ秒)
---


(3) クロノメーターを停止する

    chronometer.stop();    // (3)


(4) 経過時間をダイアログで表示する

chronometer.setBase() を行ったのが、SystemClock.elapsedRealtime() なので、経過時
間は停止時点の SystemClock.elapsedRealtime() から 開始時点の 
SystemClock.elapsedRealtime() を引く。開始時点の SystemClock.elapsedRealtime() 
は、setBase() で、クロノメーター・オブジェクトに設定済み。

    new AlertDialog.Builder(this)
        .setMessage(
            String.format(getString(R.string.finishMessage),
                    (SystemClock.elapsedRealtime() - chronometer
                            .getBase()) / 1000))    // (4)
        .setPositiveButton("OK", null)
        .show();

(5)(6) クロノメーターを開始する

経過時間を後で取得するために、setBase() で、現在の時刻を設定しておく。

    chronometer.setBase(SystemClock.elapsedRealtime());    // (5)
    chronometer.start();    // (6)


■ ボード

数字ブロックが移動するたびに、パズルの完成を判定する。
判定のメソッドとして、isComplete() を設ける。

変更・追加は、つぎのメソッドのみ。
    public boolean onClickNumberBlock(NumberButton button)  完成すれば true を返す
    private boolean isComplete() 完成したかどうか(完成すれば true を返す)


□ FifteenPuzzleBoard.java
---
package jp.marunomaruno.android.fifteenpuzzle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import jp.marunomaruno.android.util.ArrayCollectionUtil;
import jp.marunomaruno.android.util.SymmetricGroupUtil;
import android.app.Activity;
import android.content.Context;

public class FifteenPuzzleBoard {

    public Context context;
    
    public static final int ORDER = 4; // 4次の正方行列

    private NumberButton[] numberButtons;
    private int emptyBlockIndex = ORDER * ORDER;
    
    public FifteenPuzzleBoard(Context context) {
        this.context = context;

        Activity activity = (Activity) context;
        
        numberButtons = new NumberButton[] {
                null, // ボード上の番号と合わせるため、インデックス0をダミーとする
                (NumberButton) activity.findViewById(R.id.numberButton1),
                (NumberButton) activity.findViewById(R.id.numberButton2),
                (NumberButton) activity.findViewById(R.id.numberButton3),
                (NumberButton) activity.findViewById(R.id.numberButton4),
                (NumberButton) activity.findViewById(R.id.numberButton5),
                (NumberButton) activity.findViewById(R.id.numberButton6),
                (NumberButton) activity.findViewById(R.id.numberButton7),
                (NumberButton) activity.findViewById(R.id.numberButton8),
                (NumberButton) activity.findViewById(R.id.numberButton9),
                (NumberButton) activity.findViewById(R.id.numberButton10),
                (NumberButton) activity.findViewById(R.id.numberButton11),
                (NumberButton) activity.findViewById(R.id.numberButton12),
                (NumberButton) activity.findViewById(R.id.numberButton13),
                (NumberButton) activity.findViewById(R.id.numberButton14),
                (NumberButton) activity.findViewById(R.id.numberButton15),
                (NumberButton) activity.findViewById(R.id.numberButton16), 
            };

        // 最後のブロックを空とする
        numberButtons[emptyBlockIndex].setText(R.string.emptyText);

        log("initialize() ");
    }

    /**
     * 空ブロックが指定されたインデックスに入っているかどうか判定する。
     * @param indexes
     * @return 空ブロックが指定されたインデックスに入っていればtrue
     */
    private boolean isEmptyBlockIndexIn(int... indexes) {
        assert indexes.length > 0;

        return ArrayCollectionUtil.isKeyIn(emptyBlockIndex, indexes);
    }

    /**
     * 数字ボタン・クリック時のハンドラー
     * @param v
     * @return パズル終了時は true
     */
    public boolean onClickNumberBlock(NumberButton button) {
        // このブロックに対して、水平方向に有効な空ブロックの位置
        final int[][] HORIZONTAL_INDEXES = new int[][]{
                null,
                
                {2, 3, 4},
                {1, 3, 4},
                {1, 2, 4},
                {1, 2, 3},
                
                {6, 7, 8},
                {5, 7, 8},
                {5, 6, 8},
                {5, 6, 7},
                
                {10, 11, 12},
                {9, 11, 12},
                {9, 10, 12},
                {9, 10, 11},
                
                {14, 15, 16},
                {13, 15, 16},
                {13, 14, 16},
                {13, 14, 15},
        };

        // このブロックに対して、垂直方向に有効な空ブロックの位置
        final int[][] VERTICAL_INDEXES = new int[][]{
                null, 
                
                {5, 9, 13},
                {6, 10, 14},
                {7, 11, 15},
                {8, 12, 16},

                {1, 9, 13},
                {2, 10, 14},
                {3, 11, 15},
                {4, 12, 16},

                {1, 5, 13},
                {2, 6, 14},
                {3, 7, 15},
                {4, 8, 16},

                {1, 5, 9},
                {2, 6, 10},
                {3, 7, 11},
                {4, 8, 12},
        };

        System.out.printf("onClick() %s", button.toString());
        
        // 空の場合、何もしない
        if (isEmptyBlock(button)) {
            System.out.println(": empty");
            return false;    // (1)
        }
        
        int blockIndex = button.getBlockIndex();
        
        if (isEmptyBlockIndexIn(HORIZONTAL_INDEXES[blockIndex])) {    
            rotateHorizontal(blockIndex);

            System.out.println();    // 最後にlogを出すための改行
            return isComplete();    // (2)
        }

        if (isEmptyBlockIndexIn(VERTICAL_INDEXES[blockIndex])) {
            rotateVertical(blockIndex);
            
            System.out.println();    // 最後にlogを出すための改行
            return isComplete();
        }
        
        System.out.println();    // 最後にlogを出すための改行
        
        return false;
    }
    
    /**
     * パズルが完成かどうか
     * @return 完成のとき true
     */
    private boolean isComplete() {    // (3)
        // インデックス 1~15 の中に空ブロックがあればまだ完成していない
        for (int i = 1; i < numberButtons.length - 1; i++) {
            if (numberButtons[i].getText().toString().equals(
                    context.getString(R.string.emptyText))) {
                return false;
            }
        }

        // インデックス 1~15 の中で、数字の順番が逆なのがあればまだ完成していない
        for (int i = 1; i < numberButtons.length - 2; i++) {
            if (Integer.parseInt(numberButtons[i].getText().toString()) >= Integer
                    .parseInt(numberButtons[i + 1].getText().toString())) {
                return false;
            }
        }

        return true;
    }

    /**
     * 垂直にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateVertical(int index) {
        if (index < emptyBlockIndex) {
            rotateDown(index);
        } else {
            rotateUp(index);
        }            
    }

    /**
     * 水平にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateHorizontal(int index) {
        if (index < emptyBlockIndex) {
            rotateRight(index);
        } else {
            rotateLeft(index);
        }            
    }

    /**
     * 左にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateLeft(int index) {
        System.out.printf("rotateLeft() %d %d%n", index, emptyBlockIndex);
        rotateAscending(index, 1);
    }

    /**
     * 右にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateRight(int index) {
        System.out.printf("rotateRight() %d %d%n", index, emptyBlockIndex);
        rotateDescending(index, 1);
    }

    /**
     * 下にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateDown(int index) {
        System.out.printf("rotateDown() %d %d%n", index, emptyBlockIndex);
        rotateDescending(index, ORDER);
    }

    /**
     * 上にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateUp(int index) {
        System.out.printf("rotateUp() %d %d%n", index, emptyBlockIndex);
        rotateAscending(index, ORDER);
    }

    /**
     * 降順にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateDescending(int index, int step) {
        for (int i = emptyBlockIndex; i > index; i -= step) {
            numberButtons[i].setText(numberButtons[i - step].getText());
        }

        numberButtons[index].setText(R.string.emptyText);
        emptyBlockIndex = index;
    }


    /**
     * 昇順にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateAscending(int index, int step) {
        for (int i = emptyBlockIndex; i < index; i += step) {
            numberButtons[i].setText(numberButtons[i + step].getText());
        }

        numberButtons[index].setText(R.string.emptyText);
        emptyBlockIndex = index;
    }

    /**
     * このブロックが空かどうかを判断する。
     * @return 空の場合true
     */
    public boolean isEmptyBlock(NumberButton button) {
        return button.getBlockIndex() == emptyBlockIndex;
    }
    
    /**
     * ボードをリセットする
     */
    public void reset() {
        // 1~15 までの乱数を生成する
        List<Integer> numberList = new ArrayList<Integer>();
        numberList.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
                14, 15));
        
        int[] numberArray;
        do {
            Collections.shuffle(numberList);
            numberArray = ArrayCollectionUtil.toArray(numberList);
        } while (SymmetricGroupUtil.sgn(numberArray) != 1);    
                                        // 解けない問題(遇置換でない)なら作り直し

        if (Boolean.parseBoolean(context.getResources().getString(R.string.debug))) {
            System.out.println("DEBUG MODE");
            numberArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
                    14, 15};
        }
        
        // 数字ブロックを設定する
        for (int i = 1; i < numberArray.length + 1; i++) {
            numberButtons[i].setText(String.valueOf(numberArray[i - 1]));
        }

        // 空ブロックを設定する
        numberButtons[ORDER * ORDER].setText(R.string.emptyText);
        emptyBlockIndex = ORDER * ORDER;

        log("onClickStartButton() ");
    }
    
    private void log(String message) {
        System.out.println(message);
        System.out.print(toString());
    }

    @Override
    public String toString() {
        return String.format(
                "[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n",
                numberButtons[1].getText(), numberButtons[2].getText(),
                numberButtons[3].getText(), numberButtons[4].getText(),
                numberButtons[5].getText(), numberButtons[6].getText(),
                numberButtons[7].getText(), numberButtons[8].getText(),
                numberButtons[7].getText(), numberButtons[10].getText(),
                numberButtons[11].getText(), numberButtons[12].getText(),
                numberButtons[13].getText(), numberButtons[14].getText(),
                numberButtons[15].getText(), numberButtons[16].getText());
    }
}
---

(1)(2) onClickNumberBlock の戻り

ブロックの移動がない場合は、単純に false を返す。

    return false;    // (1)

ブロックの移動があった場合は、完成かどうか判断して、その結果を返す。

    return isComplete();    // (2)


(3) パズルが完成かどうか

    private boolean isComplete() {    // (3)

ロジックは、単に、ブロック 1 〜 15 の間に空ブロックがなくて、番号の昇順に並んで
いれば、パズル完成、としている。


■レイアウト

クロノメーター (Chronometer) 要素を追加した。それ以外は前回からの変更なし。

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:orientation="vertical" >

    <Button
        android:id="@+id/start_button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickStartButton"
        android:text="@string/start" />

    <TableLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center" >

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton1"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="1"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton2"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="2"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton3"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="3"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton4"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="4"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton5"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="5"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton6"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="6"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton7"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="7"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton8"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="8"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton9"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="9"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton10"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="10"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton11"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="11"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton12"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="12"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton13"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="13"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton14"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="14"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton15"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="15"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton16"
                android:layout_width="0dip"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:layout_weight="1"
                android:onClick="onClickNumberButton"
                android:text="@string/emptyText"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>
    </TableLayout>

    <Chronometer
        android:id="@+id/chronometer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:format="@string/chronometerFormat" />    <!-- (1) -->

</LinearLayout>
---

(1) クロノメーター

表示形式は、res/values/strings.xml で指定。

    <Chronometer
        android:id="@+id/chronometer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:format="@string/chronometerFormat" />    <!-- (1) -->


■ データ

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="debug">true</string>
    <string name="app_name">15パズル</string>
    <string name="start">スタート</string>
    <string name="emptyText">★</string>
    <string name="chronometerFormat">経過時間 %s</string>
    <string name="finishMessage">%d 秒で完成しました。</string>
        
</resources>
---

                                                                            以上


コメント (0) |  トラックバック (0) | 

EclipseではじめるAndroidプログラミング入門―SDK2.1~2.3/4.0対応

2012年02月05日 | Android
EclipseではじめるAndroidプログラミング入門―SDK2.1~2.3/4.0対応
http://www.shuwasystem.co.jp/products/7980html/3199.html

著者 掌田津耶乃
価格 2940円(税込)(本体2800円)
ISBN 978-4-7980-3199-6
発売日 2011/12/22
判型 B5変
色数 2色
ページ数 504
CD/DVD −
対象読者 初級
シリーズ −

目次
Chapter 1 Android開発の基礎知識
1.1 EclipseとAndroid開発
1.1.1 Androidとは?
1.1.2 AndroidはJavaで開発する
1.1.3 Androidのシステム構成
1.1.4 Eclipseとは?
1.1.5 Eclipseの特徴
1.1.6 Android Development Tools(ADT)
1.1.7 Android SDKについて
1.1.8 開発に必要なソフトウェアを整理する
1.1.9 JDKの入手
1.1.10 JDKのインストール
1.1.11 Mac OS XのJDKについて
1.1.12 Eclipse IDE for Java EE Developersの入手
1.1.13 Pleiadesの入手とインストール
1.1.14 Android SDKの入手とインストール
1.1.15 SDKパッケージのインストール
1.2 Eclipseの基本操作
1.2.1 Eclipseを起動する
1.2.2 エクスプローラー
1.2.3 アウトライン
1.2.4 エディター
1.2.5 タスクリスト
1.2.6 「問題」ビュー
1.2.7 Javadoc/宣言
1.2.8 コンソール
1.2.9 パースペクティブについて
1.2.10 ADTをインストールする
1.2.11 ADTの設定を行う
1.2.12 SDKパッケージの管理
1.2.13 AVDマネージャの起動
1.2.14 仮想デバイスの作成
1.2.15 「設定」ウインドウについて
1.2.16 「一般」設定について
1.2.17 「Android」設定について
1.2.18 「Java」設定について
1.2.19 「インストール/更新」設定について
1.2.20 <ソース>メニューについて
1.2.21 <リファクタリング>メニューについて

Chapter 2 GUIを使ったアプリの基本
2.1 Android開発の基本を覚える
2.1.1 新規プロジェクトの作成
2.1.2 プロジェクトの内容をチェックする
2.1.3 エミュレーターで実行する
2.1.4 実機にインストールして実行する
2.1.5 実行構成について
2.2 プロジェクトの基本構成
2.2.1 プロジェクトのファイルを確認する
2.2.2 Activityクラスについて
2.2.3 onCreateメソッドについて
2.2.4 Rクラスについて
2.2.5 main.xmlとレイアウト・エディター
2.2.6 main.xmlのソースコードをチェック
2.2.7 レイアウト用タグの属性
2.2.8 strings.xmlについて
2.2.9 マニフェストファイルについて
2.2.10 AndroidManifest.xmlのソースコード
2.3 基本GUIを利用する
2.3.1 ボタンを作成する
2.3.2 ボタン名にリソースを設定する
2.3.3 main.xmlの変更を確認する
2.3.4 strings.xmlをチェックする
2.3.5 Activityクラスの修正
2.3.6 doActionメソッドの処理
2.3.7 イベントリスナーによるイベント処理
2.3.8 EditTextを利用する
2.3.9 EditTextのテキスト操作
2.3.10 トーストを表示しよう
2.3.11 コードによるGUI作成

Chapter 3 基本ウィジェットをマスターする
3.1 選択用ウィジェット
3.1.1 CheckBoxの利用
3.1.2 ToggleButtonについて
3.1.3 RadioButtonとRadioGroup
3.2 Spinnerによるリスト
3.2.1 Spinnerによるリスト表示
3.2.2 Spinnerのイベント処理
3.2.3 ArrayAdapterでSpinner項目を生成する
3.2.4 SeekBarによるスライダーの利用
3.2.5 OnSeekBarChangeListenerによるイベント処理
3.2.6 一定間隔ごとにSeekBarを設定するには?
3.3 より柔軟な入力と表示
3.3.1 RatingBarについて
3.3.2 ProgressBarについて
3.3.3 入力フィールドについて
3.3.4 プログラム内からのタイプ操作
3.4 ウィジェットのイベント処理
3.4.1 長押しイベントについて
3.4.2 画面の回転

Chapter 4 ダイアログとメニュー
4.1 ダイアログを利用する
4.1.1 AlertDialogを使う
4.1.2 ダイアログのボタン設定
4.1.3 ボタンの処理をまとめる
4.1.4 ダイアログにリストを表示する
4.1.5 ラジオボタンによるリストの選択
4.1.6 リストの複数項目の選択
4.2 特殊なダイアログ
4.2.1 TimePickerとDatePicker
4.2.2 DatePickerDialogを利用する
4.2.3 TimePickerDialogを利用する
4.2.4 ProgressDialogを利用する
4.3 メニューの利用
4.3.1 オプションメニューの利用
4.3.2 メニュー関連イベント用のメソッド
4.3.3 コンテキストメニューについて
4.3.4 コンテキストメニュー用メソッド

Chapter 5 レイアウトを考える
5.1 レイアウトとコンテナ
5.1.1 レイアウトとは?
5.1.2 レイアウトの種類
5.1.3 RelativeLayoutによるレイアウト
5.1.4 TableLayoutを利用する
5.1.5 内部にレイアウトを組み込む
5.1.6 スクロール表示とScrollView
5.1.7 GridViewについて
5.2 より高度なインターフェイス
5.2.1 スライディングドロワー「SlidingDrawer」
5.2.2 SlidingDrawerの開閉イベント処理
5.2.3 タブパネルの仕組み
5.2.4 タブパネルを作る
5.2.5 TabHostのコーディング
5.2.6 タブ切り替えのイベント処理
5.3 ListViewによるリスト表示
5.3.1 ListViewを使う
5.3.2 コードから項目を設定する
5.3.3 リスト項目の追加・削除
5.3.4 ListActivityを使う
5.3.5 選択モードについて
5.3.6 CheckTextViewによるリスト項目
5.3.7 CheckTextViewによるリスト項目
5.3.8 リスト項目のカスタマイズ

Chapter 6 インテントをマスターする
6.1 インテントの基本をマスターする
6.1.1 アクティビティとインテント
6.1.2 明示的インテントと暗示的インテント
6.1.3 アクティビティの切り替え
6.1.4 HelloAppActivityの修正
6.1.5 OtherActivityの作成
6.1.6 アクティビティのライフサイクル
6.1.7 暗示的インテントについて
6.2 インテントを活用する
6.2.1 ACTION_SENDアクションを使う
6.2.2 Intentから値を受け取る
6.2.3 Intentにオブジェクトを渡す
6.2.4 オブジェクトをIntentから受け取る
6.2.5 Intentから返り値を受け取る
6.3 ステータスバーの利用
6.3.1 ノーティフィケーション
6.3.2 ノーティフィケーションを作ってみる
6.3.3 ノーティフィケーション利用の流れ
6.3.4 主なプロパティを指定する

Chapter 7 グラフィックの描画
7.1 Viewクラスとグラフィック描画
7.1.1 Viewと描画の仕組み
7.1.2 MyViewクラスの作成
7.1.3 MyViewのソースコードを作成する
7.1.4 main.xmlにMyViewを追加する
7.1.5 onDrawメソッドの描画処理
7.1.6 主な図形の描画メソッド
7.1.7 図形のプロパティを設定する
7.1.8 カラーを使いこなす
7.1.9 テキストの描画
7.2 グラフィック機能を使いこなす
7.2.1 ActivityからMyViewを制御する
7.2.2 タッチして描画する
7.2.3 イメージの描画
7.2.4 パスを使ったクリッピング
7.2.5 座標変換について
7.2.6 座標軸の保存とリストア
7.3 SurfaceViewによる高速描画
7.3.1 SurfaceViewとは?
7.3.2 SurfaceViewクラスの基本ソースコード
7.3.3 描画コードを作成する
7.3.4 描画処理の流れを整理する
7.3.5 ユーザー操作による表示の更新
7.3.6 タイマースレッドとScheduledExecutorService
7.3.7 ScheduledExecutorService利用の流れ

Chapter 8 ハードウェアアクセス
8.1 センサーの利用
8.1.1 センサーとSensorEventListener
8.1.2 センサー利用の基本
8.1.3 センサー利用クラスの基本コード
8.1.4 センサーを使う
8.1.5 センサー利用の流れを整理する
8.1.6 GPSを利用する
8.1.7 GPSを使ったプログラムの実際
8.2 カメラの利用
8.2.1 カメラとSurfaceView
8.2.2 カメラを使ったサンプルの作成
8.2.3 カメラのプレビューを表示する
8.2.4 CameraとSurfaceViewの流れ
8.2.5 カメラで撮影をする
8.2.6 撮影の流れを整理する
8.2.7 Intentでカメラアプリと連携する
8.2.8 カメラアプリからプレビュー画像を取得する

Chapter 9 データの利用
9.1 データアクセス
9.1.1 Androidのデータ管理
9.1.2 テキストファイルのアクセス
9.1.3 テキストファイルへのアクセス
9.1.4 SQLiteの利用
9.1.5 main.xmlの準備
9.1.6 SQLiteOpenHelperクラスの作成
9.1.7 Activityクラスの作成
9.1.8 データの追加・削除・検索
9.2 設定画面による値保存
9.2.1 設定画面とPreferenceActivity
9.2.2 設定用ファイルの用意
9.2.3 PreferenceActivityクラスの作成
9.2.4 設定画面を利用する
9.2.5 設定を変更したときの処理
9.2.6 Activityから設定値を利用する

Chapter 10 アプリ以外のプログラム
10.1 サービス
10.1.1 サービスとは?
10.1.2 サービスの基本構造
10.1.3 Serviceクラスの作成
10.1.4 MyService/MyBinderの実装
10.1.5 ActivityからMyServiceを利用する
10.1.6 ServiceConnectionとBroadcastReceiver
10.1.7 サービスの機能を外部から呼び出す
10.2 ウィジェットの作成
10.2.1 ウィジェットとは?
10.2.2 XMLファイルの作成
10.2.3 レイアウトとマニフェストの修正
10.2.4 AppWidgetProviderクラスの作成
10.2.5 サービスとの連携
10.2.6 サービスを実装する
10.2.7 サービスの処理の流れを整理する
10.3 タブレット・アプリとフラグメント
10.3.1 タブレットと画面の分断問題
10.3.2 フラグメントの基本
10.3.3 プロジェクトを作成する
10.3.4 レイアウトの作成
10.3.5 マニフェストファイルの作成
10.3.6 HelloFragmentAppActivityの作成
10.3.7 TitlesFragmentクラスの作成
10.3.8 DetailsFragmentクラスの作成

Chapter 11 これから先の世界
11.1 学習の指針
11.1.1 まだやっていないこと
11.1.2 分断化問題について
11.1.3 Android 4.0について
11.1.4 Android Marketについて

コメント (0) |  トラックバック (1) | 

[Android] 15 パズル (2) - カスタム・ボタン

2012年02月04日 | Android
                                                                    2012-01-04
[Android] 15 パズル (2) - カスタム・ボタン
================================================================================

前回の「15 パズル (1) - テーブル・レイアウト」
http://blog.goo.ne.jp/marunomarunogoo/e/36e4574a26b3362719a348dcdaf003d3
は、16 個の数字ボタンすべてに別々のハンドラーを設定していた。
このサンプルは、上記のサンプルに対し、次のようにしている。
・数字ボタンに対するハンドラーをまとめた
・ボタン・クラスを継承したカスタム・ボタンを作る
・4x4 のボードに対応するクラスを作り、そこで、ボタンの移動などを管理する


今回作ったクラスはつぎの 3 つ。
FifteenPuzzleActivity    アクティビティ
FifteenPuzzleBoard       ボード
NumberButton             数字ブロック(カスタム・ボタン)

なお、前回も使ったつぎのクラスに変更はない。
ArrayCollectionUtil      配列やコレクション関係のユーティリティ
SymmetricGroupUtil       対称群と置換のユーティリティ


■ アクティビティ

ハンドラーを定義して、ボードのメソッドを呼び出すだけになった。

□ FifteenPuzzleActivity.java
---
package jp.marunomaruno.android.fifteenpuzzle;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class FifteenPuzzleActivity extends Activity {

    private FifteenPuzzleBoard board;    // 15パズルのボード    // (1)
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        board = new FifteenPuzzleBoard(this);        // (2)
    }

    /**
     * 数字ボタン・クリック時のハンドラー
     * @param v
     */
    public void onClickNumberButton(View v) {
        board.onClickNumberBlock((NumberButton) v);    // (3)
    }

    /**
     * 開始ボタン・クリック時のハンドラー
     * @param v
     */
    public void onClickStartButton(View v) {
        board.reset();    // (4)
    }
}
---

(1)(2) 15パズルのボード

4x4 のボードのオブジェクト。数字をずらすロジックなどは、こちらにある。

    private FifteenPuzzleBoard board;    // 15パズルのボード    // (1)

    board = new FifteenPuzzleBoard(this);        // (2)


(3) 数字ボタン・クリック時のハンドラー呼び出し

    board.onClickNumberBlock((NumberButton) v);    // (3)


(4) 開始ボタン・クリック時のハンドラー呼び出し

    board.reset();    // (4)


■ ボード

4x4 のボードを管理するクラス。数字ボタンをクリックしたときの動きなどを規定してい
る。

□ FifteenPuzzleBoard.java
---
package jp.marunomaruno.android.fifteenpuzzle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import jp.marunomaruno.android.util.ArrayCollectionUtil;
import jp.marunomaruno.android.util.SymmetricGroupUtil;
import android.app.Activity;
import android.content.Context;

public class FifteenPuzzleBoard {

    public Context context;
    
    public static final int ORDER = 4; // 4次の正方行列

    private NumberButton[] numberButtons;
    private int emptyBlockIndex = ORDER * ORDER;
    
    public FifteenPuzzleBoard(Context context) {
        this.context = context;

        Activity activity = (Activity) context;
        
        numberButtons = new NumberButton[] {
                null, // ボード上の番号と合わせるため、インデックス0をダミーとする
                (NumberButton) activity.findViewById(R.id.numberButton1),
                (NumberButton) activity.findViewById(R.id.numberButton2),
                (NumberButton) activity.findViewById(R.id.numberButton3),
                (NumberButton) activity.findViewById(R.id.numberButton4),
                (NumberButton) activity.findViewById(R.id.numberButton5),
                (NumberButton) activity.findViewById(R.id.numberButton6),
                (NumberButton) activity.findViewById(R.id.numberButton7),
                (NumberButton) activity.findViewById(R.id.numberButton8),
                (NumberButton) activity.findViewById(R.id.numberButton9),
                (NumberButton) activity.findViewById(R.id.numberButton10),
                (NumberButton) activity.findViewById(R.id.numberButton11),
                (NumberButton) activity.findViewById(R.id.numberButton12),
                (NumberButton) activity.findViewById(R.id.numberButton13),
                (NumberButton) activity.findViewById(R.id.numberButton14),
                (NumberButton) activity.findViewById(R.id.numberButton15),
                (NumberButton) activity.findViewById(R.id.numberButton16), 
            };

        // 最後のブロックを空とする
        numberButtons[emptyBlockIndex].setText(R.string.emptyText);

        log("initialize() ");
    }

    /**
     * 空ブロックが指定されたインデックスに入っているかどうか判定する。
     * @param indexes
     * @return 空ブロックが指定されたインデックスに入っていればtrue
     */
    private boolean isEmptyBlockIndexIn(int... indexes) {
        assert indexes.length > 0;

        return ArrayCollectionUtil.isKeyIn(emptyBlockIndex, indexes);
    }

    /**
     * 数字ボタン・クリック時のハンドラー
     * @param v
     */
    public void onClickNumberBlock(NumberButton button) {
        // このブロックに対して、水平方向に有効な空ブロックの位置
        final int[][] HORIZONTAL_INDEXES = new int[][]{    // (1)
                null,
                
                {2, 3, 4},
                {1, 3, 4},
                {1, 2, 4},
                {1, 2, 3},
                
                {6, 7, 8},
                {5, 7, 8},
                {5, 6, 8},
                {5, 6, 7},
                
                {10, 11, 12},
                {9, 11, 12},
                {9, 10, 12},
                {9, 10, 11},
                
                {14, 15, 16},
                {13, 15, 16},
                {13, 14, 16},
                {13, 14, 15},
        };

        // このブロックに対して、垂直方向に有効な空ブロックの位置
        final int[][] VERTICAL_INDEXES = new int[][]{    // (2)
                null, 
                
                {5, 9, 13},
                {6, 10, 14},
                {7, 11, 15},
                {8, 12, 16},

                {1, 9, 13},
                {2, 10, 14},
                {3, 11, 15},
                {4, 12, 16},

                {1, 5, 13},
                {2, 6, 14},
                {3, 7, 15},
                {4, 8, 16},

                {1, 5, 9},
                {2, 6, 10},
                {3, 7, 11},
                {4, 8, 12},

        };

        System.out.printf("onClick() %s", button.toString());
        
        // 空の場合、何もしない
        if (isEmptyBlock(button)) {
            System.out.println(": empty");
            return;
        }
        
        
        int blockIndex = button.getBlockIndex();
        
        if (isEmptyBlockIndexIn(HORIZONTAL_INDEXES[blockIndex])) {    // (3)
            rotateHorizontal(blockIndex);    // (4)

            System.out.println();    // 最後にlogを出すための改行
            return;
        }

        if (isEmptyBlockIndexIn(VERTICAL_INDEXES[blockIndex])) {
            rotateVertical(blockIndex);    // (5)
            
            System.out.println();    // 最後にlogを出すための改行
            return;
        }
        
        System.out.println();    // 最後にlogを出すための改行
    }
    
    /**
     * 垂直にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateVertical(int index) {
        if (index < emptyBlockIndex) {
            rotateDown(index);
        } else {
            rotateUp(index);
        }            
    }

    /**
     * 水平にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateHorizontal(int index) {
        if (index < emptyBlockIndex) {
            rotateRight(index);
        } else {
            rotateLeft(index);
        }            
    }

    /**
     * 左にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateLeft(int index) {
        System.out.printf("rotateLeft() %d %d%n", index, emptyBlockIndex);
        rotateAscending(index, 1);
    }

    /**
     * 右にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateRight(int index) {
        System.out.printf("rotateRight() %d %d%n", index, emptyBlockIndex);
        rotateDescending(index, 1);
    }

    /**
     * 下にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateDown(int index) {
        System.out.printf("rotateDown() %d %d%n", index, emptyBlockIndex);
        rotateDescending(index, ORDER);
    }

    /**
     * 上にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateUp(int index) {
        System.out.printf("rotateUp() %d %d%n", index, emptyBlockIndex);
        rotateAscending(index, ORDER);
    }

    /**
     * 降順にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateDescending(int index, int step) {
        for (int i = emptyBlockIndex; i > index; i -= step) {
            numberButtons[i].setText(numberButtons[i - step].getText());
        }

        numberButtons[index].setText(R.string.emptyText);
        emptyBlockIndex = index;
    }

    /**
     * 昇順にローテートする。
     * @param index 空ブロックを置く位置
     */
    private void rotateAscending(int index, int step) {
        for (int i = emptyBlockIndex; i < index; i += step) {
            numberButtons[i].setText(numberButtons[i + step].getText());
        }

        numberButtons[index].setText(R.string.emptyText);
        emptyBlockIndex = index;
    }

    /**
     * このブロックが空かどうかを判断する。
     * @return 空の場合true
     */
    public boolean isEmptyBlock(NumberButton button) {
        return button.getBlockIndex() == emptyBlockIndex;
    }
    
    /**
     * ボードをリセットする
     */
    public void reset() {
        // 1~15 までの乱数を生成する
        List<Integer> numberList = new ArrayList<Integer>();
        numberList.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
                14, 15));
        
        int[] numberArray;
        do {
            Collections.shuffle(numberList);
            numberArray = ArrayCollectionUtil.toArray(numberList);
        } while (SymmetricGroupUtil.sgn(numberArray) != 1);    // 解けない問題(遇置換でない)なら作り直し

        // 数字ブロックを設定する
        for (int i = 1; i < numberArray.length + 1; i++) {
            numberButtons[i].setText(String.valueOf(numberArray[i - 1]));
        }

        // 空ブロックを設定する
        numberButtons[ORDER * ORDER].setText(R.string.emptyText);
        emptyBlockIndex = ORDER * ORDER;

        log("onClickStartButton() ");
    }
    
    private void log(String message) {
        System.out.println(message);
        System.out.print(toString());
    }

    @Override
    public String toString() {
        return String.format(
                "[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n",
                numberButtons[1].getText(), numberButtons[2].getText(),
                numberButtons[3].getText(), numberButtons[4].getText(),
                numberButtons[5].getText(), numberButtons[6].getText(),
                numberButtons[7].getText(), numberButtons[8].getText(),
                numberButtons[7].getText(), numberButtons[10].getText(),
                numberButtons[11].getText(), numberButtons[12].getText(),
                numberButtons[13].getText(), numberButtons[14].getText(),
                numberButtons[15].getText(), numberButtons[16].getText());
    }
}
---

(1) このブロックに対して、水平方向に有効な空ブロックの位置の定義

最初はダミーで null を入れている。

    final int[][] HORIZONTAL_INDEXES = new int[][]{    // (1)
            null,
            
            {2, 3, 4},
            ... (中略) ...
            {13, 14, 15},
    };

たとえば、自身が 2 のときは、水平方向に動かす場合、1 または 3, 4 に空ブロックがあることになる。


(2) このブロックに対して、垂直方向に有効な空ブロックの位置の定義

    final int[][] VERTICAL_INDEXES = new int[][]{    // (2)
            null, 
            
            {5, 9, 13},
            ... (中略) ...
            {4, 8, 12},
        };


(3)(4) 水平方向に空ブロックがあれば、水平方向にローテートする

    if (isEmptyBlockIndexIn(HORIZONTAL_INDEXES[blockIndex])) {    // (3)
        rotateHorizontal(blockIndex);    // (4)


(5) 垂直方向に空ブロックがあれば、垂直方向にローテートする

    rotateVertical(blockIndex);    // (5)

なお、水平方向、垂直方向のローテートは、自身と空ブロックの位置関係により、
右・左ローテート、上・下ローテートすることになる。
また、左・上のローテートは、空ブロックを番号の大きい方向に持っていく(昇順)ロー
テート、右・下のローテートは、空ブロックを番号の小さい方向に持っていく(降順)ロー
テートとなっているので、ロジックはこの昇順・降順ローテートが担当する。


■ 数字ブロック(ボタン)

Button クラスを継承したカスタム・ボタンのクラス。
現在のブロックの位置を示すインデックスを管理する。


□ NumberButton.java
---
package jp.marunomaruno.android.fifteenpuzzle;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.Button;

/**
 * 数字ボタン。
 * 2つの番号で管理する。
 * ・ブロック・インデックス: 生成時のインデックスで、変更されない
 * ・番号: ブロックに表示される番号。
 * @author marunomaruno
 */
public class NumberButton extends Button {    // (1)

    /**
     * 空ブロックを示す番号
     */
    public static final int EMPTY_NUMBER = 0;    // (2)

    private static int maxNumber = 0;    // 最大の番号    // (3)

    private int blockIndex; // このブロックのインデックス(生成時のまま変更なし)// (4)

    public NumberButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize();
    }

    public NumberButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public NumberButton(Context context) {
        super(context);
        initialize();
    }

    private void initialize() {
        maxNumber++;    // (5)
        blockIndex = maxNumber;
        setText(String.valueOf(blockIndex));
    }
    
    /**
     * このブロックの番号を設定する。
     * @param newNumber 新しい番号
     */
    public void setNumber(int newNumber) {
        if (newNumber != EMPTY_NUMBER) {    // (6)
            setText(String.valueOf(newNumber));

        } else {
            setText(getResources().getString(R.string.emptyText));
        }
    }
    
    /**
     * 番号を取得する。
     * @return このブロックの番号。数値でない場合は0.
     */
    public int getNumber() {
        try {
            return Integer.parseInt(getText().toString());    // (7)
        } catch (NumberFormatException e) {
            return EMPTY_NUMBER;
        }
    }

    /**
     * このブロックのインデックスを取得する。
     * @return このブロックのインデックス
     */
    public int getBlockIndex() {
        return blockIndex;
    }

    @Override
    public String toString() {
        return String.format("NB[%d, %s]", blockIndex, getText());
    }
}
---

(1) Button クラスを継承

    public class NumberButton extends Button {    // (1)


(2) 空ブロックを示す番号

    public static final int EMPTY_NUMBER = 0;    // (2)

(3)(5) 最大の番号

このインスタンスを生成するときにインデックスとして番号を振っていくので、現在の最
大の番号を静的に保持する。

    private static int maxNumber = 0;    // 最大の番号    // (3)

    maxNumber++;    // (5)


(4) このブロックのインデックス

このブロックのインデックス。これは、生成時のまま変更しない。

    private int blockIndex; // このブロックのインデックス(生成時のまま変更な
し)// (4)


(6) このブロックの番号を設定

空ブロックでなければ、引数の値を設定する。
空ブロックのときは、リソースで指定された空ブロックの記号を設定する。

    if (newNumber != EMPTY_NUMBER) {    // (6)
        setText(String.valueOf(newNumber));

    } else {
        setText(getResources().getString(R.string.emptyText));
    }

(7) このブロックの番号を取得

数値でない場合は 0 にする。

    try {
        return Integer.parseInt(getText().toString());    // (7)
    } catch (NumberFormatException e) {
        return EMPTY_NUMBER;
    }


■レイアウト

カスタム・ボタンを使っているので、ボタンのクラス名は、完全修飾名としてつぎを指定
する。
    jp.marunomaruno.android.fifteenpuzzle.NumberButton

数字ブロッククリック時のハンドラーを同じものにした。

□ res/layout/main.xml
---
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center_vertical"
    android:orientation="vertical" >

    <Button
        android:id="@+id/start_button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickStartButton"
        android:text="@string/start" />

    <TableLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" >

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton1"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="1"
                android:textAppearance="?android:attr/textAppearanceLarge" /> 
                                                                    

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton2"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="2"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton3"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="3"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton4"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="4"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton5"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="5"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton6"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="6"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton7"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="7"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton8"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="8"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton9"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="9"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton10"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="10"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton11"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="11"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton12"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="12"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>

        <TableRow
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton13"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="13"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton14"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="14"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton15"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="15"
                android:textAppearance="?android:attr/textAppearanceLarge" />

            <jp.marunomaruno.android.fifteenpuzzle.NumberButton
                android:id="@+id/numberButton16"
                android:layout_width="0dip"
                android:layout_weight="1"
                android:layout_height="90px"
                android:layout_margin="1px"
                android:onClick="onClickNumberButton"
                android:text="@string/emptyText"
                android:textAppearance="?android:attr/textAppearanceLarge" />
        </TableRow>
    </TableLayout>

</LinearLayout>
---

(1) カスタム・ボタンの指定

    <jp.marunomaruno.android.fifteenpuzzle.NumberButton
        android:id="@+id/numberButton1"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="90px"
        android:layout_margin="1px"
        android:onClick="onClickNumberButton"
        android:text="1"
        android:textAppearance="?android:attr/textAppearanceLarge" /> 

                                                                            以上


コメント (0) |  トラックバック (0) |