2013-03-21 [C] 外部変数(グローバル変数)の定義(仮定義) ================================================================================ 何のキーワードもつけない外部変数を 2 つのモジュールで定義すると、片一方は自動的 に extern がついた扱いになる。 ただし、2 つとも初期値を設定すると、リンクで二重定義となりエラー。 これって C の仕様かな。それとも、gcc 固有の拡張か? (gcc 3.4) どうやら、C 言語の仕様。 仮定義というもの。リンケージ時に仮定義の中のひとつが定義になる。 ヘッダー・ファイルで外部変数を定義するときに、初期値をつけないことにしておけば、 extern をつけたり、つけなくしたりするための条件コンパイルや、マクロ定義は必要なく なるなあ。 □ global_test.c --- #include <stdio.h> #include <stdlib.h> void func(void); int global = 1234; // (1) int main(void) { printf("main: %d\n", global); func(); return EXIT_SUCCESS; } --- □ global_test2.c --- #include <stdio.h> #include <stdlib.h> void func(void); int global; // (2) void func(void) { printf("func: %d\n", global); } --- □ 実行結果 --- main: 1234 func: 1234 --- □ nm コマンドの結果 --- 00402000 D _global --- (1)と(2)の組合せで、nm コマンドの結果(シンボル・タイプ) ------------------- ----------- ------------------ (1)\(2) int global; int global = 1234; ------------------- ----------- ------------------ int global; B D int global = 1234; D (*) ------------------- ----------- ------------------ (*) リンケージでエラー(multiple definition of `global') nm コマンドは、指定されたライブラリ内に存在するシンボルのリストを表示する。 シンボル・タイプは以下のとおり。 T (コードセクション内の普通の定義) D (初期化されたデータセクション) B (初期化されないデータセクション) U (未定義。シンボルはライブラリによって使われているが、ライブラリ内では定義 されていない) W (weak. もしも他のライブラリも同じシンボルを定義している場合、その定義によ りオーバーライドされる) 参考: Program Library HOWTO > 5. 雑録 > 5.1. nm コマンド http://archive.linux.or.jp/JF/JFdocs/Program-Library-HOWTO/miscellaneous.html ついでに、 JIS X3010「プログラム言語C」6.9.2 外部オブジェクト定義 の例1 を示す。 □ global_tes3.c --- int i1 = 1; // 定義、外部結合 static int i2 = 2; // 定義、内部結合 extern int i3 = 3; // 定義、外部結合 int i4; // 仮定義、外部結合 static int i5; // 仮定義、内部結合 int i1; // 正しい仮定義、前の定義を参照する //int i2; // 前に内部結合を持つ定義があるため、結合の不一致が生じ、 // "6.2.2 識別子の結合" によって動作は未定義となる int i3; // 正しい仮定義、前の定義を参照する int i4; // 正しい仮定義、前の定義を参照する //int i5; // 前に内部結合を持つ定義があるため、結合の不一致が生じ、 // "6.2.2 識別子の結合" によって動作は未定義となる extern int i1; // 外部結合を持つ前の定義を参照する extern int i2; // 内部結合を持つ前の定義を参照する extern int i3; // 外部結合を持つ前の定義を参照する extern int i4; // 外部結合を持つ前の定義を参照する extern int i5; // 内部結合を持つ前の定義を参照する --- □ global_test4.c --- //int i1 = 1; // 定義、外部結合 ※二重定義になるのでコメントアウト static int i2 = 2; // 定義、内部結合 //extern int i3 = 3; // 定義、外部結合 ※二重定義になるのでコメントアウト int i4; // 仮定義、外部結合 static int i5; // 仮定義、内部結合 int i1; // 正しい仮定義、前の定義を参照する //int i2; // 前に内部結合を持つ定義があるため、結合の不一致が生じ、 // "6.2.2 識別子の結合" によって動作は未定義となる int i3; // 正しい仮定義、前の定義を参照する int i4; // 正しい仮定義、前の定義を参照する //int i5; // 前に内部結合を持つ定義があるため、結合の不一致が生じ、 // "6.2.2 識別子の結合" によって動作は未定義となる extern int i1; // 外部結合を持つ前の定義を参照する extern int i2; // 内部結合を持つ前の定義を参照する extern int i3; // 外部結合を持つ前の定義を参照する extern int i4; // 外部結合を持つ前の定義を参照する extern int i5; // 内部結合を持つ前の定義を参照する --- □ nm コマンドの結果 --- 00402004 D _i1 00402008 d _i2 00402000 d _i2 0040200c D _i3 004040b0 B _i4 00404020 b _i5 00404010 b _i5 --- シンボル・タイプで、小文字はそのシンボルがローカルであることを意味し、大文字はそ のシンボルがグローバル (外部定義) であることを意味する。 以上
2012-11-29 gprof: プロファイラ・プログラム ================================================================================ ■gprof gprof は、プロファイラとして、つぎのことを測定します。 ・コードの各セクションが消費する計算時間 コンパイル時に -pg オプションをつけることで使えるようになります。 ■gprof の使用法 1. コンパイル コンパイル時に -pg オプションをつけることで使えるようになります。 >gcc -Wall -pg prof_sample.c コンパイルすると、つぎのファイルができます。 prof_sample.gcno □ prof_sample.c --- #include <stdio.h> #include <stdlib.h> #include <time.h> #include <assert.h> #define MAX (10 * 1000) void sort(int a[], int size); void swapIfDesc(int a[], int i, int j); void swap(int a[], int i, int j); int main(void) { int a[MAX]; int i; puts("program start"); fflush(stdout); for (i = 0; i < MAX; ++i) { a[i] = rand(); } long start = clock(); sort(a, MAX); printf("%6ld ms¥n", (clock() - start)); fflush(stdout); for (i = 1; i < MAX; ++i) { assert(a[i-1] <= a[i]); } puts("program end"); fflush(stdout); return EXIT_SUCCESS; } void sort(int a[], int size) { int i, j; for (i = 0; i < size; ++i) { for (j = i; j < size; ++j) { swapIfDesc(a, i, j); } }; } void swapIfDesc(int a[], int i, int j) { if (a[i] >= a[j]) { swap(a, i, j); } } void swap(int a[], int i, int j) { int w = a[i]; a[i] = a[j]; a[j] = w; } ----------------------------------------------------------- 2. 実行 >a.exe --- program start 3062 ms program end --- 3. プロファイルを見る つぎのように、gprof コマンドを使って、測定した結果を見ます。 > gprof a.exe □ 実行結果 --- Flat profile: Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 53.21 0.83 0.83 50005000 0.00 0.00 swapIfDesc 33.33 1.35 0.52 1 0.52 1.56 sort 13.46 1.56 0.21 24964410 0.00 0.00 swap % the percentage of the total running time of the time program used by this function. (全体の総実行時間に対するその関数の総実行時間の占める割合) cumulative a running sum of the number of seconds accounted seconds for by this function and those listed above it. (その関数より上位に示されているすべての関数の総実行時間と, その関数の総実行時間の累積時間(秒)) self the number of seconds accounted for by this seconds function alone. This is the major sort for this listing. (その関数の総実行時間(秒)) calls the number of times this function was invoked, if this function is profiled, else blank. (その関数が呼び出された総回数) self the average number of milliseconds spent in this ms/call function per call, if this function is profiled, else blank. (その関数の一回の呼び出しに対する平均実行時間(ミリ秒)) total the average number of milliseconds spent in this ms/call function and its descendents per call, if this function is profiled, else blank. (その関数の一回の呼び出しに対する,その関数とその関数に呼び出された サブルーチンの平均実行時間(ミリ秒)) name the name of the function. This is the minor sort for this listing. The index shows the location of the function in the gprof listing. If the index is in parenthesis it shows where it would appear in the gprof listing if it were to be printed. Call graph (explanation follows) granularity: each sample hit covers 4 byte(s) for 0.64% of 1.56 seconds index % time self children called name 0.52 1.04 1/1 main [2] [1] 100.0 0.52 1.04 1 sort [1] 0.83 0.21 50005000/50005000 swapIfDesc [3] ----------------------------------------------- <spontaneous> [2] 100.0 0.00 1.56 main [2] 0.52 1.04 1/1 sort [1] ----------------------------------------------- 0.83 0.21 50005000/50005000 sort [1] [3] 66.7 0.83 0.21 50005000 swapIfDesc [3] 0.21 0.00 24964410/24964410 swap [4] ----------------------------------------------- 0.21 0.00 24964410/24964410 swapIfDesc [3] [4] 13.5 0.21 0.00 24964410 swap [4] ----------------------------------------------- This table describes the call tree of the program, and was sorted by the total amount of time spent in each function and its children. Each entry in this table consists of several lines. The line with the index number at the left hand margin lists the current function. The lines above it list the functions that called this function, and the lines below it list the functions this one called. This line lists: index A unique number given to each element of the table. Index numbers are sorted numerically. The index number is printed next to every function name so it is easier to look up where the function in the table. (関数につけられる連続した番号) % time This is the percentage of the `total' time that was spent in this function and its children. Note that due to different viewpoints, functions excluded by options, etc, these numbers will NOT add up to 100%. (全実行時間に対するその関数の総実行時間(その関数から呼び出された サブルーチンの実行時間も含む) の割合) self This is the total amount of time spent in this function. (その関数の総実行時間) children This is the total amount of time propagated into this function by its children. (その関数に呼び出されたサブルーチンの総実行時間) called This is the number of times the function was called. If the function called itself recursively, the number only includes non-recursive calls, and is followed by a `+' and the number of recursive calls. (その関数が呼び出された総回数) name The name of the current function. The index number is printed after it. If the function is a member of a cycle, the cycle number is printed between the function's name and the index number. For the function's parents, the fields have the following meanings: self This is the amount of time that was propagated directly from the function into this parent. children This is the amount of time that was propagated from the function's children into this parent. called This is the number of times this parent called the function `/' the total number of times the function was called. Recursive calls to the function are not included in the number after the `/'. name This is the name of the parent. The parent's index number is printed after it. If the parent is a member of a cycle, the cycle number is printed between the name and the index number. If the parents of the function cannot be determined, the word `<spontaneous>' is printed in the `name' field, and all the other fields are blank. For the function's children, the fields have the following meanings: self This is the amount of time that was propagated directly from the child into the function. children This is the amount of time that was propagated from the child's children to the function. called This is the number of times the function called this child `/' the total number of times the child was called. Recursive calls by the child are not listed in the number after the `/'. name This is the name of the child. The child's index number is printed after it. If the child is a member of a cycle, the cycle number is printed between the name and the index number. If there are any cycles (circles) in the call graph, there is an entry for the cycle-as-a-whole. This entry shows who called the cycle (as parents) and the members of the cycle (as children.) The `+' recursive calls entry shows the number of function calls that were internal to the cycle, and the calls entry for each member shows, for that member, how many times it was called from other members of the cycle. Index by function name [1] sort [4] swap [3] swapIfDesc --- □ オプション -b 説明部分を省略 -p Flat profile のみを出力 -P Call graph のみを出力 -c 関数内部で使われる関数についての使用状況も出力 -z 実行時に使っていない関数や時間を計測できない程度の関数も、使用状況を出力 -e 関数名 指定した関数の出力を結果から除く 以上
自分自身のインスタンスを定数で持つクラス ================================================================================ 自分自身のインスタンスを定数で持つクラスのサンプル。 メンバー変数は、たんに int 型の値を持っているだけ。 コンストラクターをすべて private にすれば、他から直接インスタンスを生成できない ようにすることもできる。 □ Item.h --- #ifndef ITEM_H_ #define ITEM_H_ #include <iostream> class Item { public: static const Item* ITEM1; // (1) static const Item* ITEM2; // (1) static const Item* ITEM_LIST[]; // (2) static const int ITEM_LIST_SIZE; // (3) private: int value; public: Item(); Item(int value); virtual ~Item(); int getValue() const; }; #endif /* ITEM_H_ */ --- (1) 自分自身のオブジェクトの定数の宣言 static const Item* ITEM1; // (1) static const Item* ITEM2; // (1) (2)(3) オブジェクト定数配列の宣言とその要素数の宣言 オブジェクト定数を配列で持っていることで、使い勝手が向上するかもしれない。 そのときの要素数も持たせておく。 static const Item* ITEM_LIST[]; // (2) static const int ITEM_LIST_SIZE; // (3) ここで、ITEM_LIST_SIZE は int 型だから、 static const int ITEM_LIST_SIZE = sizeof(Item::ITEM_LIST) / sizeof(Item::ITEM_LIST[0]); とすると、つぎのメッセージでコンパイルエラーとなる。 invalid application of `sizeof' to incomplete type `const Item*[]' この時点では、ITEM_LIST の要素が確定していないため。 □ Item.cpp --- #include <iostream> #include <cassert> #include "Item.h" const Item* Item::ITEM1 = new Item(10); // (1) const Item* Item::ITEM2 = new Item(20); // (1) const Item* Item::ITEM_LIST[] = { ITEM1, ITEM2 }; // (2) const int Item::ITEM_LIST_SIZE = sizeof(Item::ITEM_LIST) / sizeof(Item::ITEM_LIST[0]); // (3) Item::Item() { assert(true); } Item::Item(int value) : value(value) { assert(true); } Item::~Item() { assert(true); } int Item::getValue() const { return value; } --- (1) 自分自身のオブジェクトの定数の定義 const Item* Item::ITEM1 = new Item(10); // (1) const Item* Item::ITEM2 = new Item(20); // (1) (2) オブジェクト定数配列の定義 const Item* Item::ITEM_LIST[] = { ITEM1, ITEM2 }; // (2) (3) オブジェクト定数配列の要素数の定義 const int Item::ITEM_LIST_SIZE = sizeof(Item::ITEM_LIST) / sizeof(Item::ITEM_LIST[0]); // (3) □ ItemTest.cpp --- #include <iostream> using namespace std; #include "Item.h" int main() { cout << Item::ITEM1->getValue() << endl; cout << Item::ITEM2->getValue() << endl; for (int i = 0; i < Item::ITEM_LIST_SIZE; ++i) { cout << Item::ITEM_LIST[i]->getValue() << endl; } Item item; cout << item.getValue() << endl; Item item2(30); cout << item2.getValue() << endl; return 0; } --- □ 実行結果 --- 10 20 10 20 0 30 --- 以上
■ [C++][STL] vector にオブジェクトを入れるときのクラスの注意点 ・vector にオブジェクトを入れるときに、そのコピーコンストラクターの引数は const でなければならない。 const にしないとビルドエラーになる。 うっかり付け忘れても、普通に使っている分には問題ないかもしれないが、vector なんかに入れようとすると怒られる。 ついでに、vector に入れるためには、コピーコンストラクターだけではなく、引数のないコンストラクター、デストラクターが必要。また、自分でコピーコンストラクターを作らないといけない状況の場合は、代入演算子のオーバーロードも必要になってくる。 そういえば、前、関係演算子(== や <) もオーバーロードが必要なものがあったような、なかったような。。。vector の場合は、なくても平気かな。 □ vector_sample.cpp --- #include <iostream> #include <vector> using namespace std; class A { public: A() {} A(A& r) {} // A(const A& r) ならOK }; int main() { vector<A> v; v.push_back(A()); // no matching function for call to `A::A(A)' to `A::A(const A&)' return 0; } --- □ ビルド結果 --- **** Build of configuration Debug for project vector_sample **** **** Internal Builder is used for build **** g++ -O0 -g3 -Wall -c -fmessage-length=0 -o src/vector_sample.o ../src/vector_sample.cpp ../src/vector_sample.cpp: In function `int main()': ../src/vector_sample.cpp:21: error: no matching function for call to `A::A(A)' ../src/vector_sample.cpp:16: note: candidates are: A::A(A&) C:/MinGW/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/stl_construct.h: In function `void std::_Construct(_T1*, const _T2&) [with _T1 = A, _T2 = A]': C:/MinGW/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/stl_vector.h:560: instantiated from `void std::vector<_Tp, _Alloc>::push_back(const _Tp&) [with _Tp = A, _Alloc = std::allocator<A>]' ../src/vector_sample.cpp:21: instantiated from here C:/MinGW/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/stl_construct.h:81: error: no matching function for call to `A::A(const A&)' ../src/vector_sample.cpp:16: note: candidates are: A::A(A&) ../src/vector_sample.cpp:15: note: A::A() C:/MinGW/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/vector.tcc: In member function `void std::vector<_Tp, _Alloc>::_M_insert_aux(__gnu_cxx::__normal_iterator<typename _Alloc::pointer, std::vector<_Tp, _Alloc> >, const _Tp&) [with _Tp = A, _Alloc = std::allocator<A>]': C:/MinGW/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/stl_vector.h:564: instantiated from `void std::vector<_Tp, _Alloc>::push_back(const _Tp&) [with _Tp = A, _Alloc = std::allocator<A>]' ../src/vector_sample.cpp:21: instantiated from here C:/MinGW/bin/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/bits/vector.tcc:234: error: no matching function for call to `A::A(const A&)' ../src/vector_sample.cpp:16: note: candidates are: A::A(A&) Build error occurred, build is stopped Time consumed: 1047 ms. --- 以上
[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] 日付関係のユーティリティ ================================================================================ 日付関係のユーティリティとして、次のようなものを作った。 関数の概要 --- 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; } --- 以上
[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] ○×ゲーム ================================================================================ ○×ゲーム。 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) オブジェクト指向 ================================================================================ [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) シンプル ================================================================================ インベーダー・ゲームを作ってみる。 ゲームの概要: ・画面上部にインベーダーがあり、これが左右に動く(端まで行ったら折り返す程度) ・画面下部に砲台があり、ユーザーのタッチムーブ(ドラッグ)によって左右に動く ・画面をタッチすると、砲台から弾が上部向けてに発射する ・弾がインベーダーに当たると得点する ゲーム自体は、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] 簡易電卓アプリ ================================================================================ 単純な機能の電卓アプリ。 つぎのように、単純な機能しか持たない。 ・ひとつの演算子に対して必ず「=」を押す ・連続して複数の演算子を使えない ・「=」の後も続けて計算はできない 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 ================================================================================ 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 ================================================================================ 他のレイアウトの 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 レイアウト ================================================================================ 他のウィジェットの横や下にどの程度離れて配置するか、を示すルールベース・モデルの レイアウト。 他のウィジェットとの相対位置を基にして、自分の位置を決める。 他のウィジェットは、自分の隣りである必要はなく、離れていても 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) テーブル・レイアウト ================================================================================ テーブル形式でウィジェットを配置するグリッド・モデルのレイアウトである。 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 と同じ。 以上