goo blog サービス終了のお知らせ 

marunomaruno-memo

marunomaruno-memo

[Android] クリックしたタイムスタンプを保存して、メールで送信する

2012年06月24日 | Android
[Android] クリックしたタイムスタンプを保存して、メールで送信する
================================================================================

ちょっと必要があって、標記のアプリを作った。
メールの送信先のアドレスも固定(リソースで定義)。

タイムスタンプは long 型なので、それらの値を List<Long> で保持したいところ。でも、
onSaveInstanceState()、onRestoreInstanceState() でインスタンス間の受け渡しを行う
にしても、残念ながら Bundle クラスには、putLongArrayList() というメソッドはない。
しょうがないので、long 型のタイムスタンプを上位 4 バイトと下位 4 バイトに分けて、
上位 4 バイトは long 型、下位 4 バイトは int 型のリスト(正確には Integer 型のリ
スト)に保存することにした。そうすることで、putIntegerArrayList() メソッドが使え
る。

しかし、putLongArrayList() がないのは、int が整数の標準の型なのでしょうがないと
して、putIntegerArray() がないというのは、納得がいかない気がする。というよりも、
putIntegerCollection() というのがあってもよさそうだと思う。


■ アクティビティ

□ TimestampLoggerActivity.java
---
package jp.marunomaruno.android.timestamplogger;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

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

/**
 * クリックしたタイムスタンプを保存して、メールで送信する。 
 * メールの送信先のアドレスも固定。
 *
 * @author marunomaruno
 * @version 1.0, 2012-06-20
 */
public class TimestampLoggerActivity extends Activity {
    private static final String TEMP_FILE_NAME = "data.dat";
    private static final long TIMESTAMP_HEIGHT_BYTES_MASK = 0xFFFFFFFF00000000L;
                                  // long型の上位4バイトを取得するマスク // (1)
    private static final String TIMESTAMP_LIST = "timestampList";
    private static final String TIMESTAMP_HEIGHT_BYTES = "timestampHightBytes";
    private ArrayList<Integer> timestampList; 
                                 // タイムスタンプの下位4バイト分のリスト // (2)
    private long timestampHightBytes = 0; // タイムスタンプの上位4バイト // (3)

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

        timestampList = new ArrayList<Integer>();
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        timestampHightBytes = savedInstanceState
                .getLong(TIMESTAMP_HEIGHT_BYTES);
        timestampList = savedInstanceState.getIntegerArrayList(TIMESTAMP_LIST);

        System.out.println("onRestoreInstanceState: " + timestampList);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putLong(TIMESTAMP_HEIGHT_BYTES, timestampHightBytes);
        outState.putIntegerArrayList(TIMESTAMP_LIST, timestampList);
        System.out.println("onSaveInstanceState: " + timestampList);
    }

    @Override
    protected void onPause() {
        super.onPause();
        try {
            write();

        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("onPause: " + timestampList);
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        try {
            read();

        } catch (FileNotFoundException e) {
            assert true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("onRestart: " + timestampList);
    }

    /**
     * タイムスタンプを取得するボタンのクリック・ハンドラー
     *
     * @param view
     */
    public void onStampButtonClick(View view) {
        // リストの最初のタイムスタンプの上位4バイトを保持する
        if (timestampHightBytes == 0) {
            timestampHightBytes = System.currentTimeMillis()
                    & TIMESTAMP_HEIGHT_BYTES_MASK; // (4)
        }

        // タイムスタンプの下位4バイトをリストに追加する
        timestampList.add((int) System.currentTimeMillis()); // (5)
        TextView stampView = (TextView) findViewById(R.id.stampView);
        stampView.setText(toTimestampStrings());
    }

    /**
     * タイムスタンプのリストをクリアするボタンのクリック・ハンドラー
     *
     * @param view
     */
    public void onClearButtonClick(View view) {
        timestampList.clear();
        TextView stampView = (TextView) findViewById(R.id.stampView);
        stampView.setText("");
    }

    /**
     * タイムスタンプを送信するボタンのクリック・ハンドラー
     *
     * @param view
     */
    public void onEmailButtonClick(View view) {
        if (timestampList.size() == 0) {
            TextView stampView = (TextView) findViewById(R.id.stampView);
            stampView.setText(R.string.noTimestamp);
            return;
        }

        Uri uri = Uri.parse("mailto:" + getString(R.string.emailAddress)); // (6)
        Intent intent = new Intent(Intent.ACTION_SENDTO, uri); // (7)
        intent.putExtra(Intent.EXTRA_SUBJECT, String.format(
                "timestamp %1$tF %1$tT",
                (timestampHightBytes | timestampList.get(0)))); // (8)
        intent.putExtra(Intent.EXTRA_TEXT, toTimestampStrings()); // (9)
        startActivity(intent); // (10)
    }

    /**
     * タイムスタンプのリストを文字列化する。 
     * タイムスタンプは、JISの日時の文字列(yyyy-mm-dd hh:mm:ss)であらわし、
     * これをリストとして並べたもの。
     *
     * @return タイムスタンプのリストの文字列
     */
    private String toTimestampStrings() {
        StringBuilder sb = new StringBuilder();
        for (int timestamp : timestampList) {
            sb.append(String.format("[%1$tF %1$tT]%n",
                    (timestampHightBytes | timestamp)));
        }
        return sb.toString();
    }

    /**
     * データをファイルに一時保存のため書き出す。
     *
     * @throws IOException
     */
    public void write() throws IOException {
        DataOutputStream out = new DataOutputStream(openFileOutput(TEMP_FILE_NAME,
                MODE_PRIVATE));

        out.writeLong(timestampHightBytes);
        for (int timestamp : timestampList) {
            out.writeInt(timestamp);
        }

        out.close();
    }

    /**
     * 一時保存されたデータをファイルから読み出す。
     *
     * @throws IOException
     */
    public void read() throws IOException {
        DataInputStream in = new DataInputStream(openFileInput(TEMP_FILE_NAME));

        timestampList.clear();

        try {
            timestampHightBytes = in.readLong();
            while (true) {
                timestampList.add(in.readInt());
            }

        } catch (EOFException e) { // (10)
            assert true;
        }

        in.close();
    }

}
---

(1) long型の上位4バイトを取得するマスク

long 型のタイムスタンプを上位 4 バイトと下位 4 バイトに分けるためのマスク。

    private static final long TIMESTAMP_HEIGHT_BYTES_MASK = 0xFFFFFFFF00000000L; // (1)


(2) タイムスタンプの下位4バイト分のリスト

    private ArrayList<Integer> timestampList;  // (2)


(3) タイムスタンプの上位4バイト

    private long timestampHightBytes = 0;  // (3)


(4) リストの最初のタイムスタンプの上位 4 バイトを保持する

TIMESTAMP_HEIGHT_BYTES_MASK と AND を取ることで、上位 4 バイト分を確保する。

    timestampHightBytes = System.currentTimeMillis()
                    & TIMESTAMP_HEIGHT_BYTES_MASK; // (4)


(5) タイムスタンプの下位4バイトをリストに追加する

下位 4 バイトは、int 型にキャストすればよい。

    timestampList.add((int) System.currentTimeMillis()); // (5)


(6)-(10) タイムスタンプをメールで送信する

メールのあて先アドレスを Uri インスタンスとして組み立てる。

    Uri uri = Uri.parse("mailto:" + getString(R.string.emailAddress)); // (6)

メールを送るためのインテント・インスタンスを生成する

    Intent intent = new Intent(Intent.ACTION_SENDTO, uri); // (7)

メールの件名を設定する。このとき、件名に最初のタイムスタンプを入れるので、タイム
スタンプを組み立ても行う。
上位 4 バイトと下位 4 バイトに分かれているものを、OR することで、ふたたび long 
型にする。

    intent.putExtra(Intent.EXTRA_SUBJECT, String.format(
                "timestamp %1$tF %1$tT",
                (timestampHightBytes | timestampList.get(0)))); // (8)

本文に、タイムスタンプを文字列化して並べる。

    intent.putExtra(Intent.EXTRA_TEXT, toTimestampStrings()); // (9)


メール送信のアクティビティを起動する。

    startActivity(intent); // (10)


(11) EOF の検知

DataInputStream の readInt() メソッドを使っているので、EOF は EOFException を使
って検知する。

    } catch (EOFException e) { // (11)


■ レイアウト

タイムスタンプの表示は、今回のわたしの使い方としては、せいぜい 10 件程度なので、
ListView などを使わずに、単純に TextView を使っている。

□ res/values/strings.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" >

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/stamp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onStampButtonClick"
            android:text="@string/stampButtonLabel"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClearButtonClick"
            android:text="@string/clearButtonLabel"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/email"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onEmailButtonClick"
            android:text="@string/emailButtonLabel"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <TextView
        android:id="@+id/stampView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="" />

</LinearLayout>
---


■ リソース

【emailアドレス】部分に、タイムスタンプを送信したいあて先を指定する。


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

    <string name="hello">Hello World, TimestampLoggerActivity!</string>
    <string name="app_name">TimestampLogger</string>
    <string name="stampButtonLabel">クリック</string>
    <string name="clearButtonLabel">クリア</string>
    <string name="emailButtonLabel">email</string>
    <string name="noTimestamp">タイムスタンプがありません。メール送信はしません。</string>
    <string name="emailAddress">【emailアドレス】</string>

</resources>
---

                                                                            以上


[C][アルゴリズム] チューリングマシンのエミュレーター

2012年06月23日 | C / C++
[C][アルゴリズム] チューリングマシンのエミュレーター
================================================================================

2ヶ月くらい前にチューリングマシンのエミュレーターを作った。
まだ、公開するつもりではなかった(もう少し検証する予定だった)が、本日が、チュー
リングの生誕100年だったので、ベータ版として公開。
そのうち、直すかもしれない。


■チューリングマシン

チューリングマシンは、テープ、ヘッド、制御部の 3 つから構成されています。

テープは無限の長さを持っていて、各ます目には 0 か 1 のどちらかの記号が書けるよう
になっています。ヘッドはテープ上の記号を読み書きするもので、常にひとつのます目を
見ています。また、制御部は、現在の状態を表す変数と動作を決める規則(プログラム)
が入っています。 制御部は、あらかじめ決められた p0 から pn までの有限個の状態の
うちのひとつを取ります(p0 を初期状態、pn を停止状態といいます)。また、このチ
ューリングマシンは、動作を決めた規則の中で、ヘッドの読んだ記号と現在の状態の 2 
つから次の動作を繰り返します。

●チューリングマシンの動作
① テープに 0 か 1 の記号を書き込む。
② ヘッドを右か左のどちらかに移動する。
③ 制御部の状態を別の(または同じ)状態に移す。

●チューリングマシンを使った計算手順
① テープ上に、入力データに相当する 0,1 の記号列を用意する。
② 制御部の状態を初期状態にして、規則に従い動作を開始する。
③ 制御部の状態が停止状態になったとき、計算を終了したものとする。
 出力(計算結果)は、停止したときのテープ上に残された記号で表される。
③’制御部の状態が停止状態にならないとき、計算は終了しないものとする。

●例
5 + 3 = 8 を行う

テープ上のデータについては、簡単にするために記号 1 の個数で整数値を表します。特
にここでは、n + 1 個の記号 1 で整数の n を表すことにし、足される 2 数の区切りと
してはひとつの記号 0 を使うものとします。

                        5      3
 入力データ:……000001111110111100000……

 このテープ上のデータに対して、次の 4 つの状態と規則でチューリングマシンを動作さ
せると出力はどうなるでしょうか。
(状態と規則)
① 制御部の状態:p0,p1,p2,p3 の 4 つ
         p0 が初期状態、p3 が停止状態

② 規則

状態 入力記号 書込み記号 ヘッドの移動 次の状態
p0     0          0          右         p0
p0     1          0          右         p1
p1     0          0          右         p3
p1     1          0          右         p2
p2     0          1          右         p3
p2     1          1          右         p2

参考)コンピュータシステムの基礎、アイテック情報技術教育研究所 (著) より


■ 実装

上記の説明に合わせて、つぎのように作る。

ルールとテープを標準入力またはファイルから指定して、動かす。
テープの動きの結果は標準出力に出る。

注)ルールはファイルを使わなければならない。

コマンド形式
---
    turing ルール・ファイル名 [テープ・ファイル名]
---


□ テープ

0 と 1 を記述したファイル。


□ ルール

つぎの 5 つをタブ区切りのファイルとして作る。
なお、停止状態は、「次の状態」の中での最大値とする。
---
状態 入力記号 書込み記号 ヘッドの移動 次の状態
---

状態            現在の状態。0 以上の整数で指定。

入力記号        現在の状態で、ヘッドが指す記号。0 または 1

書込み記号        現在の状態で、ヘッドの位置に書き込む記号。0 または 1

ヘッドの移動    ヘッドの移動方向。左は -1、右は 1

次の状態        遷移後の状態。0 以上の整数で指定。最大値が停止状態になる


■ プログラム

□ turing.c
---
/*
 ============================================================================
 Name        : turing.c
 Author      : marunomaruno
 Version     : 0.1, 2012-06-23
 Description : チューリングマシン
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

/**
 * ルール
 */
typedef struct {
    int status; // 状態(0以上の値)
    char inSymbol; // 入力記号('0', '1' または 'B')
    char outSymbol; // 書込み記号('0' または '1')
    int direction; // ヘッドの移動方向(-1: 左、1: 右)
    int nextStatus; // 次の状態(0以上の値)
} Rule;

Rule* findRule(char symbol, int status, Rule* ruleTable, int size);
void printlnRule(Rule* rule);
void printRule(Rule* rule);
int findCharOnTape(char* tape, int start, char c);
void printProgress(char* tape, int head, Rule* rule);
void printlnProgress(char* tape, int head, Rule* rule);
void printCount(char* tape);
void printlnCount(char* tape);
int readTape(FILE* in, char* tape);
int readRule(FILE* in, Rule** rule);
void printRuleTable(Rule* rule, int size);
int getStopStatus(Rule* rule, int size);

/**
 * ルールとテープをファイルから入力して、チューリングマシンを実行する。
 * ルールは、次の5項目をタブ区切りで記述する。
 *     状態, 入力記号, 書込み記号, ヘッドの移動, 次の状態
 *
 * @param argc コマンドライン・パラメーター数
 * @param argv コマンドライン・パラメーター値
 *             argv[1]: ルールのファイル。省略時は標準入力
 *             argv[2]: テープのファイル。省略時は標準入力
 * @return 常に0
 */
int main(int argc, char* argv[]) {
    printf("program start¥n");

    const int START_STATUS = 0; // 初期状態

    // 状態, 入力記号, 書込み記号, ヘッドの移動, 次の状態
    Rule* ruleTable;

    int status; // 状態

    char tape[BUFSIZ]; // テープ
    int head; // テープのヘッドの位置

    // ファイル
    FILE* tapeFp;
    FILE* ruleFp;

    switch(argc) {
    case 1:
        ruleFp = stdin;
        tapeFp = stdin;
        break;

    case 2:
        ruleFp = fopen(argv[1], "r");
        tapeFp = stdin;
        break;

    default:
        ruleFp = fopen(argv[1], "r");
        tapeFp = fopen(argv[2], "r");
        break;
    }

    if (ruleFp == NULL) {
        assert(0);
    }
    if (tapeFp == NULL) {
        assert(0);
    }

    // ルールの読み取り
    int ruleSize = readRule(ruleFp, &ruleTable);
    printf("ruleSize = %d¥n", ruleSize);
    printRuleTable(ruleTable, ruleSize);
    fclose(ruleFp);

    // テープの読み取り
    int tapeLength = readTape(tapeFp, tape);;
    printf("tape = ¥"%s¥"¥n", tape);
    fclose(tapeFp);

    // 状態を定義する
    status = START_STATUS; // 初期状態
    int stopStatus = getStopStatus(ruleTable, ruleSize);    // 停止状態

    head = 0;    // テープ・ヘッドの初期化
    while (status != stopStatus) {
        assert(0 <= head && head < tapeLength);
        Rule* rule = findRule(tape[head], status, ruleTable, ruleSize); 
                                                             // ルールを見つける
        printlnProgress(tape, head, rule);
        tape[head] = rule->outSymbol;    // テープの書き換え
        head += rule->direction;    // ヘッドを移動
        status = rule->nextStatus;    // 状態を新しい状態にする
    }
    printlnProgress(tape, head, NULL);

    free(ruleTable);

    printf("program end¥n");
    return EXIT_SUCCESS;
}

/**
 * テープを読み込む。
 * テープの最大長は、BUFSIZ (512) バイトとする。
 * @param in ファイルポインター
 * @param tape テープ
 * @return テープの長さ
 */
int readTape(FILE* in, char* tape) {
    assert(in != NULL);
    assert(tape != NULL);

    fgets(tape, BUFSIZ, in);
    tape[BUFSIZ] = '¥0';
    char* p = strstr(tape, "¥n");
    if (p != NULL) {
        *p = '¥0';
    }
    return strlen(tape);
}

/**
 * ルールを読み込む。
 * なお、ルール配列はこの関数中で領域確保するので、使用側で解放する必要がある。
 * @param in ファイルポインター
 * @param rule ルール配列
 * @return ルール配列のサイズ
 */
int readRule(FILE* in, Rule** rule) {
    assert(in != NULL);
    assert(rule != NULL);

    const int RULE_MALLOC_UNIT = 16;   // ルール配列をとる場合のサイズの初期単位
    const char* DELIMITTER = "¥t";
    int limit = RULE_MALLOC_UNIT;
    int size = 0;
    char buf[BUFSIZ];
    char token[BUFSIZ];

    Rule* p = (Rule*) malloc(RULE_MALLOC_UNIT * sizeof(Rule));
    assert(p != NULL);
    *rule = p;

    size = 0;
    while (fgets(buf, BUFSIZ, in) != NULL) {
        size++;
        if (size == limit) {
            limit *= 2;
            realloc(*rule, limit * sizeof(Rule));
            assert(*rule != NULL);
            p = *rule + size - 1;
        }

        strncpy(token, strtok(buf, DELIMITTER), BUFSIZ);
        token[BUFSIZ] = '¥0';
        p->status = atoi(token);

        strncpy(token, strtok(NULL, DELIMITTER), BUFSIZ);
        token[BUFSIZ] = '¥0';
        p->inSymbol = token[0];

        strncpy(token, strtok(NULL, DELIMITTER), BUFSIZ);
        token[BUFSIZ] = '¥0';
        p->outSymbol = token[0];

        strncpy(token, strtok(NULL, DELIMITTER), BUFSIZ);
        token[BUFSIZ] = '¥0';
        p->direction = atoi(token);

        strncpy(token, strtok(NULL, DELIMITTER), BUFSIZ);
        token[BUFSIZ] = '¥0';
        p->nextStatus = atoi(token);

        p++;
    }

    return size;
}

/**
 * 途中経過をプリントする。
 * @param tape テープ
 * @param head ヘッドの位置
 * @param rule ルール
 */
void printProgress(char* tape, int head, Rule* rule) {
    assert(tape != NULL);
    assert(0 <= head && head < BUFSIZ);

    int i;

    printf("%s ", tape);
    printRule(rule);
    printf(" ");

    printlnCount(tape);

    for (i = 0; i < head; i++) {
        printf(" ");
    }
    printf("^");
    fflush(stdout);
}

/**
 * 途中経過をプリントし、改行する。
 * @param tape テープ
 * @param head ヘッドの位置
 * @param rule ルール
 */
void printlnProgress(char* tape, int head, Rule* rule) {
    assert(tape != NULL);
    assert(0 <= head && head < BUFSIZ);

    printProgress(tape, head, rule);
    printf("¥n");
    fflush(stdout);
}

/**
 * 停止状態を取得する。
 * 停止状態は、ルール中の状態の最大値になる。
 * @param rule ルール
 * @param size ルール配列のサイズ
 * @return 停止状態
 */
int getStopStatus(Rule* rule, int size) {
    assert(rule != NULL);
    assert(size > 0);

    int max = rule->nextStatus;
    rule++;
    int i = 0;
    for (i = 1; i < size; i++) {
        if (max < rule->nextStatus) {
            max = rule->nextStatus;
        }
        rule++;
    }
    return max;
}

/**
 * ルールをプリントする。
 * @param rule ルール
 * @param size ルール配列のサイズ
 */
void printRuleTable(Rule* rule, int size) {
    assert(rule != NULL);
    assert(size > 0);

    int i = 0;
    for (i = 0; i < size; i++) {
        printlnRule(rule);
        rule++;
    }
    fflush(stdout);
}

/**
 * ルールをプリントする。
 * @param rule ルール. NULLのときは、空白をプリントする。
 */
void printRule(Rule* rule) {
    if (rule != NULL) {
        printf("[%d, %c, %c, %d, %d]", rule->status, rule->inSymbol,
                rule->outSymbol, rule->direction, rule->nextStatus);
    } else {
        printf("[      -      ]");
    }

    fflush(stdout);
}

/**
 * ルールをプリントし、改行する。
 * @param rule ルール
 */
void printlnRule(Rule* rule) {
    printRule(rule);
    printf("¥n");
    fflush(stdout);
}

/**
 * 記号と状態から合致するルールを見つける
 * @param symbol 記号
 * @param status 状態
 * @param ruleTable ルール表
 * @param size ruleTableの要素数
 * @return 合致したルール
 */
Rule* findRule(char symbol, int status, Rule* ruleTable, int size) {
    assert(symbol == '0' || symbol == '1');
    assert(status >= 0);
    assert(ruleTable != NULL);
    assert(size > 0);

//    printf("* %c %d %d¥n", symbol, status, size); fflush(stdout);

    Rule* rule = NULL;
    for (rule = ruleTable; rule < ruleTable + size; rule++) {
        assert(ruleTable <= rule && rule < ruleTable + size);
        if (symbol == rule->inSymbol && status == rule->status) {
            return rule;
        }
    }

    assert(0);    // 必ず見つかるはず
    return NULL;
}

/**
 * startの位置から、指定された文字cを見つけ、その位置を返す。
 * @param tape テープ
 * @param start 見つけるための開始位置
 * @param c 見つける文字
 * @return 見つかった位置。見つからない場合は-1
 */
int findCharOnTape(char* tape, int start, char c) {
    assert(tape != NULL);
    assert(start >= 0);
    assert(c == '0' || c == '1');

    int i = -1;

    for (i = start; i < strlen(tape); i++) {
        assert(start <= i && i < strlen(tape));
        if (tape[i] == c) {
            return i;
        }
    }

    return -1;
}

/**
 * テープにある連続した1の数をプリントする。
 * @param tape テープ
 */
void printCount(char* tape) {
    assert(tape != NULL);

    int i = -1;
    int start = -1;

    // 1の位置を検索する
    while ((i = findCharOnTape(tape, (i + 1), '1')) != -1) {
        // 次の0の位置
        start = i;
        if ((i = findCharOnTape(tape, (start + 1), '0')) == -1) {
            printf("%d ", (strlen(tape) - start - 1));
            break;
        }

        printf("%d ", (i - start - 1));
    }
    fflush(stdout);
}

/**
 * テープにある連続した1の数をプリントし、改行する。
 * @param tape テープ
 */
void printlnCount(char* tape) {
    assert(tape != NULL);

    printCount(tape);
    printf("¥n");
    fflush(stdout);
}
---


■ 実行


例にあるように、「5 + 3 = 8」を計算する。


□ tape.txt
---
000001111110111100000
---

1 の並んでいる数(から 1 引いたもの)が数値をあらわす。
間の 0 が区切り。


□ rule.txt
---
0    0    0    1    0
0    1    0    1    1
1    0    0    1    3
1    1    0    1    2
2    0    1    1    3
2    1    1    1    2
---

最大の状態が「3」。


□ 実行結果
---
program start
ruleSize = 6
[0, 0, 0, 1, 0]
[0, 1, 0, 1, 1]
[1, 0, 0, 1, 3]
[1, 1, 0, 1, 2]
[2, 0, 1, 1, 3]
[2, 1, 1, 1, 2]
tape = "000001111110111100000"
000001111110111100000 [0, 0, 0, 1, 0] 5 3 
^
000001111110111100000 [0, 0, 0, 1, 0] 5 3 
 ^
000001111110111100000 [0, 0, 0, 1, 0] 5 3 
  ^
000001111110111100000 [0, 0, 0, 1, 0] 5 3 
   ^
000001111110111100000 [0, 0, 0, 1, 0] 5 3 
    ^
000001111110111100000 [0, 1, 0, 1, 1] 5 3 
     ^
000000111110111100000 [1, 1, 0, 1, 2] 4 3 
      ^
000000011110111100000 [2, 1, 1, 1, 2] 3 3 
       ^
000000011110111100000 [2, 1, 1, 1, 2] 3 3 
        ^
000000011110111100000 [2, 1, 1, 1, 2] 3 3 
         ^
000000011110111100000 [2, 1, 1, 1, 2] 3 3 
          ^
000000011110111100000 [2, 0, 1, 1, 3] 3 3 
           ^
000000011111111100000 [      -      ] 8 
            ^
program end
---

                                                                            以上



[C] 日付関係のユーティリティ

2012年06月11日 | Android
[C] 日付関係のユーティリティ
================================================================================

日付関係のユーティリティとして、次のようなものを作った。

関数の概要
---
time_t toSystemTime(char *dateString);          日付文字列をシステム時刻に変換
time_t toStrictSystemTime(char* dateString);    日付文字列をシステム時刻に変換
char* toDateString(char *dateString, time_t systemTime);    
                                                システム時刻を日付文字列に変換
char* toJaWeekString(char *weekString, time_t systemTime) 
                                システム時刻を曜日(日、月、...、土)文字列に変換
int isLeap(int year);                         year 年がうるう年か平年かを返す
int lastMonthDay(int year, int month);        year 年 month 月の最後の日付を返す
---

関数の詳細
---
time_t toSystemTime(char *dateString);

    日付文字列をシステム時刻に変換する。
    日付は、西暦年が 1901 以上、月は 1~12 の範囲、日は 1~31 の範囲内でなければ不
    正な日付とする。

    引数   dateString 日付を表す文字列("YYYYMMDD")

    戻り値 システム時刻(1970年1月1日 0時0分0秒からの経過時間(秒単位))。変換でき
		   ない場合、-1.


time_t toStrictSystemTime(char* dateString);

    日付文字列をシステム時刻に変換する。
    日付は、西暦年が1901以上とするが、厳密に月・日の範囲内でなければ不正な日付と
	する。

    引数   dateString 日付を表す文字列("YYYYMMDD")

    戻り値 システム時刻(1970年1月1日 0時0分0秒からの経過時間(秒単位))。変換でき
		   ない場合、-1.


char* toDateString(char *dateString, time_t systemTime);

    システム時刻を日付文字列に変換する。

    引数   systemTime システム時刻
    引数   dateString 日付を表す文字列を格納する文字列へのポインター

    戻り値 日付を表す文字列("YYYYMMDD")


char* toJaWeekString(char *weekString, time_t systemTime) {

    システム時刻を曜日(日、月、...、土)文字列に変換する。

    引数   systemTime システム時刻
    引数   weekString 曜日を表す文字列を格納する文字列へのポインター

    戻り値 曜日を表す文字列


int isLeap(int year);

    year年がうるう年か平年かを返す。

    引数   year 西暦年。正数を指定する。

    戻り値 うるう年のとき1、平年のとき0


int lastMonthDay(int year, int month);

    year年month月の最後の日付を返す。

    引数   year 西暦年
    引数   month 月。ただし、通常の月より1引いた値を使う(0~11)

    戻り値 year年month月の最後の日付
---


▲ 公開用ヘッダー

□ DateUtil.h
---
/*
 ============================================================================
 Name        : DateUtil.h
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : 日付を扱うユーティリティ(公開用)
 ============================================================================
 */
#ifndef DATEUTIL_H_
#define DATEUTIL_H_

enum {
    YEAR_LENGTH = 4,
    MONTH_LENGTH = 2,
    DAY_LENGTH = 2,
};

/**
 * システム時刻を曜日(日、月、...、土)文字列に変換する。
 * @param systemTime システム時刻
 * @param weekString 曜日を表す文字列を格納する文字列へのポインター
 * @return 曜日を表す文字列
 */
char* toJaWeekString(char *weekString, time_t systemTime);

/**
 * year年がうるう年か平年かを返す。
 * @param year 西暦年。正数を指定する。
 * @return うるう年のとき1、平年のとき0
 */
int isLeap(int year);

/**
 * year年month月の最後の日付を返す。
 * @param year 西暦年
 * @param month 月。ただし、通常の月より1引いた値を使う(0~11)
 * @return year年month月の最後の日付
 */
int lastMonthDay(int year, int month);

/**
 * 日付文字列をシステム時刻に変換する。
 * 日付は、西暦年が1900以上とするが、厳密に月・日の範囲内でなければ不正な日付と
する。
 * @param dateString 日付を表す文字列("YYYYMMDD")
 * @return システム時刻(1970年1月1日 0時0分0秒からの経過時間(秒単位))。変換でき
ない場合、-1.
 */
time_t toStrictSystemTime(char* dateString);

/**
 * 日付文字列をシステム時刻に変換する。
 * 日付は、西暦年が1901以上、月は1~12の範囲、日は1~31の範囲内でなければ不正な日
付とする。
 * @param dateString 日付を表す文字列("YYYYMMDD")
 * @return システム時刻(1970年1月1日 0時0分0秒からの経過時間(秒単位))。変換でき
ない場合、-1.
 */
time_t toSystemTime(char *dateString);

/**
 * システム時刻を日付文字列に変換する。
 * @param systemTime システム時刻
 * @param dateString 日付を表す文字列を格納する文字列へのポインター
 * @return 日付を表す文字列("YYYYMMDD")
 */
char* toDateString(char *dateString, time_t systemTime);

#endif /* DATEUTIL_H_ */
----


▲ 非公開用ヘッダー

内部的に使うだけの関数の定義。
リトルエンディアンを意識するのはここだけ。

□ DateUtilInner.h
---
/*
 ============================================================================
 Name        : DateUtilInner.h
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : 日付を扱うユーティリティ(非公開用)
 ============================================================================
 */
#ifndef DATE_UTIL_INNER_H_
#define DATE_UTIL_INNER_H_

static struct tm* makeTMStruct(char* weekString, struct tm* workTime);

#endif /* DATE_UTIL_INNER_H_ */
---


▲ モジュール

□ DateUtil.c
---
/*
 ============================================================================
 Name        : DateUtil.c
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : 日付を扱うユーティリティ
 ============================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>

#include "DateUtil.h"
#include "DateUtilInner.h"

/**
 * 日付文字列をシステム時刻に変換する。
 * 日付は、西暦年が1900以上とするが、厳密に月・日の範囲内でなければ不正な日付と
する。
 * @param dateString 日付を表す文字列("YYYYMMDD")
 * @return システム時刻(1970年1月1日 0時0分0秒からの経過時間(秒単位))。変換でき
ない場合、-1.
 */
time_t toStrictSystemTime(char* dateString) {
    struct tm workTime;

    if (strlen(dateString) != (YEAR_LENGTH + MONTH_LENGTH + DAY_LENGTH)) {
        return -1;
    }

    if (makeTMStruct(dateString, &workTime) == NULL) {
        return -1;
    }

    if (workTime.tm_mday > lastMonthDay(workTime.tm_year, workTime.tm_mon)) {
        return -1;
    }

    return (mktime(&workTime));
}

/**
 * 日付文字列をシステム時刻に変換する。
 * 日付は、西暦年が1901以上、月は1~12の範囲、日は1~31の範囲内でなければ不正な日
付とする。
 * @param dateString 日付を表す文字列("YYYYMMDD")
 * @return システム時刻(1970年1月1日 0時0分0秒からの経過時間(秒単位))。変換でき
ない場合、-1.
 */
time_t toSystemTime(char* dateString) {
    struct tm workTime;
    if (makeTMStruct(dateString, &workTime) == NULL) {
        return -1;
    }
    return (mktime(&workTime));
}

struct tm* makeTMStruct(char* weekString, struct tm* workTime) {
    char year[YEAR_LENGTH + 1];
    char month[MONTH_LENGTH + 1];
    char day[DAY_LENGTH + 1];

    if (strlen(weekString) != (YEAR_LENGTH + MONTH_LENGTH + DAY_LENGTH)) {
        return NULL;
    }

    // 年を設定する
    strncpy(year, weekString + 0, YEAR_LENGTH);
    year[YEAR_LENGTH] = '¥0';
    workTime->tm_year = atoi(year) - 1900;
    if (workTime->tm_year <= 0) {
        return NULL;
    }

    // 月を設定する
    strncpy(month, weekString + (YEAR_LENGTH), MONTH_LENGTH);
    month[MONTH_LENGTH] = '¥0';
    workTime->tm_mon = atoi(month) - 1;
    if (workTime->tm_mon < 0 || workTime->tm_mon > 11) {
        return NULL;
    }

    // 日を設定する
    strncpy(day, weekString + (YEAR_LENGTH + MONTH_LENGTH), DAY_LENGTH);
    day[DAY_LENGTH] = '¥0';
    workTime->tm_mday = atoi(day);
    if (workTime->tm_mday < 1 || workTime->tm_mday > 31) {
        return NULL;
    }

    // 時刻を設定する
    workTime->tm_hour = 0;
    workTime->tm_min = 0;
    workTime->tm_sec = 0;
    workTime->tm_isdst = -1;

    return workTime;
}

/**
 * システム時刻を日付文字列に変換する。
 * @param systemTime システム時刻
 * @param dateString 日付を表す文字列を格納する文字列へのポインター
 * @return 日付を表す文字列("YYYYMMDD")
 */
char* toDateString(char *dateString, time_t systemTime) {
    struct tm* time = localtime(&systemTime);
    strftime(dateString, ((YEAR_LENGTH + MONTH_LENGTH + DAY_LENGTH) + 1),
            "%Y%m%d", time);
    return dateString;
}

/**
 * システム時刻を曜日(日、月、...、土)文字列に変換する。
 * @param systemTime システム時刻
 * @param weekString 曜日を表す文字列を格納する文字列へのポインター
 * @return 曜日を表す文字列
 */
char* toJaWeekString(char *weekString, time_t systemTime) {
    const char WEEK[][3] = { "日", "月", "火", "水", "木", "金", "土", "日", };
    struct tm* time = localtime(&systemTime);
    strncpy(weekString, WEEK[time->tm_wday], 2);
    weekString[2] = '¥0';
    return weekString;
}

/**
 * year年がうるう年か平年かを返す。
 * @param year 西暦年。正数を指定する。
 * @return うるう年のとき1、平年のとき0
 */
int isLeap(int year) {
    assert(year > 0);
    return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0));
}

/**
 * year年month月の最後の日付を返す。
 * @param year 西暦年
 * @param month 月。ただし、通常の月より1引いた値を使う(0~11)
 * @return year年month月の最後の日付
 */
int lastMonthDay(int year, int month) {
    const int LAST_DAYS[][2] = { { 31, 31 }, // 1
            { 28, 29 }, // 2
            { 31, 31 }, // 3
            { 30, 30 }, // 4
            { 31, 31 }, // 5
            { 30, 30 }, // 6
            { 31, 31 }, // 7
            { 31, 31 }, // 8
            { 30, 30 }, // 9
            { 31, 31 }, // 10
            { 30, 30 }, // 11
            { 31, 31 }, // 12
            };

    return LAST_DAYS[month][isLeap(year)];
}
---


▲ テスト

上記の関数を確認するためのテスト・プログラム。
数字、ひらがな、かたかなの限界値をチェックする。
また、リトルエンディアン前提なので、バイト順が逆転している場合に範囲になるかどう
かもチェックする。


□ DateUtilTest.c
---
/*
 ============================================================================
 Name        : DateUtilTest.c
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : 日付を扱うユーティリティのテスト
 ============================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>

#include "DateUtil.h"

int main(void) {
    time_t calendarTime;
    char buffer[100];

    printf("test start¥n");
    fflush(stdout);

    // isLeap()
    assert(isLeap(2000));
    assert(isLeap(2012));
    assert(!isLeap(2013));
    assert(!isLeap(2100));

    // lastMonthDay()
    assert(31 == lastMonthDay(2012, 0));
    assert(29 == lastMonthDay(2012, 1));
    assert(31 == lastMonthDay(2012, 2));
    assert(30 == lastMonthDay(2012, 3));
    assert(31 == lastMonthDay(2012, 4));
    assert(30 == lastMonthDay(2012, 5));
    assert(31 == lastMonthDay(2012, 6));
    assert(31 == lastMonthDay(2012, 7));
    assert(30 == lastMonthDay(2012, 8));
    assert(31 == lastMonthDay(2012, 9));
    assert(30 == lastMonthDay(2012, 10));
    assert(31 == lastMonthDay(2012, 11));

    assert(31 == lastMonthDay(2013, 0));
    assert(28 == lastMonthDay(2013, 1));
    assert(31 == lastMonthDay(2013, 2));
    assert(30 == lastMonthDay(2013, 3));
    assert(31 == lastMonthDay(2013, 4));
    assert(30 == lastMonthDay(2013, 5));
    assert(31 == lastMonthDay(2013, 6));
    assert(31 == lastMonthDay(2013, 7));
    assert(30 == lastMonthDay(2013, 8));
    assert(31 == lastMonthDay(2013, 9));
    assert(30 == lastMonthDay(2013, 10));
    assert(31 == lastMonthDay(2013, 11));

    // 本日
    printf("%s¥n", toDateString(buffer, time(NULL)));
    printf("%s¥n", toJaWeekString(buffer, time(NULL)));

    // toCalendarTime(), toStrictCalendarTime(), toDateString(), toJaWeekString
()
    // 1. 平年
    strcpy(buffer, "20100228");
    calendarTime = toSystemTime(buffer);
    assert(strcmp("20100228", toDateString(buffer, calendarTime)) == 0);
    assert(strcmp("日", toJaWeekString(buffer, calendarTime)) == 0);

    // 2. 平年(厳密にはエラー)
    strcpy(buffer, "20100229");
    calendarTime = toSystemTime(buffer);
    assert(strcmp("20100301", toDateString(buffer, calendarTime)) == 0);
    assert(strcmp("月", toJaWeekString(buffer, calendarTime)) == 0);

    // 3. 平年
    strcpy(buffer, "20100228");
    calendarTime = toStrictSystemTime(buffer);
    assert(strcmp("20100228", toDateString(buffer, calendarTime)) == 0);
    assert(strcmp("日", toJaWeekString(buffer, calendarTime)) == 0);

    // 4. 平年(エラー)
    strcpy(buffer, "20100229");
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    // 5. うるう年
    strcpy(buffer, "20120229");
    calendarTime = toSystemTime(buffer);
    assert(strcmp("20120229", toDateString(buffer, calendarTime)) == 0);
    assert(strcmp("水", toJaWeekString(buffer, calendarTime)) == 0);

    // 6. うるう年(厳密にはエラー)
    strcpy(buffer, "20120230");
    calendarTime = toSystemTime(buffer);
    assert(strcmp("20120301", toDateString(buffer, calendarTime)) == 0);
    assert(strcmp("木", toJaWeekString(buffer, calendarTime)) == 0);

    // 7. うるう年
    strcpy(buffer, "20120229");
    calendarTime = toStrictSystemTime(buffer);
    assert(strcmp("20120229", toDateString(buffer, calendarTime)) == 0);
    assert(strcmp("水", toJaWeekString(buffer, calendarTime)) == 0);

    // 8. 平うるう年(エラー)
    strcpy(buffer, "20120230");
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    // 9. 日付不正(1900年)
    strcpy(buffer, "19000101");
    calendarTime = toSystemTime(buffer);
    assert(calendarTime == -1);
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    // 10. 日付不正(0月)
    strcpy(buffer, "20000010");
    calendarTime = toSystemTime(buffer);
    assert(calendarTime == -1);
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    // 11. 日付不正(32日)
    strcpy(buffer, "20120532");
    calendarTime = toSystemTime(buffer);
    assert(calendarTime == -1);
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    // 12. 日付不正(9文字)
    strcpy(buffer, "201205301");
    calendarTime = toSystemTime(buffer);
    assert(calendarTime == -1);
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    // 13. 日付不正(7文字)
    strcpy(buffer, "2012053");
    calendarTime = toSystemTime(buffer);
    assert(calendarTime == -1);
    calendarTime = toStrictSystemTime(buffer);
    assert(calendarTime == -1);

    printf("test end¥n");
    fflush(stdout);

    return 0;
}
---

                                                                            以上


[C][SJIS] Shift_JIS 文字関係のユーティリティ

2012年06月10日 | C / C++
[C][SJIS] Shift_JIS 文字関係のユーティリティ
================================================================================

Shift_JIS 文字関係のユーティリティとして、次のようなものを作った。
ただし、リトル・エンディアン限定。

関数の概要
---
int stoi(const char* s);              日本語の数字列を数値(int)に変換する。
int sjisToDigit(const char* s);       日本語の数字を数値(int)に変換する。
int isSjisDigit(const char* s);       数字("0"~"9")か判断する。
int isSjisHiragana(const char* s);    ひらがなか判断する。
int isSjisKatakana(const char* s);    カタカナか判断する。
---

関数の詳細
---
int stoi(const char* s);

    引数sで与えられた Shift_JIS で書かれた日本語の数字列を数値(int)に変換する。
    この関数は、標準関数 atoi() の Shift_JIS 版であるが、負値を扱えない。
    数字列は Shift_JIS の数字以外は記述できない。Shift_JIS の数字以外が出てきた
    時点で変換をやめる。

    引数    s  Shift_JIS で書かれた日本語の数字列

    戻り値  Shift_JIS で書かれた日本語の数字列を変換した数値


int sjisToDigit(const char* s);

    引数sで与えられた Shift_JIS で書かれた日本語の数字を数値(int)に変換する。
    この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだ
    けで判断する。

    引数    s  Shift_JIS で書かれた日本語の数字

    戻り値  Shift_JIS で書かれた日本語の数字を変換した数値。変換できない場合は -1


int isSjisDigit(const char* s);

    引数sで与えられた Shift_JIS で書かれた日本語が数字("0"~"9")か判断する。
    この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだ
    けで判断する。
     Shift_JIS の"0"~"9"は、0x824f ~ 0x8258.

    引数   s  Shift_JIS で書かれた日本語の数字

    戻り値 数値であれば 1.そうでなければ 0


int isSjisHiragana(const char* s);

    引数sで与えられた Shift_JIS で書かれた日本語がひらがなか判断する。
    この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだ
	けで判断する。
    Shift_JIS のひらがなは、0x829F ~ 0x82F1.

    引数   s  Shift_JIS で書かれた日本語文字列

    戻り値 ひらがなであれば 1.そうでなければ 0


int isSjisKatakana(const char* s);

    引数 s で与えられた Shift_JIS で書かれた日本語がカタカナか判断する。
    この数字は、C言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだ
	けで判断する。
    Shift_JIS のカタカナは、0x829F ~ 0x82F1.

    引数   s  Shift_JIS で書かれた日本語文字列

    戻り値 カタカナであれば 1.そうでなければ 0
---



▲ 公開用ヘッダー

□ sjisUtil.h
---
/*
 ============================================================================
 Name        : sjisUtil.h
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : リトルエンエンディアンの Shift_JIS 関係のユーティリティ(公開用)
 ============================================================================
 */

#ifndef SJIS_UTIL_H_
#define SJIS_UTIL_H_

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語がひらがなか判断する。
 * この数字は、C言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * Shift_JIS のひらがなは、0x829F ~ 0x82F1.
 * @param s  Shift_JIS で書かれた日本語文字列
 * @return ひらがなであれば 1.そうでなければ 0
 */
int isSjisHiragana(const char* s);

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語がカタカナか判断する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * Shift_JIS のカタカナは、0x829F ~ 0x82F1.
 * @param s  Shift_JIS で書かれた日本語文字列
 * @return カタカナであれば 1.そうでなければ 0
 */
int isSjisKatakana(const char* s);

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語の数字列を数値(int)に変換する。
 * この関数は、標準関数 atoi() の Shift_JIS 版であるが、負値を扱えない。
 * 数字列は Shift_JIS の数字以外は記述できない。 Shift_JIS の数字以外が出てきた時点で変換をやめる。
 * @param s  Shift_JIS で書かれた日本語の数字列
 * @return  Shift_JIS で書かれた日本語の数字列を変換した数値
 */
int stoi(const char* s);

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語の数字を数値(int)に変換する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * @param s  Shift_JIS で書かれた日本語の数字
 * @return  Shift_JIS で書かれた日本語の数字を変換した数値。変換できない場合は-1
 */
int sjisToDigit(const char* s);

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語が数字("0"~"9")か判断する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * Shift_JIS の"0"~"9"は、0x824f ~ 0x8258.
 * @param s  Shift_JIS で書かれた日本語の数字
 * @return 数値であれば 1.そうでなければ 0
 */
int isSjisDigit(const char* s);

#endif /* SJIS_UTIL_H_ */
----


▲ 非公開用ヘッダー

内部的に使うだけの関数の定義。
リトルエンディアンを意識するのはここだけ。

□ sjisUtilInner.h
---
/*
 ============================================================================
 Name        : sjisUtilInner.h
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : リトルエンエンディアンの Shift_JIS 関係のユーティリティ(内部用)
 ============================================================================
 */

#ifndef SJIS_UTIL_INNER_H_
#define SJIS_UTIL_INNER_H_

/**
 * 2 バイト文字 s が 2 バイト文字 start と end の間に入っているか判断する。
 * この 2 バイト文字は、C 言語の文字列の要件を満たす必要はなく、それぞれの引数からの 2 バイトだけで判断する。
 * @param s  Shift_JIS で書かれた 2 バイト文字
 * @param start  Shift_JIS で書かれた 2 バイト文字で、検査する範囲の始端
 * @param end  Shift_JIS で書かれた 2 バイト文字で、検査する範囲の終端
 * @return 範囲内であれば 1.そうでなければ 0
 */
static int inRange2byteChar(const char* s, const char* start, const char* end);

#endif /* SJIS_UTIL_INNER_H_ */
---


▲ モジュール

□ sjisUtil.c
---
/*
 ============================================================================
 Name        : sjisUtil.c
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : リトルエンエンディアンの Shift_JIS 関係のユーティリティ
 ============================================================================
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sjisUtil.h"
#include "sjisUtilInner.h"

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語の数字列を数値(int)に変換する。
 * この関数は、標準関数 atoi() の Shift_JIS 版であるが、負値を扱えない。
 * 数字列は Shift_JIS の数字以外は記述できない。 Shift_JIS の数字以外が出てきた時点で変換をやめる。
 * @param s  Shift_JIS で書かれた日本語の数字列
 * @return  Shift_JIS で書かれた日本語の数字列を変換した数値
 */
int stoi(const char* s) {
    int num = 0;
    int i;

    for (i = 0; i < strlen(s); i += 2) {
        int digit = 0;
        if ((digit = sjisToDigit(&s[i])) == -1) {
            break;
        }
        num = num * 10 + digit;
    }

    return num;
}

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語の数字を数値(int)に変換する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * @param s  Shift_JIS で書かれた日本語の数字
 * @return  Shift_JIS で書かれた日本語の数字を変換した数値。変換できない場合は -1
 */
int sjisToDigit(const char* s) {
    if (!isSjisDigit(s)) {
        return -1;
    }

    return s[1] - 0x4f;
}

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語が数字("0"~"9")か判断する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * Shift_JIS の"0"~"9"は、0x824f ~ 0x8258.
 * @param s  Shift_JIS で書かれた日本語の数字
 * @return 数値であれば 1.そうでなければ 0
 */
int isSjisDigit(const char* s) {
    return inRange2byteChar(s, "0", "9");
}

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語がひらがなか判断する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * Shift_JIS のひらがなは、0x829F ~ 0x82F1. 長音(ー)もひらがなとする。
 * @param s  Shift_JIS で書かれた日本語文字列
 * @return ひらがなであれば 1.そうでなければ 0
 */
int isSjisHiragana(const char* s) {
    return inRange2byteChar(s, "ぁ", "ん") || inRange2byteChar(s, "ー", "ー");
}

/**
 * 引数 s で与えられた Shift_JIS で書かれた日本語がカタカナか判断する。
 * この数字は、C 言語の文字列の要件を満たす必要はなく、引数 s からの 2 バイトだけで判断する。
 * Shift_JIS のカタカナは、0x829F ~ 0x82F1. 長音(ー)もカタカナとする。
 * @param s  Shift_JIS で書かれた日本語文字列
 * @return カタカナであれば 1.そうでなければ 0
 */
int isSjisKatakana(const char* s) {
    return inRange2byteChar(s, "ァ", "ヶ") || inRange2byteChar(s, "ー", "ー");
}

/**
 * 2 バイト文字sが 2 バイト文字startとendの間に入っているか判断する。
 * この 2 バイト文字は、C 言語の文字列の要件を満たす必要はなく、それぞれの引数からの 2 バイトだけで判断する。
 * @param s  Shift_JIS で書かれた 2 バイト文字
 * @param start  Shift_JIS で書かれた 2 バイト文字で、検査する範囲の始端
 * @param end  Shift_JIS で書かれた 2 バイト文字で、検査する範囲の終端
 * @return 範囲内であれば 1.そうでなければ 0
 */
int inRange2byteChar(const char* s, const char* start, const char* end) {
    typedef union {
        unsigned char str[2];
        unsigned short num;
    } DoubleByteChar;

    DoubleByteChar value;
    DoubleByteChar startValue;
    DoubleByteChar endValue;

    value.str[0] = s[1];
    value.str[1] = s[0];

    startValue.str[0] = start[1];
    startValue.str[1] = start[0];

    endValue.str[0] = end[1];
    endValue.str[1] = end[0];

    return (startValue.num <= value.num) && (value.num <= endValue.num);
}
---


▲ テスト

上記の関数を確認するためのテスト・プログラム。
数字、ひらがな、かたかなの限界値をチェックする。
また、リトルエンディアン前提なので、バイト順が逆転している場合に範囲になるかどう
かもチェックする。


□ test.c
---
/*
 ============================================================================
 Name        : test.c
 Author      : marunomaruno
 Version     : V1.0, 2012-06-08
 Copyright   : marunomaruno
 Description : リトルエンエンディアンの Shift_JIS 関係のユーティリティのテスト・
プログラム
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "sjisUtil.h"

int main(void) {
    printf("test start\n");

    char hiragana[][3] = { "あ", "い", "か", "さ", "た", "や", "ら", "わ", "っ",
 "ん",
            "ー", };
    char katakana[][3] = { "ア", "イ", "ウ", "サ", "タ", "ヤ", "ラ", "ワ", "ッ",
 "ン",
            "ー", };
    char num[][3] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
 };
    char other[][3] = { { 0x4e, 0x82, '0' }, { 0x59, 0x82, '0' }, { 0x9e, 0x82,
            '0' }, { 0xf2, 0x82, '0' }, { 0xfc, 0x82, '0' },
            { 0x97, 0x823, '0' }, "氓", "A", "一", "所", };
    int i;

    // 長音も含め、ひらがな、カタカナ、数字
    for (i = 0; i < 11; i++) {
        assert(isSjisHiragana(hiragana[i]));
    }

    for (i = 0; i < 11; i++) {
        assert(isSjisKatakana(katakana[i]));
    }

    for (i = 0; i < 10; i++) {
        assert(isSjisDigit(num[i]));
    }

    // 長音
    assert(isSjisKatakana("ー"));
    assert(isSjisHiragana("ー"));

    // ひらがな、カタカナ、数字でない
    for (i = 0; i < 10; i++) {
        assert(!isSjisHiragana(katakana[i]));
        assert(!isSjisHiragana(num[i]));
        assert(!isSjisHiragana(other[i]));
    }

    for (i = 0; i < 10; i++) {
        assert(!isSjisKatakana(hiragana[i]));
        assert(!isSjisKatakana(num[i]));
        assert(!isSjisKatakana(other[i]));
    }

    for (i = 0; i < 10; i++) {
        assert(!isSjisDigit(hiragana[i]));
        assert(!isSjisDigit(katakana[i]));
        assert(!isSjisDigit(other[i]));
    }

    // 全角数字を数値に変換
    for (i = 0; i < 10; i++) {
        assert(i == sjisToDigit(num[i]));
    }

    // 全角数字を数値に変換
    assert(1234 == stoi("1234"));
    assert(1234567890 == stoi("1234567890"));
    assert(0 == stoi("01234567890"));
    assert(123456 == stoi("12345607890"));

    // 文字列としての検査
    char* s1;
    s1 = "あいうえおー";
    for (i = 0; i < strlen(s1); i += 2) {
        if (!isSjisHiragana(&s1[i])) {
            assert(0);
        }
    }
    assert(1);

    s1 = "あいうえおー1";
    for (i = 0; i < strlen(s1); i += 2) {
        if (!isSjisHiragana(&s1[i])) {
            assert(1);
        }
    }
    assert(i >= strlen(s1));

    s1 = "あいうえおーア";
    for (i = 0; i < strlen(s1); i += 2) {
        if (!isSjisHiragana(&s1[i])) {
            assert(1);
        }
    }
    assert(i >= strlen(s1));

    s1 = "アイウエオー";
    for (i = 0; i < strlen(s1); i += 2) {
        if (!isSjisKatakana(&s1[i])) {
            assert(0);
        }
    }
    assert(1);

    s1 = "アイウエオー1";
    for (i = 0; i < strlen(s1); i += 2) {
        if (!isSjisKatakana(&s1[i])) {
            assert(1);
        }
    }
    assert(i >= strlen(s1));

    s1 = "アイウエオーあいうえおア";
    for (i = 0; i < strlen(s1); i += 2) {
        if (!isSjisKatakana(&s1[i])) {
            assert(1);
        }
    }
    assert(i >= strlen(s1));

    printf("test end\n");
    return EXIT_SUCCESS;
}
---

                                                                            以上


[leJOS][NXT] Bluetooth を使った送受信

2012年05月11日 | LEGO
[leJOS][NXT] Bluetooth を使った送受信
================================================================================

※ このサンプルは leJOS 0.9.1 で作成している。

Bluetooth を使って、データを送受信する。
プログラムは以下の 3 つを作った。

Sender11      リモートデバイス側。NXT のセンサーから値を読んで、Bluetooth を使っ
              て相手(Receiver11)に送信する

Receiver11    ローカルデバイス側。Sender11 から送られてきたデータを受信して、画
              面に表示する。

SensorValues  エンティティ。送受信時に使う NXT のセンサーの値を保持する。

Bluetooth では、データを送受信するときに、リモートデバイス側とローカルデバイス側
とで通信をする。このとき、リモートデバイス側のプログラム(Sender11)を先に起動して
おく。


■ リモートデバイス側

NXT の Esacape ボタン以外を押下したときに、各センサーの値を読み取って、相手の 
NXT に送信する。Esacape ボタンを押下したときは、プログラムを終了する。


□ Sender11.java
---
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import lejos.nxt.Button;
import lejos.nxt.LCD;
import lejos.nxt.LightSensor;
import lejos.nxt.Motor;
import lejos.nxt.SensorPort;
import lejos.nxt.SoundSensor;
import lejos.nxt.TouchSensor;
import lejos.nxt.UltrasonicSensor;
import lejos.nxt.comm.Bluetooth;
import lejos.nxt.comm.NXTConnection;

/**
 * NXTのセンサーからデータを取得し、Bluetoothを使って値を送信する。
 * リモートデバイス側(先にこちらのプログラムを起動しておく)
 * leJOS 0.9.1
 * @author marunomaruno
 * @version 1.0, 2012/03/14
 * @since 1.0
 */
public class Sender11 {
    public static void main(String[] args) throws Exception {
        // センサーを設定する
        TouchSensor touchSensor = new TouchSensor(SensorPort.S1);
        SoundSensor soundSensor = new SoundSensor(SensorPort.S2);
        LightSensor lightSensor = new LightSensor(SensorPort.S3);
        UltrasonicSensor ultrasonicSensor = new UltrasonicSensor(SensorPort.S4);

        // センサーが安定するまで待つ
        Thread.sleep(200);

        // ローカルデバイスとの通信を待つ
        LCD.drawString("Waiting...", 0, 0);
        NXTConnection connection = Bluetooth.waitForConnection();    // (1)

        // 接続できない場合、プログラムを終了する
        if (connection == null) {    // (2)
            LCD.clear();
            LCD.drawString("Connect fail", 0, 0);
            Button.waitForAnyPress();
            System.exit(1);
        }

        // ローカルにもログをとっておく
        File file = new File("values.dat");
        file.delete();
        file.createNewFile();
        DataOutputStream log = new DataOutputStream(new FileOutputStream(
                file));

        // Bluetooth 用のデータストリームを取得する
        DataOutputStream out = connection.openDataOutputStream();    // (3)

        // センサー値を保持するオブジェクトを確保する
        SensorValues values = new SensorValues();    // (4)

        // ESCが押されたら終わり
        while (Button.waitForAnyPress() != Button.ID_ESCAPE) {
            try {
                // IDをカウントアップする
                values.id++;

                // センサーから値を取得する
                values.buttonId = Button.readButtons();
                values.touch = touchSensor.isPressed() ? 1 : 0;
                values.sound = soundSensor.readValue();
                values.light = lightSensor.readValue();
                values.ultrasonic = ultrasonicSensor.getDistance();

                values.tachoCountA = Motor.A.getTachoCount();
                values.tachoCountB = Motor.B.getTachoCount();
                values.tachoCountC = Motor.C.getTachoCount();

                // データを画面に表示する
                LCD.clear();
                values.print(System.out);

                // ログファイルに出力する
                values.write(log);

                // Bluetoothに出力する
                values.write(out);    // (5)

            } catch (IOException e) {
                System.out.println(" write error " + e);
            }
        }

        // ログファイルを閉じる
        try {
            log.close();

        } catch (IOException e) {
            LCD.drawString("Close Exception", 0, 0);
            LCD.drawString(e.getMessage(), 0, 1);
            Button.waitForAnyPress();
        }

        // 通信を閉じる
        try {
            LCD.clear();
            LCD.drawString("Closing... ", 0, 0);
            out.close();    // (6)
            connection.close();    // (7)

        } catch (IOException e) {
            LCD.drawString("Close Exception", 0, 0);
            LCD.drawString(e.getMessage(), 0, 1);
            Button.waitForAnyPress();
        }

        // 終了メッセージを表示する
        LCD.clear();
        LCD.drawString("Finished", 0, 0);
        Button.waitForAnyPress();
    }
}
----

(1) ローカルデバイスとの通信を待つ

Bluetooth クラス の waitForConnection() メソッドを使って、ローカルデバイスとの通
信を待つ。通信があれば、待ち状態は解除され、BTConnection オブジェクトが返る。
なお、NXTConnection は、BTConnection のスーパークラス。

    NXTConnection connection = Bluetooth.waitForConnection();    // (1)

---
static BTConnection waitForConnection() 
          現在のデフォルトの PIN を使って取得する。
---

PIN は、ペアリングするときに使用する 4 桁の数字。デフォルトだと、「0000」または
「1234」。


□ Bluetooth クラス

java.lang.Object
  lejos.nxt.comm.NXTCommDevice
      lejos.nxt.comm.Bluetooth

Bluetooth 通信をあらわすクラス。

メソッド
---
static BTConnection connect(RemoteDevice remoteDevice) 
static BTConnection connect(String target, int mode) 
static BTConnection connect(String target, int mode, byte[] pin) 

static RemoteDevice getKnownDevice(String fName) 
static ArrayList<RemoteDevice> getKnownDevicesList() 

static BTConnection waitForConnection() 
static BTConnection waitForConnection(int timeout, int mode) 
static BTConnection waitForConnection(int timeout, int mode, byte[] pin) 
---


□ BTConnection クラス

java.lang.Object
  extended by lejos.nxt.comm.NXTConnection
      extended by lejos.nxt.comm.BTConnection

Bluetooth の接続をあらわすクラス。つぎに示す NXTConnection クラスを継承している。

メソッド
---
void  closeStream()
int   getSignalStrength()
void  openStream()
void  setActiveMode(int mode)
---


□ NXTConnection クラス

java.lang.Object
  lejos.nxt.comm.NXTConnection

一般的な NXT leJOS の接続を表すクラス。


メソッド
---
void             close() 
DataInputStream  openDataInputStream() 
DataOutputStream openDataOutputStream() 
InputStream      openInputStream() 
OutputStream     openOutputStream() 
---


(2) 接続できない場合、プログラムを終了する

Bluetooth.waitForConnection() メソッドで、接続ができない場合は、null が返るので、
これをチェックする。

    if (connection == null) {    // (2)


(3) Bluetooth 用のデータストリームを取得する

このプログラムは、センサーから読み取ったデータを相手に送信する側なので、出力スト
リームを開く。

    DataOutputStream out = connection.openDataOutputStream();    // (3)


(4) センサー値を保持するオブジェクトを確保する

NXT のセンサーからの値は、SensorValues オブジェクトとして生成する。

    SensorValues values = new SensorValues();    // (4)


(5) Bluetooth に出力する

SensorValues オブジェクトの write() メソッドに、Bluetooth の出力ストリーム・オブ
ジェクトを渡して書き込む。

    values.write(out);    // (5)


(6)(7) 通信を閉じる

通信が終わったら、出力ストリーム、接続ともに閉じる。

    out.close();    // (6)
    connection.close();    // (7)


■ ローカルデバイス側

Bluetooth を使って送られてきたデータを受信して、その値を表示するプログラム。
リモートデバイス側の名前を指定して、相手を特定する。


□ Receiver11.java
---
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.bluetooth.RemoteDevice;

import lejos.nxt.Button;
import lejos.nxt.LCD;
import lejos.nxt.comm.Bluetooth;
import lejos.nxt.comm.NXTConnection;

/**
 * Bluetoothを使って送られてきたデータを受信して、その値を表示する。
 * ローカルデバイス側(先に、リモートデバイス側を起動しておく)
 * leJOS 0.9.1
 * @author marunomaruno
 * @version 1.0, 2011/08/14
 * @since 1.0
 */
public class Receiver11 {
    public static void main(String[] args) throws Exception {

        // リモートデバイスと接続する
        LCD.drawString("Connecting...", 0, 0);

        // リモートデバイスのNXT名を設定する
        RemoteDevice device = Bluetooth.getKnownDevice("NXT名");    // (1)

        // リモートデバイスが見つからない場合、プログラムを終了する
        if (device == null) {
            LCD.clear();
            LCD.drawString("No such device", 0, 0);
            Button.waitForAnyPress();
            System.exit(1);
        }

        // 接続する
        NXTConnection connection = Bluetooth.connect(device);    // (2)

        // 接続できない場合、プログラムを終了する
        if (connection == null) {
            LCD.clear();
            LCD.drawString("Connect fail", 0, 0);
            Button.waitForAnyPress();
            System.exit(1);
        }

        // データストリームを開く
        DataInputStream in = connection.openDataInputStream();    // (3)

        // ローカルにもログをとっておく
        File file = new File("values.dat");
        file.delete();
        file.createNewFile();
        DataOutputStream log = new DataOutputStream(new FileOutputStream(file));

        SensorValues values = new SensorValues();

        // メッセージを受け取る
        try {
            while (Button.ESCAPE.isDown() != true) {
                // ストリームから値を読み込む
                values.read(in);    // (4)

                // ログファイルに出力する
                values.write(log);

                // データを画面に表示する
                LCD.clear();
                values.print(System.out);

                // 待つ
                Thread.sleep(200);
            }

        } catch (IOException e) {
            System.out.println(" write error " + e);
        }

        // ログファイルを閉じる
        try {
            log.close();

        } catch (IOException e) {
            LCD.drawString("Close Exception", 0, 0);
            LCD.drawString(e.getMessage(), 0, 1);
            Button.waitForAnyPress();
        }

        // 通信を閉じる
        try {
            LCD.clear();
            LCD.drawString("Closing...", 0, 0);
            in.close();
            connection.close();

        } catch (IOException e) {
            LCD.drawString("Close Exception", 0, 0);
            LCD.drawString(e.getMessage(), 0, 1);
            Button.waitForAnyPress();
        }

        LCD.clear();
        LCD.drawString("Finished", 0, 0);
        Button.waitForAnyPress();
    }

}
---

(1) リモートデバイスのNXT名を設定する

Bluetooth クラスの getKnownDevice() メソッドを使って、相手のデバイス名を指定して、
接続先のデバイスを取得する。

    RemoteDevice device = Bluetooth.getKnownDevice("NXT名");    // (1)

リモートデバイスが見つからない場合は、null が返る。


Bluetooth クラスのメソッド
---
static RemoteDevice getKnownDevice(String fName) 
static ArrayList<RemoteDevice> getKnownDevicesList() 
---


□ RemoteDevice クラス

java.lang.Object
  javax.bluetooth.RemoteDevice

リモートデバイスをあらわすクラス。


主なメソッド
---
 String  getBluetoothAddress() 
 String  getDeviceAddr() 
 int     getDeviceClass() 
 String  getFriendlyName(boolean alwaysAsk) 
static RemoteDevice getRemoteDevice(Connection conn) 
 boolean isAuthenticated() 
 boolean isEncrypted() 
 void    setDeviceAddr(String deviceAddr) 
 void    setFriendlyName(String fName) 
---


(2) 接続する

    NXTConnection connection = Bluetooth.connect(device);    // (2)

接続できない場合は、null が返る。


Bluetooth クラスのメソッド
---
static BTConnection connect(RemoteDevice remoteDevice) 
static BTConnection connect(String target, int mode) 
static BTConnection connect(String target, int mode, byte[] pin) 
---


(3) データストリームを開く

このプログラムは、Bluetooth から受信した値を画面に表示するので、入力ストリームを
開く。

    DataInputStream in = connection.openDataInputStream();    // (3)


(4) ストリームから値を読み込む

SensorValues オブジェクトの read() メソッドに、Bluetooth の入力ストリーム・オブ
ジェクトを渡して書き込む。

    values.read(in);    // (4)


■ エンティティ

NXT のセンサーの値を管理するエンティティ・クラス。
入力ストリームや出力ストリームのオブジェクトを受け取ることで、そのストリームに対
する入出力も行う。
なお、データの値は int 値として送受信している。

管理している値はつぎのとおり。
    ------------- ------------------------
    フィールド名  意味
    ------------- ------------------------
    id            ID
    buttonId      ボタンのID
    touch         タッチセンサーの値
    sound         音センサーの値
    light         光センサーの値
    ultrasonic    超音波センサーの値
    tachoCountA   モーターAのタコカウント
    tachoCountB   モーターBのタコカウント
    tachoCountC   モーターCのタコカウント
    ------------- ------------------------


□ SensorValues.java
---
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintStream;

/**
 * NXTのセンサーの値を保持するエンティティ
 * leJOS 0.9.1, Android 2.2
 * @author marunomaruno
 * @version 1.0, 2012/03/14
 * @since 1.0
 */
public class SensorValues {

    int id;    // ID

    int buttonId;    // ボタンのID
    int touch;    // タッチセンサーの値
    int sound;    // 音センサーの値
    int light;    // 光センサーの値
    int ultrasonic;    // 超音波センサーの値

    int tachoCountA;    // モーターAのタコカウント
    int tachoCountB;    // モーターBのタコカウント
    int tachoCountC;    // モーターCのタコカウント

    /**
     * 値を表示する。
     * @param out 表示するストリーム
     */
    public void print(PrintStream out) {
        out.println(id);
        out.println(toSensorString());
        out.println(toMotorString());
    }

    /**
     * データを入力する。
     * @param in 入力ストリーム
     * @throws IOException
     */
    public void read(DataInputStream in) throws IOException {
        id = in.readInt();

        buttonId = in.readInt();
        touch = in.readInt();
        sound = in.readInt();
        light = in.readInt();
        ultrasonic = in.readInt();

        tachoCountA = in.readInt();
        tachoCountB = in.readInt();
        tachoCountC = in.readInt();
    }

    /**
     * データを出力する。
     * @param out 出力ストリーム
     * @throws IOException
     */
    public void write(DataOutputStream out) throws IOException {
        out.writeInt(id);

        out.writeInt(buttonId);
        out.writeInt(touch);
        out.writeInt(sound);
        out.writeInt(light);
        out.writeInt(ultrasonic);

        out.writeInt(tachoCountA);
        out.writeInt(tachoCountB);
        out.writeInt(tachoCountC);

        out.flush();
    }

    /**
     * すべてのセンサーの値からなる文字列を組み立てる。
     * @return 組み立てた文字列
     */
    public String toSensorString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        sb.append(buttonId);
        sb.append(", ");
        sb.append(touch);
        sb.append(", ");
        sb.append(sound);
        sb.append(", ");
        sb.append(light);
        sb.append(", ");
        sb.append(ultrasonic);
        sb.append("]");

        return sb.toString();
    }

    /**
     * すべてのモーターの値からなる文字列を組み立てる。
     * @return 組み立てた文字列
     */
    public String toMotorString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        sb.append(toMotorString(new int[]{tachoCountA}));
        sb.append(", ");
        sb.append(toMotorString(new int[]{tachoCountB}));
        sb.append(", ");
        sb.append(toMotorString(new int[]{tachoCountC}));
        sb.append("]");

        return sb.toString();
    }

    /**
     * 指定されたモーター値の配列からなる文字列を組み立てる。
     * @param values 値の配列
     * @return 組み立てた文字列
     */
    public String toMotorString(int[] values) {
        if (values.length == 0) {
            return "[]";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("[");
        sb.append(values[0]);
        for (int i = 1; i < values.length; i++) {
            sb.append(", ");
            sb.append(values[i]);
        }
        sb.append("]");

        return sb.toString();
    }

    /*
     * (非 Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(id);

        sb.append("[");
        sb.append(toSensorString());
        sb.append(", ");
        sb.append(toMotorString());
        sb.append("]");

        return sb.toString();
    }
}
---

                                                                            以上


[Android] 丸が自動的に動く

2012年05月10日 | Android
[Android] 丸が自動的に動く
================================================================================

画面上を○が自動的に動くサンプル。
View クラスをオーバーライドしている以外は、比較的単純なサンプル。


▲ レイアウト

View クラスを継承した CanvasView クラスを使っているので、完全限定クラス名(FQCN)
で要素名を記述している。


□ 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" >

    <jp.marunomaruno.android.movingcircle.CanvasView
        android:id="@+id/canvasView"
        android:layout_width="match_parent"
        android:layout_height="400px" />

</LinearLayout>
---


▲ アクティビティ

Eclipse が自動生成するものと変わりなし。

□ MovingCircle01Activity.java
---
package jp.marunomaruno.android.movingcircle;

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

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


▲ ビュー

View クラスを継承したクラスとして作る。
描画する部分は onDraw() メソッドをオーバーライドして作る。
Thread.sleep() メソッドを利用することで、丸の動きの速さを制御している。
これには、1 ~ 10までの乱数を使う。

□ CanvasView.java
---
package jp.marunomaruno.android.movingcircle;

import java.util.Random;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
 * 丸を自動的に動かす。
 *
 * @author marunomaruno
 * @version 1.0, 2012-05-06
 */
public class CanvasView extends View {

    private int cx;
    private int cy;
    private int radius;

    private int orientationX;
    private int orientationY;

    private Paint paint;

    public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CanvasView(Context context) {
        super(context);
        init();
    }

    /**
     * すべてのコンストラクターで共通の処理。
     */
    private void init() {
        paint = new Paint();
        paint.setColor(Color.YELLOW);
        paint.setAntiAlias(true);

        radius = 30;
        cx = radius;
        cy = radius;

        orientationX = randomInt();
        orientationY = randomInt();
    }

    /**
     * 1~10 までの乱数を返す。
     * @return 1~10 までの乱数
     */
    private int randomInt() {
        return new Random().nextInt(10) + 1;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 丸を表示する
        canvas.drawCircle(cx, cy, radius, paint);

        // 丸を指定方向に移動する
        cx += orientationX;
        cy += orientationY;

        // 左右の端にぶつかったら反転する
        if (isBorderX()) {
            orientationX *= -1;
        }

        // 上下の端にぶつかったら反転する
        if (isBorderY()) {
            orientationY *= -1;
        }

        // 指定ミリ秒分ウエイトする
        try {
            Thread.sleep(randomInt());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        invalidate();
    }

    /**
     * 横の端かどうかを判断する。
     * @return 端のときは true
     */
    private boolean isBorderX() {
        return isBorder(cx, getWidth());
    }

    /**
     * たての端かどうかを判断する。
     * @return 端のときは true
     */
    private boolean isBorderY() {
        return isBorder(cy, getHeight());
    }

    /**
     * 端かどうかを判断する。
     * @return 端のときは true
     */
    private boolean isBorder(int v, int border) {
        return (v < radius) || (v > border - radius);
    }
}
---

                                                                            以上


[Android] ○×ゲーム

2012年05月04日 | Android
[Android] ○×ゲーム
================================================================================

○×ゲーム。
LinearLayout、TextView、Button だけでできるサンプル。
コードの解説はとくにない。


▲ レイアウト

LinearLayout、TextView、Button だけでで構成。
3x3 は、LinearLayout をネストさせている。
ボタンは、1~9 までの番号をつける。

□ 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:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/guide"
        android:textAppearance="?android:attr/textAppearanceMedium" />

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/button3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/button4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/button5"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/button6"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/button7"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/button8"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/button9"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClickButtonHandler"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

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

</LinearLayout>
---


▲ アクティビティ

ゲームは、ボードの 3x3 のボタンの配置が
    1 2 3
    4 5 6
    7 8 9
のとき、
    1-2-3, 4-5-6, 7-8-9,
    1-4-7, 2-5-8, 3-6-9,
    1-5-9, 3-5-7 
でがそろった方の勝ち。力技でチェックしている。


□ MaruBatsu01Activity.java
---
package jp.marunomaruno.android.marubatsu;

import java.util.Random;
import jp.marunomaruno.android.marubatsu.R;

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

/**
 * ○×ゲーム
 * @author marunomaruno
 *
 */
public class MaruBatsu01Activity extends Activity {
    public static int SIZE = 3;    // ボードのサイズ

    private static final int[] BOARD = {
        R.id.button1,
        R.id.button2,
        R.id.button3,
        R.id.button4,
        R.id.button5,
        R.id.button6,
        R.id.button7,
        R.id.button8,
        R.id.button9,
    };

    private boolean isFinish = false;    // ゲーム終了ならtrue

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

        initialize();
    }

    /**
     * ボードを初期化する。
     */
    private void initialize() {
        for (int id : BOARD) {
            Button button = (Button) findViewById(id);
            button.setText(R.string.blank);
        }
    }

    public void onClickButtonHandler(View view) {
        // ゲーム終了のときは何もしない
        if (isFinish) {
            return;
        }

        Button button = (Button) view;
        if (button.getText().equals(getString(R.string.blank))) {
            button.setText(R.string.maru);
            if (!complete()) {
                setBatsu();
                complete();
            }
        }
    }

    /**
     * ゲームが完了したかどうかを判断する。
     * @return 完了したらtrue
     */
    private boolean complete() {
        TextView textResult = (TextView) findViewById(R.id.textResult);

        // 1-2-3, 4-5-6, 7-8-9,
        // 1-4-7, 2-5-8, 3-6-9,
        // 1-5-9, 3-5-7 のいずれかが同じなら勝ち
        final int[][] PATTERN = {
                {0, 4, 8},
                {2, 4, 6},
                {0, 1, 2},
                {3, 4, 5},
                {6, 7, 8},
                {0, 3, 6},
                {1, 4, 7},
                {2, 5, 8},
        };

        String winner = null;

        for (int[] pattern : PATTERN) {
            winner = judge(pattern);
            if (winner != null) {
                textResult.setText(winner + getString(R.string.win));
                isFinish = true;
                return true;
            }
        }

        if (isDraw()) {
            textResult.setText(R.string.draw);
            isFinish = true;
        }

        return false;
    }

    /**
     * 引き分けかどうかを判定する。
     * @return 引き分けのときtrue
     */
    private boolean isDraw() {
        int count = 0;
        for (int id : BOARD) {
            Button button = (Button) findViewById(id);
            if (button.getText().equals(getString(R.string.blank))) {
                count++;
            }
        }
        // 空白ブロックが1個以下のときは引き分け
        return count <= 1;
    }

    /**
     * 「×」をランダムに配置する。
     */
    private void setBatsu() {
        int index = 0;
        Random random = new Random();

        do {
            index = random.nextInt(SIZE * SIZE);
        } while (!isBlank(index));
        Button button = (Button) findViewById(BOARD[index]);
        button.setText(R.string.batsu);
    }

    /**
     * 指定されたインデックスのボタンが空白かどうか判断する。
     * @param index インデックス
     * @return 空白のときtrue
     */
    private boolean isBlank(int index) {
        Button button = (Button) findViewById(BOARD[index]);
        return button.getText().equals(getString(R.string.blank));
    }

    /**
     * 指定された組のボタンで、誰が勝ちかを判定する。
     * @param pattern 勝ちパターンのインデックスの組
     * @return 勝ちの人の名前。勝ちがないときはnull
     */
    private String judge(int[] pattern) {
        assert pattern.length == SIZE;
        Button buttonA = (Button) findViewById(BOARD[pattern[0]]);
        Button buttonB = (Button) findViewById(BOARD[pattern[1]]);
        Button buttonC = (Button) findViewById(BOARD[pattern[2]]);
        if (buttonA.getText().equals(buttonB.getText()) &&
                buttonB.getText().equals(buttonC.getText())) {
            if (buttonA.getText().equals(getString(R.string.maru))) {
                return getString(R.string.you);
            } else if (buttonA.getText().equals(getString(R.string.batsu))) {
                return getString(R.string.computer);
            }
        }

        return null;
    }
}
---


▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, MaruBatsu01Activity!</string>
    <string name="app_name">○×(まるばつ)</string>
    <string name="guide">あなたが○です。空いているところをクリックしてください。</string>
    <string name="win">の勝ち</string>
    <string name="draw">引き分け</string>
    <string name="you">あなた</string>
    <string name="computer">アンドロイド君</string>
    <string name="maru">○</string>
    <string name="batsu">×</string>
    <string name="blank"></string>
</resources>
---

                                                                            以上


[Android] インベーダー・ゲームを作る (2) オブジェクト指向

2012年04月21日 | Android
[Android] インベーダー・ゲームを作る (2) オブジェクト指向
================================================================================

[Android] インベーダー・ゲームを作る (1) シンプル
http://blog.goo.ne.jp/marunomarunogoo/d/20120405
のものを、クラスを導入して作る。

ゲームの制御をするのは、CanvasView クラス。
ゲームで出てくるアイテムとしては、インベーダー、砲台、弾だが、それぞれ、Invader、
Gun、Bullet クラスとして定義する。
それぞれは、下記のインターフェースのいくつかを実装する形で作る。
    IDrawable      描画できる項目のインターフェース
    IAttacher      攻撃物のインターフェース
    ITarget        攻撃目標になるアイテムのインターフェース
    IMoveAutomatic 自動で動くアイテムのインターフェース
    IMoveManual    手動で動かすアイテムのインターフェース

クラスの概要(アクティビティを除く。スーパークラス、インターフェースも記述)
---
AbstractItem  インベーダー・ゲーム上のアイテムのスーパークラス。
              IDrawable

Bullet        弾のクラス。
              AbstractItem、IAttacher、IMoveAutomatic

CanvasView    インベーダー・ゲームの制御。

Gun           砲台のクラス。
              AbstractItem、IMoveManual

Invader       インベーダーのクラス
              AbstractItem、ITarget、IMoveAutomatic
---

クラス図
---
IDrawable
   A                                |---|> IAttacher
   |              |----  Bullet  ---|
   |              |                 |---|> IMoveAutomatic
   |              |                   |    
AbstractItem <|---+----  Invader -----+-|> ITarget
                  |
                  |---   Gun     -------|> IMoveManual
---


▲ アクティビティ

□ Invader11Activity.java

※ Invader01Activity.java と同じ


▲ ビュー

表示とあわせて、ゲームをコントロールする。

定数
---
int    BLOCK_SIZE        描画のときのブロックの大きさ
int    DRAWING_INTERVAL  描画間隔(ミリ秒)
int    NOT_VISIBLE       非表示
---

フィールド
---
Bullet    bullet     弾
Gun       gun        砲台
Invader   invader    インベーダー
int       score      得点
TextView  scoreView  得点を表示するビュー
---

メソッド
---
public     TextView  getScoreView() 
                     scoreView を取得する。
public     boolean   onTouchEvent(android.view.MotionEvent event) 
           
public     void      setScoreView(android.widget.TextView scoreView) 
                     scoreView を設定する。
protected  void      onDraw(android.graphics.Canvas canvas) 
           
protected  void      onSizeChanged(int w, int h, int oldw, int oldh) 
           
private    void      clearScreen(android.graphics.Canvas canvas) 
                     canvasをクリアする。
private    void      drawScore(int score) 
                     スコアを表示する。
private    void      init() 
                     すべてのコンストラクターで共通の処理。
---


□ CanvasView.java
---
package jp.marunomaruno.android.invader;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * インベーダー・ゲーム。
 *
 * @author marunomaruno
 * @version 1.0, 2012-03-06
 */
public class CanvasView extends ImageView {

    /**
     * 描画間隔(ミリ秒)
     */
    public static final int DRAWING_INTERVAL = 50;

    /**
     * 得点
     */
    private int score = 0;

    /**
     * 得点を表示するビュー
     */
    private TextView scoreView;

    /**
     * インベーダー
     */
    private Invader invader;

    /**
     * 砲台
     */
    private Gun gun;

    /**
     * 弾
     */
    private Bullet bullet;

    public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CanvasView(Context context) {
        super(context);
        init();
    }

    /**
     * すべてのコンストラクターで共通の処理。
     */
    private void init() {
        score = 0; // 得点

        // インベーダーの生成(画面左上から)
        invader = new Invader(this);

        // 弾の生成(まだ非表示)
        bullet = new Bullet(this);

        System.out.println(invader);
        System.out.println(bullet);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        System.out.printf("onSizeChanged: (%d, %d)%n", w, h);

        // 砲台の設定
        gun = new Gun(this);

        System.out.println(gun);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 画面をクリアする
        clearScreen(canvas);

        // インベーダーを表示する
        invader.draw(canvas);
        invader.resetColor();

        // 弾を表示する
        bullet.draw(canvas);

        // 砲台を表示する
        gun.draw(canvas);

        // インベーダーを指定方向に移動する
        invader.move();

        // 弾を移動する
        bullet.move();

        // 弾が当たったら得点する
        if (invader.isHit(bullet)) {
            score++;
            invader.setColor(Invader.HIT_COLOR);
            bullet.setNotVisible(); // 弾の表示を消す
            // 得点を表示する
            drawScore(score);
        }

        // 指定ミリ秒分ウエイトする
        try {
            Thread.sleep(DRAWING_INTERVAL);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: // 指をタッチした
            assert true; // 何もしない
            break;

        case MotionEvent.ACTION_MOVE: // 指を動かしている
            gun.move((int) event.getX(), gun.y); // 横に動かす
            break;

        case MotionEvent.ACTION_UP: // 指を離した
            bullet = gun.shot();
            break;

        default:
            assert true; // 何もしない
            break;
        }

        invalidate();

        return true;
    }

    /**
     * scoreView を取得する。
     *
     * @return scoreView オブジェクト
     */
    public TextView getScoreView() {
        return scoreView;
    }

    /**
     * scoreView を設定する。
     *
     * @param scoreView
     */
    public void setScoreView(TextView scoreView) {
        this.scoreView = scoreView;
    }

    /**
     * canvasをクリアする。
     */
    private void clearScreen(Canvas canvas) {
        invalidate();
    }

    /**
     * スコアを表示する。
     *
     * @param score
     *            スコア
     */
    private void drawScore(int score) {
        assert scoreView != null;

        scoreView.setText(Integer.toString(score));
    }
}
---


▲ インターフェース

アイテムの性質によって、つぎのインターフェースを作った。

インターフェースとそれぞれのメソッド
---
IDrawable       描画できる項目のインターフェース
     void       draw(android.graphics.Canvas canvas) 
     int        getColor() 
     int        getHeight() 
     int[][]    getImageTable() 
     int        getWidth() 
     int        getX() 
     int        getY() 
     boolean    isVisible() 
     void       setNotVisible() 

IAttacher       攻撃物のインターフェース
     boolean    isVisible() 
            
ITarget         攻撃目標になるアイテムのインターフェース
    boolean     isHit(IAttacher attacher) 
     void       resetColor() 
     void       setColor(int color) 
            
IMoveAutomatic  自動で動くアイテムのインターフェース
     boolean    isBorder() 
     void       move() 
            
IMoveManual     手動で動かすアイテムのインターフェース
     void       move(int x, int y) 
---


□ IDrawable インターフェース

描画できる項目のインターフェース。
描画に必要なものとして、
・描画のデータ
・色
・左上の座標
がある。これらに対するゲッターを用意している。

メソッド
---
void       draw(android.graphics.Canvas canvas) 
int        getColor() 
int        getHeight() 
int[][]    getImageTable() 
int        getWidth() 
int        getX() 
int        getY() 
boolean    isVisible() 
void       setNotVisible() 
---


・IDrawable.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;

/**
 * 描画できる項目のインターフェース
 *
 * @author maruno
 *
 */
public interface IDrawable {

    /**
     * 描画のときのブロックの大きさ
     */
    public static final int BLOCK_SIZE = 20;
    /**
     * 非表示
     */
    public static final int NOT_VISIBLE = -1;

    /**
     * 図形描画のテーブルを取得する。
     *
     * @return 図形描画のテーブル
     */
    public int[][] getImageTable();

    /**
     * X 座標を取得する。
     *
     * @return X 座標
     */
    public int getX();

    /**
     * Y 座標を取得する。
     *
     * @return Y 座標
     */
    public int getY();

    /**
     * 図形の色を取得する。
     *
     * @return 図形の色
     */
    public int getColor();

    /**
     * 図形の幅を取得する。
     *
     * @return 図形の幅
     */
    public int getWidth();

    /**
     * 図形の高さを取得する。
     *
     * @return 図形の高さ
     */
    public int getHeight();

    /**
     * 図形を描画する。
     *
     * @param canvas
     *            キャンバス
     */
    public void draw(Canvas canvas);

    /**
     * この図形が可視かどうかを返す。
     *
     * @return 可視の場合true
     */
    public boolean isVisible();

    /**
     * 図形を非表示にする。
     */
    public void setNotVisible();

}
---


□ IMoveAutomatic インターフェース

自動で動くアイテムのインターフェース。
自身が描画域の端にいるのかどうかを判断するメソッドを用意する。

メソッド
---
boolean    isBorder() 
void       move() 
---


・ IMoveAutomatic.java
---
package jp.marunomaruno.android.invader;

/**
 * 自動で動くアイテムのインターフェース
 *
 * @author maruno
 *
 */
public interface IMoveAutomatic {
    /**
     * アイテムが動く。
     */
    public void move();

    /**
     * 端かどうかを判断する。
     *
     * @return 端のときは true
     */
    public boolean isBorder();
}
---


□ IMoveManual インターフェース

手動で動かすアイテムのインターフェース。
このため、移動先の座標を指定して移動するメソッドを用意する。

メソッド
---
void       move(int x, int y) 
---


・ IMoveManual.java
---
package jp.marunomaruno.android.invader;

/**
 * 手動で動かすアイテムのインターフェース
 * @author maruno
 *
 */
public interface IMoveManual {
    /**
     * アイテムを指定座標に動かす。
     * @param x X 座標
     * @param y Y 座標
     */
    public void move(int x, int y);
}
---


□ IAttacher インターフェース

相手を攻撃する物体のインターフェース。
可視かどうかを判断することができるようにする。

メソッド
---
boolean    isVisible() 
---


・ IAttacher.java
---
package jp.marunomaruno.android.invader;

/**
 * 攻撃物のインターフェース
 * @author maruno
 *
 */
public interface IAttacher {
    public boolean isVisible();
}
---


□ ITarget インターフェース

攻撃目標になるアイテムのインターフェース。
攻撃物が自身に当たったかどうかを判断できる。
また、あたったときに、自身の色を変えることができる。

メソッド
---
boolean    isHit(IAttacher attacher) 
void       resetColor() 
void       setColor(int color) 
---


・ ITarget.java
---
package jp.marunomaruno.android.invader;

/**
 * ITarget        攻撃目標になるアイテムのインターフェース
 * 
 * @author maruno
 * 
 */
public interface ITarget {
    public boolean isHit(IAttacher attacher);

    /**
     * 図形の色を設定する。
     * 
     * @param color
     *            色
     */
    public void setColor(int color);

    /**
     * 図形の色をデフォルトの色にリセットする。
     */
    public void resetColor();
}
---


▲ アイテム

スーパークラスとして、AbstractItem をつくり、これを継承するクラスとして、イン
ベーダー、砲台、弾 をつくる。


□ AbstractItem クラス

すべてのアイテムのスーパークラスになるクラス。

図形の形、色、描画するときの左上の座標を持つ。
とくに、図形の左上の座標は public にしている。
図形の形、色は、このクラスのサブクラスで設定する。


フィールドの概要
---
public     int         x       図形の x 座標
public     int         y       図形の y 座標
protected  int         color   図形の色
protected  int         height  図形の高さ
protected  int[][]     image   図形の形
protected  CanvasView  view    図形を描画するビュー
protected  int         width   図形の幅
---


メソッドの概要
---
public     int      getColor() 
                    図形の色を取得する。
public     int      getHeight() 
                    図形の高さを取得する。
public     int[][]  getImageTable() 
                    図形描画のテーブルを取得する。
public     int      getWidth() 
                    図形の幅を取得する。
public     int      getX() 
                    X 座標を取得する。
public     int      getY() 
                    Y 座標を取得する。
public     boolean  isVisible() 
                    この図形が可視かどうかを返す。
public     void     setNotVisible() 
                    図形を非表示にする。
protected  void     draw(Canvas canvas, Paint paint, int x, int y, int[][] image) 
                    図形を描画する。
---


・ AbstractItem.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Paint;

/**
 * インベーダー・ゲーム上のアイテムのスーパークラス。 
 * アイテムの座標、色を持っている。
 *
 * @author maruno
 *
 */
public abstract class AbstractItem implements IDrawable {

    /**
     * 図形の x 座標
     */
    protected int[][] image;

    /**
     * 図形の x 座標
     */
    public int x;
    /**
     * 図形の y 座標
     */
    public int y;

    /**
     * 図形の色
     */
    protected int color;

    /**
     * 図形の幅
     */
    protected int width;
    /**
     * 図形の高さ
     */
    protected int height;
    /**
     * 図形を描画するビュー
     */
    protected CanvasView view;

    protected AbstractItem(CanvasView view, int x, int y, int[][] image,
            int width, int height, int color) {
        super();
        this.view = view;
        this.x = x;
        this.y = y;
        this.image = image;
        this.width = width;
        this.height = height;
        this.color = color;
    }

    /**
     * 図形を描画する。
     *
     * @param canvas
     *            キャンバス
     * @param paint
     *            ペイント
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param image
     *            図形を表現した2次元配列
     */
    protected void draw(Canvas canvas, Paint paint, int x, int y, int[][] image) {
        for (int i = 0; i < image.length; i++) {
            for (int j = 0; j < image[0].length; j++) {
                if (image[i][j] == 1) {
                    canvas.drawRect(x + j * BLOCK_SIZE, y + i * BLOCK_SIZE, x
                            + j * BLOCK_SIZE + BLOCK_SIZE, y + i * BLOCK_SIZE
                            + BLOCK_SIZE, paint);
                }
            }
        }
    }

    @Override
    public int[][] getImageTable() {
        return image;
    }

    @Override
    public int getX() {
        return x;
    }

    @Override
    public int getY() {
        return y;
    }

    @Override
    public int getColor() {
        return color;
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    @Override
    public boolean isVisible() {
        return x >= 0 && y >= 0;
    }

    @Override
    public void setNotVisible() {
        x = NOT_VISIBLE;
        y = NOT_VISIBLE;
    }
}
---


□ Bullet クラス

弾を管理するクラス。
弾は、一度発射されれば自動で動くので、IMoveAutomatic インターフェースを、また、
相手を攻撃するので、IAttacher インターフェースを実装する。

定数
---
int      COLOR        図形の色
int      HEIGHT       描画高
int[][]  IMAGE        図形イメージ
int      MOVING_UNIT  移動単位(ピクセル)
int      WIDTH        描画幅
---


・ Bullet.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

/**
 * 弾のクラス。
 * 
 * @author marunomaruno
 */
public class Bullet extends AbstractItem implements IMoveAutomatic, IAttacher {

    /**
     * 図形イメージ
     */
    public static final int[][] IMAGE = { { 1 }, };

    /**
     * 描画幅
     */
    public static final int WIDTH = IMAGE[0].length * BLOCK_SIZE;
    /**
     * 描画高
     */
    public static final int HEIGHT = IMAGE.length * BLOCK_SIZE;

    /**
     * 移動単位(ピクセル)
     */
    public static final int MOVING_UNIT = 5;

    /**
     * 図形の色
     */
    public static final int COLOR = Color.GREEN;

    /**
     * 弾オブジェクトを生成する。
     * 
     * @param view
     *            ビュー
     * @param x
     *            図形の左上のX座標
     * @param y
     *            図形の左上のY座標
     */
    public Bullet(CanvasView view, int x, int y) {
        super(view, x, y, IMAGE, WIDTH, HEIGHT, COLOR);
    }

    /**
     * 弾オブジェクトを非表示の状態で生成する。
     * 
     * @param view
     *            ビュー
     */
    public Bullet(CanvasView view) {
        this(view, NOT_VISIBLE, NOT_VISIBLE);
        ;
    }

    @Override
    public void draw(Canvas canvas) {
        if (isBorder()) {
            return;
        }

        Paint paint = new Paint();
        paint.setColor(color);
        draw(canvas, paint, x, y, IMAGE);
    }

    @Override
    public void move() {
        y = y - MOVING_UNIT;
    }

    @Override
    public boolean isBorder() {
        return !isVisible();
    }

    @Override
    public String toString() {
        return String.format("Bullet[%d, %d]", x, y);
    }
}
---


□ Gun クラス

砲台を管理するクラス。
砲台は、利用者の指示で動くので、IMoveManual インターフェースを実装する。


メソッド
---
public  Bullet  shot()  弾を発射する。
---


・ Gun.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

/**
 * 砲台のクラス。
 * @author marunomaruno
 */
public class Gun extends AbstractItem implements IMoveManual {

    /**
     * 図形イメージ
     */
    public static final int[][] IMAGE = {
            { 0, 0, 1, 1, 0, 0 },
            { 1, 1, 1, 1, 1, 1 },
    };

    /**
     * 描画幅
     */
    public static final int WIDTH = IMAGE[0].length * BLOCK_SIZE;
    /**
     * 描画高
     */
    public static final int HEIGHT = IMAGE.length * BLOCK_SIZE;

    /**
     * 移動単位(ピクセル)
     */
    public static final int MOVING_UNIT = 5;

    /**
     * 図形の色
     */
    public static final int COLOR = Color.BLUE;

    /**
     * 砲台オブジェクトを生成する。
     * @param view ビュー
     * @param x 図形の左上のX座標
     * @param y 図形の左上のY座標
     */
    public Gun(CanvasView view, int x, int y) {
        super(view, x, y, IMAGE, WIDTH, HEIGHT, COLOR);
    }

    /**
     * 砲台オブジェクトを生成する。
     * 砲台は画面の下段中央に配置する。
     * @param view ビュー
     */
    public Gun(CanvasView view) {
        this(view, (view.getWidth() - WIDTH) / 2, view.getHeight() - HEIGHT);
    }

    /**
     * 弾を発射する。
     */
    public Bullet shot() {
        return new Bullet(view, x + (Gun.WIDTH - Bullet.WIDTH) / 2, y);
    }

    @Override
    public void draw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(color);
        draw(canvas, paint, x, y, IMAGE);
    }

    @Override
    public void move(int x, int y) {
        this.x = Math.max(Math.min(x, view.getWidth() - WIDTH), 0);
    }

    @Override
    public String toString() {
        return String.format("Gun[%d, %d]", x, y);
    }
}
---


□ Invader クラス

インベーダーを管理するクラス。
インベーダーは、ゲームの最初から自動で動くので、IMoveAutomatic インターフェース
を、また、相手の攻撃を受けるので、ITarget インターフェースを実装する。


定数
---
int      COLOR            図形の色
int      DIRECTION_LEFT   移動方向 左
int      DIRECTION_RIGHT  移動方向 右
int      HEIGHT           描画高
int      HIT_COLOR        当たったときの図形の色
int[][]  IMAGE            図形イメージ
int      MOVING_UNIT      移動単位(ピクセル)
int      WIDTH            描画幅
---

フィールド
---
private  int  movingUnit  移動単位(ピクセル)
---


メソッド
---
public  void  invert()    移動方向を反転する。
---


・ Invader.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

/**
 * インベーダーのクラス
 * @author maruno
 *
 */
public class Invader  extends AbstractItem implements IMoveAutomatic, ITarget {

    /**
     * 図形イメージ
     */
    public static final int[][] IMAGE = {
            { 0, 0, 0, 1, 0, 1, 0, 0, 0 },
            { 1, 0, 1, 1, 1, 1, 1, 0, 1 },
            { 1, 1, 1, 0, 1, 0, 1, 1, 1 },
            { 0, 0, 1, 1, 1, 1, 1, 0, 0 },
            { 0, 0, 1, 1, 0, 1, 1, 0, 0 },
            { 0, 0, 1, 0, 0, 0, 1, 0, 0 },
    };

    /**
     * 描画幅
     */
    public static final int WIDTH = IMAGE[0].length * BLOCK_SIZE;
    /**
     * 描画高
     */
    public static final int HEIGHT = IMAGE.length * BLOCK_SIZE;

    /**
     * 移動単位(ピクセル)
     */
    public static final int MOVING_UNIT = 1;

    /**
     * 図形の色
     */
    public static final int COLOR = Color.GREEN;
    /**
     * 当たったときの図形の色
     */
    public static final int HIT_COLOR = Color.RED;

    /**
     * 移動方向 左
     */
    public static final int DIRECTION_LEFT = -1;
    /**
     * 移動方向 右
     */
    public static final int DIRECTION_RIGHT = 1;

    /**
     * 移動単位(ピクセル)
     */
    private int movingUnit = DIRECTION_RIGHT * MOVING_UNIT;

    /**
     * インベーダー・オブジェクトを生成する。
     * @param view ビュー
     * @param x 図形の左上のX座標
     * @param y 図形の左上のY座標
     */
    public Invader(CanvasView view, int x, int y) {
        super(view, x, y, IMAGE, WIDTH, HEIGHT, COLOR);
    }

    /**
     * インベーダー・オブジェクトを生成する。
     * インベーダーは画面の左上から出現する。
     * @param view ビュー
     */
    public Invader(CanvasView view) {
        this(view, 0, 0);
    }

    /**
     * 移動方向を反転する。
     */
    public void invert() {
        movingUnit = -movingUnit;
    }

    @Override
    public void draw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(color);
        draw(canvas, paint, x, y, IMAGE);    }

    @Override
    public void setColor(int color) {
        this.color = color;
    }

    @Override
    public void resetColor() {
        color = COLOR;
    }

    /**
     * インベーダーを動かす。
     * 動かすのは、単純に左右に動かし、壁にぶつかったら反転する。
     */
    @Override
    public void move() {
        x += movingUnit;

        // 左右の端にぶつかったら反転する
        if (isBorder()) {
            invert();
        }
    }

    @Override
    public boolean isBorder() {
        return (x < 0) || (x > view.getWidth() - WIDTH);
    }

    @Override
    public boolean isHit(IAttacher attacher) {
        Bullet bullet = (Bullet) attacher;

        return attacher.isVisible()
                && (bullet.y + Bullet.HEIGHT < this.y + HEIGHT)
                && (this.x < bullet.x)
                && (bullet.x + Bullet.WIDTH < this.x + WIDTH);
    }

    @Override
    public String toString() {
        return String.format("Invader[%d, %d]", x, y);
    }
}
---


▲ レイアウト

□ res/layout/main.xml

※ Invader01 プロジェクトと同じ

▲ リソース

□ res/values/strings.xml

※ Invader01 プロジェクトと同じ

                                                                            以上


[Android] インベーダー・ゲームを作る (1) シンプル

2012年04月05日 | Android
[Android] インベーダー・ゲームを作る (1) シンプル
================================================================================

インベーダー・ゲームを作ってみる。

ゲームの概要:
・画面上部にインベーダーがあり、これが左右に動く(端まで行ったら折り返す程度)
・画面下部に砲台があり、ユーザーのタッチムーブ(ドラッグ)によって左右に動く
・画面をタッチすると、砲台から弾が上部向けてに発射する
・弾がインベーダーに当たると得点する

ゲーム自体は、ImageView クラスを継承したクラス CanvasView を作って、それの上で行
う。


▲ アクティビティ

□ Invader01Activity.java
---
package jp.marunomaruno.android.invader;

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

public class InvaderActivity extends Activity {

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

        TextView scoreView = (TextView) findViewById(R.id.score);
        CanvasView canvasView = (CanvasView) findViewById(R.id.canvasView);
        canvasView.setScoreView(scoreView);
    }
}
---


▲ ビュー

ゲーム自体は、ImageView クラスを継承したクラス CanvasView を作って、それの上で行
う。

CanvasView は以下のようなメンバーからなる。

定数
---
int     BLOCK_SIZE          描画のときのブロックの大きさ
int     BULLET_COLOR        弾の色
int     BULLET_HEIGHT       弾の描画高
int[][] BULLET_IMAGE        弾の図形イメージ
int     BULLET_MOVING_UNIT  弾の移動する単位(ピクセル)
int     BULLET_WIDTH        弾の描画幅
int     DIRECTION_LEFT      移動方向 左
int     DIRECTION_RIGHT     移動方向 右
int     DRAWING_INTERVAL    描画間隔(ミリ秒)
int     GUN_COLOR           砲台の色
int     GUN_HEIGHT          砲台の描画高
int[][] GUN_IMAGE           砲台の図形イメージ
int     GUN_MOVING_UNIT     弾の移動する単位(ピクセル)
int     GUN_WIDTH           砲台の描画幅
int     INVADER_COLOR       正常時のインベーダーの色
int     INVADER_HEIGHT      インベーダーの描画高
int     INVADER_HITL_COLOR  当たったときのインベーダーの色
int[][] INVADER_IMAGE       インベーダーの図形イメージ
int     INVADER_MOVING_UNIT インベーダーの移動する単位(ピクセル)
int     INVADER_WIDTH       インベーダーの描画幅
int     NOT_VISIBLE         非表示
---

フィールド
---
private  int       bulletPointX        弾の x 座標
private  int       bulletPointY        弾の y 座標
private  int       gunPointX           砲台の x 座標
private  int       gunPointY           砲台の y 座標
private  int       invaderColor        インベーダーの表示色
private  int       invaderMovingUnit   インベーダーの移動方向
private  int       invaderPointX       インベーダーの x 座標
private  int       invaderPointY       インベーダーの y 座標
private  int       score               得点
private  TextView  scoreView           得点を表示するビュー
---

public メソッド
---
TextView getScoreView()                   scoreView を取得する。
void     setScoreView(TextView scoreView) scoreView を設定する。
void     shot()                           弾を発射する。
---

private メソッド
---
void  clearScreen(Canvas canvas)           canvasをクリアする。
void  drawBullet(Canvas canvas, int x, int y, int color)  弾を描画する。
void  drawGun(Canvas canvas, int x, int y, int color)     砲台を描画する。
void  drawImage(Canvas canvas, Paint paint, int x, int y, int[][] image)
                                                          図形を描画する。
void  drawInvader(Canvas canvas, int x, int y, int color) インベーダーを描画する。
void  drawScore(int score)                 スコアを表示する。
void  init()                               すべてのコンストラクターで共通の処理。
boolean isBorder(int x, int width)         端かどうかを判断する。
boolean isHit()                            端が当たったかどうかをを判断する。
void  moveGun(int x)                       砲台の位置をずらす。
---

オーバーライドしているメソッド
---
void    onDraw(Canvas canvas) 
void    onSizeChanged(int w, int h, int oldw, int oldh) 
boolean onTouchEvent(MotionEvent event) 
---


□ CanvasView.java
---
package jp.marunomaruno.android.invader;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * インベーダー・ゲーム。
 *
 * @author marunomaruno
 * @version 1.0, 2012-03-06
 */
/**
 * @author marunomaruno
 *
 */
public class CanvasView extends ImageView {

    // 描画イメージ
    /**
     * 描画のときのブロックの大きさ
     */
    public static final int BLOCK_SIZE = 20; // (1)
    /**
     * 非表示
     */
    public static final int NOT_VISIBLE = -1;
    /**
     * 描画間隔(ミリ秒)
     */
    public static final int DRAWING_INTERVAL = 50;

    // インベーダー関係
    /**
     * インベーダーの図形イメージ
     */
    public static final int[][] INVADER_IMAGE = {
            { 0, 0, 0, 1, 0, 1, 0, 0, 0 },
            { 1, 0, 1, 1, 1, 1, 1, 0, 1 },
            { 1, 1, 1, 0, 1, 0, 1, 1, 1 },
            { 0, 0, 1, 1, 1, 1, 1, 0, 0 },
            { 0, 0, 1, 1, 0, 1, 1, 0, 0 },
            { 0, 0, 1, 0, 0, 0, 1, 0, 0 },
    };    // (2)

    /**
     * インベーダーの描画幅
     */
    public static final int INVADER_WIDTH = INVADER_IMAGE[0].length
            * BLOCK_SIZE;// (3)
    /**
     * インベーダーの描画高
     */
    public static final int INVADER_HEIGHT = INVADER_IMAGE.length * BLOCK_SIZE;// (4)

    /**
     * 正常時のインベーダーの色
     */
    public static final int INVADER_COLOR = Color.GREEN;
    /**
     * 当たったときのインベーダーの色
     */
    public static final int INVADER_HITL_COLOR = Color.RED;

    /**
     * インベーダーの移動する単位(ピクセル)
     */
    public static final int INVADER_MOVING_UNIT = 1;
    /**
     * 移動方向 左
     */
    public static final int DIRECTION_LEFT = -1;
    /**
     * 移動方向 右
     */
    public static final int DIRECTION_RIGHT = 1;

    // 砲台関係
    /**
     * 砲台の図形イメージ
     */
    public static final int[][] GUN_IMAGE = {
            { 0, 0, 1, 1, 0, 0 },
            { 1, 1, 1, 1, 1, 1 },
    };

    /**
     * 砲台の描画幅
     */
    public static final int GUN_WIDTH = GUN_IMAGE[0].length * BLOCK_SIZE;
    /**
     * 砲台の描画高
     */
    public static final int GUN_HEIGHT = GUN_IMAGE.length * BLOCK_SIZE;

    /**
     * 弾の移動する単位(ピクセル)
     */
    public static final int GUN_MOVING_UNIT = 5;

    /**
     * 砲台の色
     */
    public static final int GUN_COLOR = Color.BLUE;

    // 弾関係
    /**
     * 弾の図形イメージ
     */
    public static final int[][] BULLET_IMAGE = {
            { 1 },
    };

    /**
     * 弾の描画幅
     */
    public static final int BULLET_WIDTH = BULLET_IMAGE[0].length * BLOCK_SIZE;
    /**
     * 弾の描画高
     */
    public static final int BULLET_HEIGHT = BULLET_IMAGE.length * BLOCK_SIZE;

    /**
     * 弾の移動する単位(ピクセル)
     */
    public static final int BULLET_MOVING_UNIT = 5;

    /**
     * 弾の色
     */
    public static final int BULLET_COLOR = Color.GREEN;

    /**
     * 得点
     */
    private int score = 0;

    // インベーダーの設定
    /**
     * インベーダーの x 座標
     */
    private int invaderPointX = 0;
    /**
     * インベーダーの y 座標
     */
    private int invaderPointY = 0;
    /**
     *  インベーダーの移動方向
     */
    private int invaderMovingUnit = DIRECTION_RIGHT * INVADER_MOVING_UNIT; // (5)
    /**
     * インベーダーの表示色
     */
    private int invaderColor = INVADER_COLOR;

    // 砲台の設定
    /**
     * 砲台の x 座標
     */
    private int gunPointX;
    /**
     * 砲台の y 座標
     */
    private int gunPointY;

    // 弾の設定
    /**
     * 弾の x 座標
     */
    private int bulletPointX = NOT_VISIBLE; // 弾は最初は非表示
    /**
     * 弾の y 座標
     */
    private int bulletPointY = NOT_VISIBLE; // 弾は最初は非表示

    /**
     * 得点を表示するビュー
     */
    private TextView scoreView;

    public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CanvasView(Context context) {
        super(context);
        init();
    }

    /**
     * すべてのコンストラクターで共通の処理。
     */
    private void init() {
        score = 0; // 得点

        // インベーダーの設定
        invaderPointX = 0;
        invaderPointY = 0;
        invaderMovingUnit = DIRECTION_RIGHT * INVADER_MOVING_UNIT; 
                                                    // インベーダーの移動方向
        invaderColor = INVADER_COLOR;

        // 弾の設定
        bulletPointX = NOT_VISIBLE; // 弾は最初は非表示
        bulletPointY = NOT_VISIBLE; // 弾は最初は非表示

        System.out.printf("Canvas: (%d, %d)%n", getWidth(), getHeight()); // (6)
        System.out.printf("Invader: (%d, %d)%n", invaderPointX, invaderPointY);
        System.out.printf("Gun: (%d, %d)%n", gunPointX, gunPointY);
        System.out.printf("Bullet: (%d, %d)%n", bulletPointX, bulletPointY);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    // (7)
        super.onSizeChanged(w, h, oldw, oldh);
        System.out.printf("onSizeChanged: (%d, %d)%n", w, h);

        // 砲台の設定
        gunPointX = (w - GUN_WIDTH) / 2;
        gunPointY = h - GUN_HEIGHT;

        System.out.printf("Gun: (%d, %d)%n", gunPointX, gunPointY);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 画面をクリアする
        clearScreen(canvas);

        // インベーダーを表示する
        drawInvader(canvas, invaderPointX, invaderPointY, invaderColor);
        invaderColor = INVADER_COLOR;

        // 弾を表示する
        drawBullet(canvas, bulletPointX, bulletPointY, BULLET_COLOR);

        // 砲台を表示する
        drawGun(canvas, gunPointX, gunPointY, GUN_COLOR);

        // インベーダーを指定方向に移動する
        invaderPointX = invaderPointX + invaderMovingUnit;

        // 左右の端にぶつかったら反転する
        if (isBorder(invaderPointX, INVADER_WIDTH)) {
            invaderMovingUnit = -invaderMovingUnit;
        }

        // 弾を移動する
        bulletPointY = bulletPointY - BULLET_MOVING_UNIT;

        // 弾が当たったら得点する
        if (isHit()) {
            score++;
            invaderColor = INVADER_HITL_COLOR;
            bulletPointX = NOT_VISIBLE; // 弾の表示を消す
            bulletPointY = NOT_VISIBLE; // 弾の表示を消す
            // 得点を表示する
            drawScore(score);
        }

        // 指定ミリ秒分ウエイトする
        try {
            Thread.sleep(DRAWING_INTERVAL);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: // 指をタッチした
            assert true; // 何もしない
            break;

        case MotionEvent.ACTION_MOVE: // 指を動かしている    // (8)
            moveGun((int) event.getX());
            break;

        case MotionEvent.ACTION_UP: // 指を離した    // (9)
            shot();
            break;

        default:
            assert true; // 何もしない
            break;
        }

        invalidate();

        return true;
    }

    /**
     * 端が当たったかどうかをを判断する。
     *
     * @return 当たったときは true
     */
    private boolean isHit() {
        return bulletPointY >= 0    // 弾が表示されている
              && (bulletPointY + BULLET_HEIGHT < invaderPointY + INVADER_HEIGHT)
              && (invaderPointX < bulletPointX)
              && (bulletPointX + BULLET_WIDTH < invaderPointX + INVADER_WIDTH); 
                                                                       // (10)
    }

    /**
     * 端かどうかを判断する。
     *
     * @param x
     *            X座標
     * @param width
     *            図形の幅
     * @return 端のときは true
     */
    private boolean isBorder(int x, int width) {
        return (x < 0) || (x > getWidth() - width);
    }

    /**
     * canvasをクリアする。
     */
    private void clearScreen(Canvas canvas) {
        invalidate();
    }

    /**
     * インベーダーを描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param color
     *            図形の色
     */
    private void drawInvader(Canvas canvas, int x, int y, int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        drawImage(canvas, paint, x, y, INVADER_IMAGE);
    }

    /**
     * 弾を描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param color
     *            図形の色
     */
    private void drawBullet(Canvas canvas, int x, int y, int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        drawImage(canvas, paint, x, y, BULLET_IMAGE);
    }

    /**
     * 砲台を描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param color
     *            図形の色
     */
    private void drawGun(Canvas canvas, int x, int y, int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        drawImage(canvas, paint, x, y, GUN_IMAGE);
    }

    /**
     * 図形を描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param image
     *            図形を表現した2次元配列
     */
    private void drawImage(Canvas canvas, Paint paint, int x, int y,
            int[][] image) {
        if (x == NOT_VISIBLE || y == NOT_VISIBLE) {
            return;
        }

        for (int i = 0; i < image.length; i++) {
            for (int j = 0; j < image[0].length; j++) {
                if (image[i][j] == 1) {
                    canvas.drawRect(x + j * BLOCK_SIZE, y + i * BLOCK_SIZE, x
                            + j * BLOCK_SIZE + BLOCK_SIZE, y + i * BLOCK_SIZE
                            + BLOCK_SIZE, paint);    // (11)
                }
            }
        }
    }

    /**
     * スコアを表示する。
     *
     * @param score
     *            スコア
     */
    private void drawScore(int score) {
        assert scoreView != null;

        scoreView.setText(Integer.toString(score));
    }

    /**
     * 弾を発射する。
     */
    public void shot() {
        bulletPointX = gunPointX + (GUN_WIDTH - BULLET_WIDTH) / 2;
        bulletPointY = gunPointY;
    }

    /**
     * 砲台の位置をずらす。
     *
     * @param x
     *            位置
     */
    private void moveGun(int x) {
        gunPointX = Math.max(Math.min(x, getWidth() - GUN_WIDTH), 0);
    }

    /**
     * scoreView を取得する。
     * @return scoreView オブジェクト
     */
    public TextView getScoreView() {
        return scoreView;
    }

    /**
     * scoreView を設定する。
     * @param scoreView
     */
    public void setScoreView(TextView scoreView) {
        this.scoreView = scoreView;
    }

}
---

(1)(2)(11) 図形の描画

ドット絵のように、単位となる四角(ブロック)を決めて、それの組み合わせで描画する。

単位となる四角の大きさを 20 と決める。

    public static final int BLOCK_SIZE = 20; // (1)

図形イメージは、2次元配列で定義する。このブロックを描画するところが 1 に設定して
いる。

    public static final int[][] INVADER_IMAGE = { // インベーダーの図形イメージ
			{ 0, 0, 0, 1, 0, 1, 0, 0, 0 },
			{ 1, 0, 1, 1, 1, 1, 1, 0, 1 },
			{ 1, 1, 1, 0, 1, 0, 1, 1, 1 },
			{ 0, 0, 1, 1, 1, 1, 1, 0, 0 },
			{ 0, 0, 1, 1, 0, 1, 1, 0, 0 },
			{ 0, 0, 1, 0, 0, 0, 1, 0, 0 },
    };    // (2)

ブロックの2次元配列を使って、描画するのは、drawRect() を使う。左上の座標が 
(x, y) になっている。

    canvas.drawRect(x + j * BLOCK_SIZE, y + i * BLOCK_SIZE, x
            + j * BLOCK_SIZE + BLOCK_SIZE, y + i * BLOCK_SIZE
            + BLOCK_SIZE, paint);    // (11)


(3)(4) 図形の幅と高さ

描画する図形の幅と高さを、BLOCK_SIZE と、図形の2次元配列から算出する。

    public static final int INVADER_WIDTH = INVADER_IMAGE[0].length
            * BLOCK_SIZE; // インベーダーの描画幅    // (3)
    public static final int INVADER_HEIGHT = INVADER_IMAGE.length * BLOCK_SIZE;
                                            // インベーダーの描画高    // (4)



(5) インベーダーの移動単位

移動方向(DIRECTION_RIGHT) と、移動単位(INVADER_MOVING_UNIT) を使って決める。
インベーダーは、最初は画面左上から出現して、右方向に移動する。

    private int invaderMovingUnit = DIRECTION_RIGHT * INVADER_MOVING_UNIT; // (5)


(6)(7) キャンバスのサイズ

コンストラクターの中からなので、この時点では幅・高さともに 0 である。

    System.out.printf("Canvas: (%d, %d)%n", getWidth(), getHeight());    // (6)

キャンバスの実際の幅・高さは、このオブジェクトが生成されないとできない。このため、

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    // (7)

をオーバーライドして設定する。onSizeChanged() メソッドは、View のサイズが変更さ
れたときに、呼び出されるメソッド。

この onSizeChanged() を利用して、砲台の初期位置を設定する。
砲台は、画面下部の中央に出るようにする。


(8) 指の動きにあわせて砲台を移動する

    case MotionEvent.ACTION_MOVE: // 指を動かしている    // (8)
        moveGun((int) event.getX());


(9) 指を離したら、弾を発射する

    case MotionEvent.ACTION_UP: // 指を離した    // (9)
        shot();


(10) インベーダー枠の中に弾の枠が入ったらあたったことにする

    return bulletPointY >= 0    // 弾が表示されている
        && (bulletPointY + BULLET_HEIGHT < invaderPointY + INVADER_HEIGHT)
        && (invaderPointX < bulletPointX)
        && (bulletPointX + BULLET_WIDTH < invaderPointX + INVADER_WIDTH); // (10)


▲ レイアウト

□ 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" >

    <jp.marunomaruno.android.invader.CanvasView
        android:id="@+id/canvasView"
        android:layout_width="match_parent"
        android:layout_height="400px" />

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/scoreTitle"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/score"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

</LinearLayout>
---


▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">インベーダー</string>
    <string name="scoreTitle">"得点: "</string>
</resources>
---

                                                                            以上


[Android] 簡易電卓アプリ

2012年04月02日 | Android
[Android] 簡易電卓アプリ
================================================================================

単純な機能の電卓アプリ。
つぎのように、単純な機能しか持たない。
    ・ひとつの演算子に対して必ず「=」を押す
    ・連続して複数の演算子を使えない
    ・「=」の後も続けて計算はできない

Android アプリケーションを作るときの基本的な要素しか使っていない。
    LinearLayout
    TextView
    Button

ボタン押下時のハンドラーも、android:onClick 属性で指定している。
    onNumberButtonClick      0 ~ 9 の数字ボタンをクリックしたとき
    onOperatorButtonClick    演算子のボタンをクリックしたとき
    onClearButtonClick       クリア・ボタンをクリックしたとき
    onEqualButtonClick       「=」ボタンをクリックしたとき


▲ レイアウト

4x4 を、LinearLayout と android:layout_weight を使って実現している。

□ 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/RESULT_VIEW"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:text="0"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON7"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="7"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON8"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="8"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON9"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="9"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/DIVIDE_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="÷"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON4"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="4"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON5"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="5"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON6"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="6"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/MULTIPLY_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="×"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON1"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="1"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON2"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="2"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON3"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="3"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/SUBTRACT_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="-"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON0"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="0"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/CLEAR_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClearButtonClick"
            android:text="C"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/EQUAL_BUTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onEqualButtonClick"
            android:text="="
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/ADD_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="+"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <TextView
        android:id="@+id/GUID_VIEW"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/guide" />

</LinearLayout>
---

---
★ @+id でつける名前だが、R クラスでは public static final int で宣言されている
ので、通常の Java の定数のように、すべて大文字で記述したほうがよいように思える。
アクティビティなんかで、switch-case を使っていると、大文字のほうがしっくりくる感
じだ。
---


▲ アクティビティ


□ Calculator01Activity.java
---
package jp.marunomaruno.android.calculator;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import jp.marunomaruno.android.calculator.R;

/**
 * 簡易電卓のアクティビティ。
 * @author marunomaruno
 *
 */
public class Calculator01Activity extends Activity {
    private int number = 0;    // 入力された数字
    private int result = 0;    // 計算結果
    private int operatorId;    // 演算子のリソースID

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

    /**
     * 数字のボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onNumberButtonClick(View view) {
        Button button = (Button) view;
        number = number * 10 + Integer.parseInt(button.getText().toString());
        show(number);
    }

    /**
     * 演算子のボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onOperatorButtonClick(View view) {
        operatorId = view.getId();
        result = number;
        number = 0;
    }

    /**
     * 「=」ボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onEqualButtonClick(View view) {
        switch (operatorId) {
        case R.id.ADD_BUTTON:
            result += number;
            break;

        case R.id.SUBTRACT_BUTTON:
            result -= number;
            break;

        case R.id.MULTIPLY_BUTTON:
            result *= number;
            break;

        case R.id.DIVIDE_BUTTON:
            result /= number;
            break;

        default:
            assert false;
            break;
        }

        number = 0;
        show(result);
    }

    /**
     * クリア・ボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onClearButtonClick(View view) {
        number = 0;
        result = 0;
        operatorId = 0;
        show(number);
    }

    /**
     * 指定された数値を表示する。
     * @param number 表示する数値
     */
    private void show(int number) {
        TextView resultView = (TextView) findViewById(R.id.RESULT_VIEW);
        resultView.setText(Integer.toString(number));
    }
}
---


▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">簡易電卓</string>
    <string name="guide">
            ・ひとつの演算子に対して必ず「=」を押す¥n
            ・連続して複数の演算子を使えない¥n
            ・「=」の後も続けて計算はできない¥n
    </string>
</resources>
---


■ 発展

つぎのようなことを考慮して、改良する。
    ・レイアウトを整える
    ・演算子を連続して使えるようにする
    ・「=」の後に続けて演算子を使えるようにする
    ・式も表示できるようにする
    ・他の演算子(平方根や三角など)をつけて、関数電卓にする
    ・16進数が使えるようにする
    ・結果がlong型まで大丈夫にする
    ・数値の桁数の制限をなくす
    ・入力に対して undo する
    ・画面の縦横が変わっても、計算結果を失わないようにする
    ・例外を出さないようにする

                                                                            以上


[Android] コンテナ (2) スクロール表示 - ScrollView、HorizontalScrollView

2012年03月26日 | Android
[Android] コンテナ (2) スクロール表示 - ScrollView、HorizontalScrollView
================================================================================

ScrollView は、そのコンテンツのスクロール機能を提供するコンテナ。画面に入りきれ
ないと思われるウィジェットを ScrollView で囲むことで、既存のレイアウトをそのまま
使うことができる。

使い方としては以下のとおり。
    <ScrollView>
        他のウィジェット
    </ScrollView>


ScrollView は、縦スクロール。HorizontalScrollView は横スクロールが可能になる。
縦と横の両方を可能にするためには、ScrollView と HorizontalScrollView とを組み合
わせればよい。すなわち、次のような形になる。
    <HorizontalScrollView>
        <ScrollView>
            他のウィジェット
        </ScrollView>
    </HorizontalScrollView>


▲ レイアウト

同じレイアウトに対して、つぎの 3 つのパターンを表示。

    ScrollView
    HorizontalScrollView
    ScrollView と HorizontalScrollView の組合わせ

レイアウトは、TableLayout01 のサンプル。
したがって、拡大していくと、それぞれが縦スクロール、横スクロール、縦横スクロール
ができる。

わかりやすくするために、ScrollView 要素に囲まれているウィジェットは、table.xml 
をインクルードしている。

□ 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:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ScrollView"
        android:textAppearance="?android:attr/textAppearanceLarge" />
    
    <ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" >

        <include android:id="@+id/table0" layout="@layout/table" />
    </ScrollView>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HorizontalScrollView"
        android:textAppearance="?android:attr/textAppearanceLarge" />
        
    <HorizontalScrollView
        android:id="@+id/horizontalScrollView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" >

        <include android:id="@+id/table1" layout="@layout/table" />
    </HorizontalScrollView>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HorizontalScrollView + ScrollView"
        android:textAppearance="?android:attr/textAppearanceLarge" />
        
    <HorizontalScrollView
        android:id="@+id/horizontalScrollView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1" 
        android:scrollbars="none">

        <ScrollView
            android:id="@+id/scrollView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:scrollbars="none">

            <include android:id="@+id/table2" layout="@layout/table" />
        </ScrollView>
    </HorizontalScrollView>

</LinearLayout>
---


□ ScrollView クラス

java.lang.Object
   +   android.view.View
        +   android.view.ViewGroup
             +   android.widget.FrameLayout
                  +   android.widget.ScrollView

ScrollView は、そのコンテンツの縦スクロール機能を提供するコンテナ。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
fillViewport  子のウィジェットが小さい場合でも、このビューの高さ分の領域をとる
---

setFillViewport(boolean) メソッドにより、属性値を動的に変更できる。


□ HorizontalScrollView

java.lang.Object
   +   android.view.View
        +   android.view.ViewGroup
             +   android.widget.FrameLayout
                  +   android.widget.HorizontalScrollView

HorizontalScrollView は、そのコンテンツの横スクロール機能を提供するコンテナ。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
fillViewport  子のウィジェットが小さい場合でも、このビューの幅分の領域をとる
---

setFillViewport(boolean) メソッドにより、属性値を動的に変更できる。


・スクロールバーの表示

android:scrollbars 属性は、スクロールバーを表示するかしないかを指定する。
つぎの値が指定できる。
    none          スクロールが必要になっても表示しない
    horizontal    横スクロールが必要なときに横バーを表示
    vertical      縦スクロールが必要なときに縦バーを表示

なお、ScrollViewで、実際にスクロールバーが表示されるのは、スクロールが必要なとき
で、android:scrollbars="vertical" と指定しても、最初からスクロールバーが表示され
るわけではない。表示させたくないときは値 "none" を指定する。


□ res/layout/table.xml

※ TableLayout01 プロジェクトの res/layout/main.xml と同じ。


▲ アクティビティ

ボタンがクリックされたら、その状態によって、ボタンの幅と高さを倍にするか半分にす
る。

□ ScrollView01Activity.java

※ クラス名が上記の名前になっているだけで、コードは LinearLayout01Activity.java と同じ。


■ 実例

サンプルのプロジェクトは、以下の記事を参照。
    SystemOut01 プロジェクト
    標準出力 - あて先に画面を追加する, 2011-12-22
    http://blog.goo.ne.jp/marunomarunogoo/d/20111222

System.out を 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" >

    <ScrollView
        android:id="@+id/sysoutScrollView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >    <!-- (1) -->

        <TextView
            android:id="@+id/sysout"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />    <!-- (2) -->
    </ScrollView>

</LinearLayout>
---

ScrollView 要素の下に TextView 要素。この TextView が画面いっぱいになったら、ス
クロール・バーが表示される。


■ 自動スクロールダウン

ScrollView を使っただけでは、自動的にスクロールダウンしない。自動的にスクロール
ダウンさせるのであれば、以下のようなスレッドを作り、ポストする。

上記の例であれば、
        TextView view = (TextView) findViewById(R.id.sysout);
としたときに、

    ((View) view.getParent()).post(new ScrollDown());

で、スクロール・ダウンさせるスレッドを設定する。

スクロール・ダウンさせるスレッドは、以下のように作成する。
なお、view は、インスタンス・フィールドか、final ローカル変数として定義してある
ものとする。

---
    /**
     * スクロールダウンさせるスレッドのクラス
     * @author marunomaruno
     */
    private class ScrollDown implements Runnable { // (8)
        public void run() {
            ((ScrollView) view.getParent()).fullScroll(View.FOCUS_DOWN); // (9)
        }
    }
---

                                                                            以上


[Android] レイアウト (5) レイアウトの組込み - include

2012年03月24日 | Android
[Android] レイアウト (5) レイアウトの組込み - include
================================================================================

他のレイアウトの XML ファイルを、自身の中に組み込むことができる。
このためには、
    <include layout="@layout/XMLファイル名" />
要素を使う。


・属性

layout
    レイアウトリソース。 必須。レイアウトリソースに対する参照。

android:id
    リソース ID。インクルードされたレイアウトのルート要素のビューで指定された ID
 はこれで上書きされる。

android:layout_height, android:layout_width
    ディメンションまたはキーワード。インクルードされたレイアウトのルート要素のビ
ューで指定された高さ、幅はこれで上書きされる。 

インクルードされたレイアウトのルート要素でサポートされていれば、 <include> には
他のレイアウト属性を含めることが可能で、それらはルート要素で定義されたものを上書
きする。


□ 参考
ソフトウェア技術ドキュメントを勝手に翻訳
Android 開発ガイド > フレームワークトピック > 7. アプリケーションリソース > 
7.5 リソースタイプ > 
7.5.4 レイアウトリソース 
http://www.techdoctranslator.com/android/guide/resources/available-resources/layout-resource


▲ レイアウト

3x3 のテーブルのレイアウトを定義する。このとき、1 行分を定義する row.xml、1 項目
分を定義する item.xml を用意し、include 要素で引っ張ってきている。

    main.xml    TableLayout 要素でテーブルを定義。3 つの row.xml を参照している
    row.xml     TableRow 要素で行を定義。3 つの item.xml を参照している
    item.xml    ToggleButton 要素

□ 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" >

    <TableLayout
        android:id="@+id/tableLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dip"
        android:weightSum="1" >

        <include android:id="@+id/row0" layout="@layout/row" />
        <include android:id="@+id/row1" layout="@layout/row" />
        <include android:id="@+id/row2" layout="@layout/row" />
        
    </TableLayout>

</LinearLayout>
---


1 行分の定義

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

    <include android:id="@+id/button0" layout="@layout/item" />
    <include android:id="@+id/button1" layout="@layout/item" />
    <include android:id="@+id/button2" layout="@layout/item" />
    
</TableRow>
---


1 項目分の定義

□ res/layout/item.xml
---
<?xml version="1.0" encoding="utf-8"?>
<ToggleButton xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="onClick"
    android:textAppearance="?android:attr/textAppearanceLarge"
 />
---


▲ アクティビティ

ボタンがクリックされたら、その状態によって、ボタンの幅と高さを倍にするか半分にす
る。

コードは LinearLayout01Activity.java と同じであるが、末端のウィジェットを区別す
るのに、getId() メソッドを使って、リソース ID をログに出す部分が追加になっている。


□ IncludeLayout01Activity.java
---
package jp.marunomaruno.android.includelayout;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ToggleButton;
import jp.marunomaruno.android.includelayout.R;

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

    public void onClick(View view) {
        ToggleButton button = (ToggleButton) view;
        if (button.isChecked()) {
            button.setWidth(button.getWidth() * 2);
            button.setHeight(button.getHeight() * 2);

        } else {
            button.setWidth(button.getWidth() / 2);
            button.setHeight(button.getHeight() / 2);

        }

        System.out.printf("parent id = %x, id = %x", ((View) view.getParent())
                .getId(), view.getId());    // (1)
        System.out.println();
    }
}
---

(1) クリックしたビューと、その親ビューのリソース ID を表示

オブジェクトの特定のために、自身のリソース ID と、親のビュー(include 要素につけ
られた ID)のリソース ID をログに出力する。
R クラスの表現とあわせるために、16 進表記している。

    System.out.printf("parent id = %x, id = %x", ((View) view.getParent())
            .getId(), view.getId());    // (1)


View クラスのメソッド
---
int               getId()      リソース ID を取得する
final ViewParent  getParent()  親ビューを取得する
---


□ ViewParent インターフェース

親ビューになる可能性のあるビューのインターフェース。
次のビューのクラスがこのインターフェースを実装している。
    AbsListView
    AbsSpinner
    AbsoluteLayout
    AdapterView<T?extends?Adapter>
    AdapterViewAnimator
    AdapterViewFlipper
    AppWidgetHostView
    CalendarView
    DatePicker
    DialerFilter
    ExpandableListView
    FragmentBreadCrumbs
    FrameLayout
    Gallery
    GestureOverlayView
    GridLayout
    GridView
    HorizontalScrollView
    ImageSwitcher
    LinearLayout
    ListView
    MediaController
    NumberPicker
    RadioGroup
    RelativeLayout
    ScrollView
    SearchView
    SlidingDrawer
    Spinner
    StackView
    TabHost
    TabWidget
    TableLayout
    TableRow
    TextSwitcher
    TimePicker
    TwoLineListItem
    ViewAnimator
    ViewFlipper
    ViewGroup
    ViewSwitcher
    WebView
    ZoomControls

ViewParent インターフェースには、getId() メソッドがないので、これを使ってリソー
ス ID を取得するためには、View にキャストして使う必要がある。


                                                                            以上


[Android] レイアウト (4) RelativeLayout レイアウト

2012年03月23日 | Android
[Android] レイアウト (4) RelativeLayout レイアウト
================================================================================

他のウィジェットの横や下にどの程度離れて配置するか、を示すルールベース・モデルの
レイアウト。
他のウィジェットとの相対位置を基にして、自分の位置を決める。
他のウィジェットは、自分の隣りである必要はなく、離れていても OK。
相対位置は、RelativeLayout に組み込まれているいくつかの属性を利用することで指定
する。

この RelativeLayout を使うと、ウィジェットの上にウィジェットを重ねられる。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
android:gravity        setGravity(int)        ウィジェットの配置方法
android:ignoreGravity  setIgnoreGravity(int)  gravityで 指定した配置方法の影響を
受けない Widget を指定
---


・コンテナを基準とする位置

これらの値は論理値(true/false)。

android:layout_alignParentBottom  ウィジェットの下辺とコンテナの下辺をそろえる
android:layout_alignParentLeft    ウィジェットの左辺とコンテナの左辺をそろえる
android:layout_alignParentRight   ウィジェットの右辺とコンテナの右辺をそろえる
android:layout_alignParentTop     ウィジェットの上辺とコンテナの上辺をそろえる

android:layout_centerHorizontal   ウィジェットをコンテナの中央に水平に配置
android:layout_centerVertical     ウィジェットをコンテナの中央に垂直に配置
android:layout_centerInParent     ウィジェットをコンテナの中央に水平・垂直に配置


・他のウィジェットを基準とする位置

他のウィジェットは、@id/リソースID で参照する。

android:layout_above        指定されたビューの上辺にこのビューを配置
android:layout_below        指定されたビューの下辺にこのビューを配置
android:layout_toLeftOf     指定されたビューの左辺にこのビューを配置
android:layout_toRightOf    指定されたビューの右辺にこのビューを配置

android:layout_alignBottom  指定されたビューの下辺にこのビューの下辺をそろえる
android:layout_alignLeft    指定されたビューの左辺にこのビューの左辺辺をそろえる
android:layout_alignRight   指定されたビューの右辺にこのビューの右辺をそろえる
android:layout_alignTop     指定されたビューの上辺にこのビューの上辺をそろえる


マージンについては、ViewGroup.MarginLayoutParams を参照。
レイアウト (1) レイアウトの概要
http://blog.goo.ne.jp/marunomarunogoo/d/20120320


▲ レイアウト

一部のウィジェットが重なって表示される。

□ 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" >

    <RelativeLayout
        android:id="@+id/relativeLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="5dip"
        android:weightSum="1" >

        <ToggleButton
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textOff="1¥nParentLeft¥nParentTop"
            android:textOn="1¥nParentLeft¥nParentTop" />

        <ToggleButton
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@id/button1"
            android:layout_below="@id/button1"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textOff="2¥nbelow=1"
            android:textOn="2¥nbelow=1" />

        <ToggleButton
            android:id="@+id/button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignTop="@id/button2"
            android:layout_below="@id/button2"
            android:layout_toRightOf="@id/button2"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textOff="3¥nalignTop=2¥nbelow=2¥ntoRightOf=2"
            android:textOn="3¥nalignTop=2¥nbelow=2¥ntoRightOf=2" />

        <ToggleButton
            android:id="@+id/button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignRight="@id/button3"
            android:layout_below="@id/button3"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textOff="4¥nalignRight=3¥nbelow=3"
            android:textOn="4¥nalignRight=3¥nbelow=3" />

        <ToggleButton
            android:id="@+id/button5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_below="@id/button2"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textOff="5¥nParentRight¥nbelow=2"
            android:textOn="5¥nParentRight¥nbelow=2" />
        
        <ToggleButton
            android:id="@+id/button6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/button2"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textOff="6¥nbelow=2"
            android:textOn="6¥nbelow=2" />
        
        </RelativeLayout>

</LinearLayout>
---


□ 表示
---
1
ParentLeft
ParenTop

2               3
below=1         alignTop=2
                below=2
6               toRightOf=2              5
below=2                                  ParentRight
                4                        bwlow=2
                alignRight=3
                below=3
---


設定値のまとめ
------------------------- --- --- --- --- --- --- 
                           1   2   3   4   5   6
------------------------- --- --- --- --- --- --- 
layout_alignParentBottom 
layout_alignParentLeft     x
layout_alignParentRight                x
layout_alignParentTop      x      
------------------------- --- --- --- --- --- --- 
layout_above             
layout_below                   1   2   3   2   2
layout_toLeftOf          
layout_toRightOf                   2
------------------------- --- --- --- --- --- --- 
layout_alignBottom       
layout_alignLeft         
layout_alignRight                      3
layout_alignTop                    2
------------------------- --- --- --- --- --- --- 

[1] は、親レイアウトの左端と上端に合わせる。

[2] は、[1] の下端に、自身の上端を合わせる。

[3] は、layout_below="2" と layout_alignTop="2" が指定されているが、[2] の上端と
自身の上端が合っている。また、layout_toRightOf="2" なので、[2] の右端が自身の左
端と合っている。

[4] は、[3] の下端に、自身の上端を合わせ、左端を合わせている。

[5] は、[2] の下端に、自身の上端を合わせ、親レイアウトの右端と自身の右端を合わせ
ている。

[6] は、[2] の下端に、自身の上端を合わせる。


□ RelativeLayout クラス

java.lang.Object
   +   android.view.View
        +   android.view.ViewGroup
             +   android.widget.RelativeLayout

相対レイアウトを定義するクラス。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
android:gravity
android:ignoreGravity
---

それぞれ、以下のメソッドで動的に変更できる。
    setGravity(int)
    setIgnoreGravity(int)


□ RelativeLayout.LayoutParams クラス

java.lang.Object
   +   android.view.ViewGroup.LayoutParams
        +   android.view.ViewGroup.MarginLayoutParams
             +   android.widget.RelativeLayout.LayoutParams

相対レイアウトのパラメーターを定義するクラス。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
android:layout_above
android:layout_alignBaseline
android:layout_alignBottom
android:layout_alignLeft
android:layout_alignParentBottom
android:layout_alignParentLeft
android:layout_alignParentRight
android:layout_alignParentTop
android:layout_alignRight
android:layout_alignTop
android:layout_alignWithParentIfMissing
android:layout_below
android:layout_centerHorizontal
android:layout_centerInParent
android:layout_centerVertical
android:layout_toLeftOf
android:layout_toRightOf
---

それぞれに対するメソッドはない。説明は上記を参照。


▲ アクティビティ

ボタンがクリックされたら、その状態によって、ボタンの幅と高さを倍にするか半分にす
る。

□ RelativeLayout01Activity.java

※ クラス名が上記の名前になっているだけで、コードは LinearLayout01Activity.java 
と同じ。

                                                                            以上


[Android] レイアウト (3) テーブル・レイアウト

2012年03月22日 | Android
[Android] レイアウト (3) テーブル・レイアウト
================================================================================

テーブル形式でウィジェットを配置するグリッド・モデルのレイアウトである。
TableLayout は TableRow と連動する。TableRow は、テーブルの行を規定する。

使い方としては、以下のとおり。

<TableLayout>
    <TableRow>
        ウィジェット
        ウィジェット
            ...
    </TableRow>

    <TableRow>
        ウィジェット
        ウィジェット
            ...
    </TableRow>

        ...
</TableLayout>


・行にセルを配置する

行の宣言は、TableRow で行う。
列数は Android が決定する。

android:layout_span 属性で、ウィジェットを複数の列に広げることができる。


・TableLayout の子要素

直接の子は TableRow 要素だけである。
ただし、TableRow 要素以外にも行と行の間に他のウィジェットを配置できる。その場合
は、横方向の LinearLayout と同様に振る舞う。


・伸縮とたたみ込み

android:stretchColumns 属性で、ここに指定した列は、行の空いているスペースいっぱ
いに広がる。この属性の値は単一の列番号またはコンマ区切りの列番号のリストで指定す
る。列番号は左の列から 0 から振られる。
実行時に、TableLayout.setColumnsStretchable() メソッドで制御できる。

android:shrinkColumns 属性で、ここに指定した列は、コンテンツを折り返して表示する。
値の指定は、android:stretchColumns 属性と同じ。
実行時に、TableLayout.setColumnsShrinkale() メソッドで制御できる。

android:collapseColumns 属性で、ここに指定した列は、最初はたたみ込まれた状態にな
る。これは最初は表示されない列となる。表示するには、TableLayout
.setColumnsCollapsed() メソッドを呼び出すことで表示される。


▲ レイアウト

□ 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" >

    <TableLayout
        android:id="@+id/tableLayout1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="5dip"
        android:weightSum="1" >

        <TableRow
            android:id="@+id/tableRow0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <ToggleButton
                android:id="@+id/button00"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[0][0]"
                android:textOn="[0][0]" />

            <ToggleButton
                android:id="@+id/button01"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[0][1]"
                android:textOn="[0][1]" />

            <ToggleButton
                android:id="@+id/button02"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[0][2]"
                android:textOn="[0][2]" />
        </TableRow>

        <TableRow
            android:id="@+id/tableRow1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <ToggleButton
                android:id="@+id/button10"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[1][0]"
                android:textOn="[1][0]" />

            <ToggleButton
                android:id="@+id/button11"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[1][1]"
                android:textOn="[1][1]" />

            <ToggleButton
                android:id="@+id/button12"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[1][2]"
                android:textOn="[1][2]" />
        </TableRow>

        <TableRow
            android:id="@+id/tableRow2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" >

            <ToggleButton
                android:id="@+id/button20"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_span="2"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[2][0]"
                android:textOn="[2][0]" />

            <ToggleButton
                android:id="@+id/button22"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:textOff="[2][2]"
                android:textOn="[2][2]" />
        </TableRow>
    </TableLayout>

</LinearLayout>
---

TableLayout の場合は、なるべく幅に入るように調整されるようだ。

TableLayout の属性は、そのスーパークラスである LinearLayout と同じ。


□ TableLayout クラス

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

テーブル形式のレイアウトを定義するクラス。
この子要素として TableRow を使うことで、行を定義する。

XML 属性としては、上記の「伸縮とたたみ込み」に関する属性がある。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
android:collapseColumns
android:shrinkColumns
android:stretchColumns
---

それぞれ、つぎのメソッドで動的に変更できる。
    setColumnCollapsed(int,boolean)
    setShrinkAllColumns(boolean)
    setStretchAllColumns(boolean)


□ TableRow クラス

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

テーブルの行を定義するクラス。
この子要素としてウィジェットを指定すると、それがテーブルの項目として定義される。

このクラスには、とくに XML 属性はない。行の属性としては、内部クラスの TableRow
.LayoutParams クラス で定義している。


□ TableRow.LayoutParams クラス

java.lang.Object
   +    android.view.ViewGroup.LayoutParams
        +    android.view.ViewGroup.MarginLayoutParams
             +    android.widget.LinearLayout.LayoutParams
                  +    android.widget.TableRow.LayoutParams

テーブルの行の属性を定義するクラス。


XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
layout_column    ビューの列数
layout_span      結合する列数
---

この属性に関するメソッドはない。


---
★?
TableLayout では、HTML の table 要素のような行の結合(rowspan)はできないようだ。
---


□ 表示
---
00        01        02

10        11        12

20                  22
---


▲ アクティビティ

□ TableLayout01Activity.java

※ クラス名が上記の名前になっているだけで、コードは LinearLayout01Activity.java と同じ。

                                                                            以上


[Android] レイアウト (2) LinearLayout レイアウト

2012年03月21日 | Android
[Android] レイアウト (2) LinearLayout レイアウト
================================================================================

LinearLayout は、ウィジェットを縦または横に並べて配置するボックス・モデルのレイア
ウト。
つぎの属性によって配置のしかたを決める。
    方向
    塗りつぶしモデル
    配置
    パディング


・方向

縦方向にウィジェットを並べるか、横方向に並べるかを示す。
    android:orientation="vertical" または "horizontal"

なにも記述しないときは横方向。


・塗りつぶしモデル

ウィジェットを並べたときに、ウィジェットの大きさをどうするかを示す。

横方向、縦方向に関して、それぞれ
        android:layout_width
        android:layout_height
属性で指定する。


・比率

そのウィジェットに割りあてられる領域の割合を示す。
    android:layout_weight


・配置

そのウィジェットの配置(左寄せ、センタリング、右寄せなど)を示す。
    android:gravity

---
★?
LinearLayout の API 上では android:layout_gravity だが、実際には android:gravity
 で設定するようだ。layout_gravity だとうまく配置できない。
たとえば、android:gravity="center" ならセンタリングしてくれるが、
android:layout_gravity="center" だと左寄せのまま。
---


指定できる値
---
top    
bottom  
left   
right   
center_vertical  
fill_vertical    
center_horizontal
fill_horizontal     
center  
fill    
clip_vertical
clip_horizontal
start   
end    
---


・パディング

ウィジェット間の余白を指定する。何も指定しなければ余白なし。
android:padding 属性をつけることで、上下左右にも余白をつけることができる。
また、上下左右個別につけることもできる。


属性
---
android:padding        上下左右のパディングをピクセル単位で設定
android:paddingBottom  下方向のパディングをピクセル単位で設定
android:paddingLeft    左方向のパディングをピクセル単位で設定
android:paddingRight   右方向のパディングをピクセル単位で設定
android:paddingTop     上方向のパディングをピクセル単位で設定
---

なお、つぎのメソッドでパディングを動的に設定できる。
    setPadding(int,int,int,int)


▲ レイアウト

テーブル形式にウィジェットを並べた。ウィジェットは、ToggleButton で、ON になると、
サイズが縦方向横方向それぞれ動的に 2 倍になる。OFF になればもとに戻る。
ただし、レイアウトによっては、見た目は 2 倍にならないときもある。

□ 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" >

    <LinearLayout
        android:id="@+id/tableLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dip" >

        <ToggleButton
            android:id="@+id/button00"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[0][0]¥nww-"
            android:textOn="[0][0]¥nww-" />

        <ToggleButton
            android:id="@+id/button01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[0][1]¥nww-"
            android:textOn="[0][1]¥nww-" />

        <ToggleButton
            android:id="@+id/button02"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[0][2]¥nmw-"
            android:textOn="[0][2]¥nmw-" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/tableLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dip" >

            <ToggleButton
            android:id="@+id/button10"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[1][0]¥nmw-"
            android:textOn="[1][0]¥nmw-" />

        <ToggleButton
            android:id="@+id/button11"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[1][1]¥nww-"
            android:textOn="[1][1]¥nww-" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/tableLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dip" >

        <ToggleButton
            android:id="@+id/button20"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:layout_weight="1"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[2][0]¥nww1"
            android:textOn="[2][0]¥nww1" />

        <ToggleButton
            android:id="@+id/button21"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:layout_weight="1"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[2][1]¥nww1"
            android:textOn="[2][1]¥nww1" />

        <ToggleButton
            android:id="@+id/button22"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[2][2]¥nww1"
            android:textOn="[2][2]¥nww1" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/tableLayout3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dip" >

        <ToggleButton
            android:id="@+id/button30"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:layout_weight="2"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[3][0]¥nww2"
            android:textOn="[3][0]¥nww2" />

        <ToggleButton
            android:id="@+id/button32"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[3][2]¥nww1"
            android:textOn="[3][2]¥nww1" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/tableLayout4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dip" >

        <ToggleButton
            android:id="@+id/button40"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:layout_weight="2"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[4][0]¥n0w2"
            android:textOn="[4][0]¥n0w2" />

        <ToggleButton
            android:id="@+id/button42"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[4][2]¥n0w1"
            android:textOn="[4][2]¥n0w1" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/tableLayout5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="5dip" >

        <ToggleButton
            android:id="@+id/button50"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:onClick="onClick"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:textOff="[5][0]¥nww-"
            android:textOn="[5][0]¥nww-" />
    </LinearLayout>

</LinearLayout>
---


□ LinearLayout.LayoutParams クラス

java.lang.Object
   +    android.view.ViewGroup.LayoutParams
        +    android.view.ViewGroup.MarginLayoutParams
             +    android.widget.LinearLayout.LayoutParams

LinearLayout で配置する場合のウィジェットに割り当てられる領域の割合などを設定す
る。

XML 属性(属性名の前に、名前空間プレフィックス android: がつく)
---
android:layout_gravity  ウィジェットの配置
android:layout_weight   そのウィジェットに割りあてられる領域の割合
---


□ android:layout_weight の使い方 (1) 

・android:layout_weight に "0" 以外の値を指定する。

・2 つの要素で、同じ 0 以外の値を指定すると領域が均等に二分

・ひとつ目で "1"、ふたつ目に "2" を指定すると、ふたつ目のウィジェットはひとつ目
の 2 倍の領域を使う


□ android:layout_weight の使い方 (2) 

割合に基づいてサイズを指定する。

水平レイアウトの場合

・レイアウト内のすべてのウィジェットで android:layout_width="0dp" とする

・レイアウト内のすべてのウィジェットで android:layout_weight の値を適切な割合に
する

・レイアウト内のすべてのウィジェットの android:layout_weight の値の合計が 100 に
なるようにする

実際には、合計が 100 にならなくても、全体の合計に対する割合で、領域の大きさを決
めてくれる。値も、整数値である必要はなく、実数値でもよい。


▲ アクティビティ

ボタンがクリックされたら、その状態によって、ボタンの幅と高さを倍にするか半分にす
る。

□ LinearLayout01Activity.java
---
package jp.marunomaruno.android.linearlayout;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.ToggleButton;
import jp.marunomaruno.android.linearlayout.R;

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

    public void onClick(View view) {
        ToggleButton button = (ToggleButton) view;
        if (button.isChecked()) {
            button.setWidth(button.getWidth() * 2);
            button.setHeight(button.getHeight() * 2);

        } else {
            button.setWidth(button.getWidth() / 2);
            button.setHeight(button.getHeight() / 2);

        }
    }
}
---

ボタンに表示される 3 ケタの記号は順番に、
    layout_width 値
    layout_height 値
    layout_weight 値
を意味し、数字はその値である。また、記号 w と m は、それぞれ
    w:    wrap_content
    m:    match_parent
を意味する。


□ 表示
---
00        01        02
ww-       ww-       mw-

10        11        12
mw-       ww-       mw-

20        21        22
ww1       ww1       ww1

30                  32
ww2                 ww1

40                  42
0w2                 0w1

          50
          ww-
---

[3x] と [4x] とでは、layout_width 属性に値が設定されているかどうかの違い。

                                                                            以上