[Android] フォームの標準ウィジェット(1) 静的な選択関連 ================================================================================ ウィジェットは、ホーム画面に追加される小さな機能。 Android では、標準で、いくつかのウィジェットを用意している。これらを使って、デー タを表示したり、入力したり、選択したりすることができる。 選択する以下の ウィジェット(Widget)とそのイベント処理。 ・ボタン ・チェックボックス ・トグルボタン ・ラジオボタン ・スピナー ・シークバー(別記) ・レーティングバー(別記) 上記のウィジェットは、それぞれつぎのクラスを使う。また、そのときに使われるリス ナー・インターフェースも示す。 ------------------ -------------- ---------------------------------------------- ウィジェット クラス リスナー・インターフェース ------------------ -------------- ---------------------------------------------- ボタン Button View.OnClickListener, View.OnLongClickListener View.OnTouchListener チェックボックス CheckBox CompoundButton.OnCheckedChangeListener View.OnClickListener, View.OnLongClickListener View.OnTouchListener トグルボタン ToggleButton CompoundButton.OnCheckedChangeListener View.OnClickListener, View.OnLongClickListener View.OnTouchListener ラジオボタン RadioGroup RadioGroup.OnCheckedChangeListener RadioButton スピナー Spinner AdapterView.OnItemSelectedListener View.OnTouchListener シークバー SeekBar SeekBar.OnSeekBarChangeListener View.OnTouchListener レーティングバー RatingBar RatingBar.OnRatingBarChangeListener View.OnTouchListener ------------------ -------------- ---------------------------------------------- このサンプルは、アプリケーションの起動時に選択する値が決まっているパターンを示す。 ▲ レイアウト □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <!-- (1) ボタン --> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <!-- (2) チェックボックス --> <CheckBox android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CheckBox" /> <!-- (3) トグルボタン --> <ToggleButton android:id="@+id/toggleButton1" android:layout_width="180dp" android:layout_height="wrap_content" android:text="ToggleButton" /> <!-- (4) ラジオボタン --> <RadioGroup android:id="@+id/radioGroup1" android:layout_width="wrap_content" android:layout_height="wrap_content" > <RadioButton android:id="@+id/radio0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="@string/listItem0" /> <!-- (5) --> <RadioButton android:id="@+id/radio1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/listItem1" /> <RadioButton android:id="@+id/radio2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/listItem2" /> </RadioGroup> <!-- (6) スピナー --> <Spinner android:id="@+id/spinner1" android:layout_width="match_parent" android:layout_height="wrap_content" android:entries="@array/list1" /> </LinearLayout> --- (1) ボタン クリックする、を実現している基本的なユーザー・インターフェースになっているビュー。 クリック時、長いクリック(長押し)時の処理をハンドラーとしてプログラムする。また、 タッチ時の処理もハンドリングできる。 <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> ボタンに限らず、GUI 部品の属性には、android:onClick 属性があり、クリックされたと きに動くハンドラー・メソッドを指定することができる。 今回は、リスナー・クラスを用意して、それぞれの部品の setOnXxxListener() メソッド でリスナーを登録するので、ここでは指定はしていない。 (2) チェックボックス チェックしている状態とチェックしていない状態の 2 つの状態を持つビュー。 チェックボックスをクリックするたびに、この 2 つの状態が入れ替わる。 <CheckBox android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CheckBox" /> android:checked 属性が "true" のとき、最初からチェックがついている状態になる。 (3) トグルボタン トグルボタンは、クリックするたびに状態が OFF/ON を繰り返す種類のボタンである。 <ToggleButton android:id="@+id/toggleButton1" android:layout_width="180dp" android:layout_height="wrap_content" android:text="ToggleButton" /> android:checked 属性が "true" のとき、最初から ON の状態になる。 android:text 要素に値を設定しても表示としては変わらない。表示の文字列をデフォル トの「OFF」「ON」から変更するには、android:textOff 属性と、android:textOn 属性を 使う。 (4)(5) ラジオボタン チェックボックスのように、チェックしている状態とチェックしていない状態の 2 つの 状態を持つビュー。 ただし、グループ化することで、そのグループの中ではひとつだけがチェックしている状 態になる。 同じグループは、RadioGroup 要素で囲む。 <RadioGroup android:id="@+id/radioGroup1" android:layout_width="wrap_content" android:layout_height="wrap_content" > <RadioButton android:id="@+id/radio0" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="true" android:text="@string/listItem0" /> <!-- (5) --> android:checked 属性が "true" の項目があれば、それが最初からチェックがついている 状態になる。 (6) スピナー 選択リスト。複数の項目からひとつを選択できるのは、ラジオボタンと同じ。 アプリ起動時に、このリスナーのハンドラーが動く。 <Spinner android:id="@+id/spinner1" android:layout_width="match_parent" android:layout_height="wrap_content" android:entries="@array/list1" /> なにも設定しないと、リストの先頭のものが選択された状態になる。 android:entries 属性に、表示する項目のリストを設定する。 この例のように静的な場合は、リソースの <string-array> 要素で設定している。 ※ なお、最初に選択する項目の指定は、プログラムで指定するしかなさそう。 Spinner.setSelection() に相当する XML 属性がない。 ▲リソース値 □ res/values/strings.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">FormWidgets01</string> <string name="listItem0">アイスクリーム</string> <string name="listItem1">かりんとう</string> <string name="listItem2">さつまいも</string> <string-array name="list1"> <item>@string/listItem0</item> <item>@string/listItem1</item> <item>@string/listItem2</item> </string-array> </resources> --- string-array 要素を使って、Spinner で使う項目を設定している。 ▲ アクティビティ 単に、Widget にあわせたリスナーを割り当てているだけである。 なお、リスナーはインナー・クラスを使っている。 どのハンドラーが使われたかは、トーストとログで表示している。 □ FormWidgets01Activity.java package jp.marunomarun.android.formwidgets; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.AdapterView; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.RatingBar; import android.widget.SeekBar; import android.widget.Spinner; import android.widget.Toast; import android.widget.ToggleButton; public class FormWidgets01Activity extends Activity { private static final int TOAST_DURATION = Toast.LENGTH_SHORT; private Context context; // (1) /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); context = this; // (2) // ボタン Button button1 = (Button) findViewById(R.id.button1); OnCheckAndLongClickListener clickListener = new OnCheckAndLongClickListener(); OnTouchListener touchListener = new OnTouchListener(); button1.setOnClickListener(clickListener); button1.setOnLongClickListener(clickListener); button1.setOnTouchListener(touchListener); // チェックボックス CheckBox check1 = (CheckBox) findViewById(R.id.checkBox1); check1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener()); check1.setOnClickListener(clickListener); check1.setOnLongClickListener(clickListener); check1.setOnTouchListener(touchListener); // トグルボタン ToggleButton toggle1 = (ToggleButton) findViewById(R.id.toggleButton1); toggle1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener()); toggle1.setOnClickListener(clickListener); toggle1.setOnLongClickListener(clickListener); toggle1.setOnTouchListener(touchListener); // ラジオボタン RadioGroup group1 = (RadioGroup) findViewById(R.id.radioGroup1); group1.setOnCheckedChangeListener(new OnCheckRadioGroupListener()); group1.setOnClickListener(clickListener); // 動かないだけ group1.setOnLongClickListener(clickListener); // 動かないだけ group1.setOnTouchListener(touchListener); // 動かないだけ // スピナー Spinner spinner1 = (Spinner) findViewById(R.id.spinner1); spinner1.setOnItemSelectedListener(new OnItemSelectedSpinnerListener()); // spinner1.setOnClickListener(listener); // RuntimeException) // spinner1.setOnLongClickListener(listener); // RuntimeException spinner1.setOnTouchListener(touchListener); } /** * ボタンのクリック、長クリックに対するリスナー * * @author marunomaruno */ private class OnCheckAndLongClickListener implements View.OnClickListener, View.OnLongClickListener { @Override public void onClick(View v) { String message = String.format( "OnClickListener.onClick() view: %s", v.getTag()); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); } @Override public boolean onLongClick(View v) { String message = String.format( "OnLongClickListener.onLongClick() view: %s", v.getTag()); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); return false; } } /** * タッチに対するリスナー * * @author marunomaruno */ private class OnTouchListener implements View.OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { String message = String.format( "OnTouchListener.onTouch() view: %s, event: %s", v.getTag(), event); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); return false; } } /** * チェックボックス、トグルボタンのクリックに対するリスナー * * @author marunomaruno */ private class OnCheckCompoundButtonListener implements CompoundButton.OnCheckedChangeListener { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { String message = String.format( "OnCheckedChangeListener.onCheckedChanged(): view: %s, %b", buttonView.getTag(), isChecked); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); } } /** * ラジオボタンのクリックに対するリスナー * * @author marunomaruno */ private class OnCheckRadioGroupListener implements RadioGroup.OnCheckedChangeListener { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { RadioButton button = (RadioButton) findViewById(checkedId); String message = String.format( "OnCheckedChangeListener.onCheckedChanged(): %s", button .getText()); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); } } /** * スピナーの選択に対するリスナー * * @author marunomaruno */ private class OnItemSelectedSpinnerListener implements AdapterView.OnItemSelectedListener { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { Spinner spinner = (Spinner) parent; String item = (String) spinner.getSelectedItem(); String message = String .format("OnItemSelectedListener.onItemSelected() parent: %s, view: %s, %d: %s", parent.getTag(), view.getTag(), position, item); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); } @Override public void onNothingSelected(AdapterView<?> parent) { String message = String.format( "OnItemSelectedListener.onNothingSelected() parent: %s", parent.getTag()); System.out.println(message); Toast.makeText(context, message, TOAST_DURATION).show(); } } --- (1)(2) コンテキスト・オブジェクト 内部クラスでトーストを使っているので、このアクティビティ自身(this オブジェクト) を持っていたほうが都合がよい。 private Context context; // (1) context = this; // (2) (●) ボタン クリックする、を実現している基本的なユーザー・インターフェースになっているビュー。 クリック、長押し時の処理をハンドラーとしてプログラムする。また、タッチ時の処理も ハンドリングできる。 Button button1 = (Button) findViewById(R.id.button1); button1.setOnClickListener(clickListener); button1.setOnLongClickListener(clickListener); button1.setOnTouchListener(touchListener); クリックは setOnClickListener()、長押しは setOnLongClickListener()、タッチは setOnTouchListener() を使ってそれぞれのリスナー・オブジェクトを設定する。 クリックのときは、OnTouchListener.onTouch() の ACTION_DOWN イベント、そして ACTION_UP が動く。 この戻り値が false のとき、OnClickListener.onClick()。 まとめると、ACTION_DOWN > ACTION_UP > onClick --- OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fc70 action=0 ...} OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fc70 action=1 ...} OnClickListener.onClick() view: Button --- 長押しのときは、クリックに加えて、ACTION_MOVE も動く。 まとめると、ACTION_DOWN > ACTION_MOVE > onLongClick > ACTION_UP > onClick --- OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fb60 action=0 ...} OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fb60 action=2 ...} OnLongClickListener.onLongClick() view: Button OnTouchListener.onTouch() view: Button, event: MotionEvent{46c1fb60 action=1 ...} OnClickListener.onClick() view: Button --- △ Button クラス java.lang.Object + android.view.View + android.widget.TextView + android.widget.Button クリックして何らかの動作をさせるためのウィジェット。 Button クラス独自のメソッドはなく、スーパークラスである TextView クラスのメソッ ドをそのまま利用している。 クリックに関しては、通常のクリックと、長クリック(長押し)の 2 つの種類がある。 --- void setOnClickListener(View.OnClickListener l) void setOnLongClickListener(View.OnLongClickListener l) void setOnTouchListener(View.OnTouchListener l) --- (*1) TextView のメソッド △ View.OnClickListener インターフェース android.view.View.OnClickListener クリックに関するリスナー・インターフェース。 このハンドラー・メソッドの onClick() の指定は、ウィジェット上で頻繁に使うことが 多いので、レイアウトの XML ファイルでも、android:onClick 属性として用意されてい る。 ・メソッド --- abstract void onClick(View v) --- △ View.OnLongClickListener インターフェース android.view.View.OnLongClickListener 長押しに関するリスナー・インターフェース。 ・メソッド --- abstract boolean onLongClick(View v) --- (●) チェックボックス チェックしている状態とチェックしていない状態の 2 つの状態を持つビュー。 チェックボックスをクリックするたびに、この 2 つの状態が入れ替わる。 CheckBox check1 = (CheckBox) findViewById(R.id.checkBox1); check1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener()); check1.setOnClickListener(clickListener); check1.setOnLongClickListener(clickListener); check1.setOnTouchListener(touchListener); クリック時の動き まとめると、ACTION_DOWN > ACTION_UP > onCheckedChanged > onClick --- OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fc70 action=0 ...} OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fc70 action=1 ...} OnCheckedChangeListener.onCheckedChanged(): view: CheckBox, false OnClickListener.onClick() view: CheckBox --- 長押し時の動き まとめると、ACTION_DOWN > ACTION_MOVE > onLongClick > ACTION_UP > onCheckedChanged > onClick --- OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=0 ...] OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=2 ...} OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=2 ...} OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=2 ...} OnLongClickListener.onLongClick() view: CheckBox OnTouchListener.onTouch() view: CheckBox, event: MotionEvent{46c1fb60 action=1 ...} OnCheckedChangeListener.onCheckedChanged(): view: CheckBox, false OnClickListener.onClick() view: CheckBox --- △ CheckBox クラス java.lang.Object + android.view.View + android.widget.TextView + android.widget.Button + android.widget.CompoundButton + android.widget.CheckBox チェックする・外すことができるウィジェット。 ・メソッドなどは、スーパークラスである CompoundButton クラスのメソッドを利用して いる。CheckBox として使うのは、以下のものが主。 --- boolean isChecked() void setChecked(boolean checked) void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) void toggle() --- △ CompoundButton クラス java.lang.Object + android.view.View + android.widget.TextView + android.widget.Button + android.widget.CompoundButton CheckBox や RadioButton、ToggleButton クラスなどの、ボタンをクリックするとそのボ タンの状態も変化する種類のクラスのスーパークラス。 サブクラスとして、次のクラスがある。 CheckBox, RadioButton, Switch, ToggleButton ・主なメソッド --- boolean isChecked() void setButtonDrawable(int resid) void setButtonDrawable(Drawable d) void setChecked(boolean checked) void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) void toggle() --- △ CompoundButton.OnCheckedChangeListener インターフェース android.widget.CompoundButton.OnCheckedChangeListener ボタンの状態が変化したときのリスナー・インターフェース。 ・メソッド --- abstract void onCheckedChanged(CompoundButton buttonView, boolean isChecked) --- isChecked: チェックされていれば true (●) トグルボタン トグルボタンは、クリックするたびに状態が OFF/ON を繰り返す種類のボタンである。 ToggleButton toggle1 = (ToggleButton) findViewById(R.id.toggleButton1); toggle1.setOnCheckedChangeListener(new OnCheckCompoundButtonListener()); toggle1.setOnClickListener(clickListener); toggle1.setOnLongClickListener(clickListener); toggle1.setOnTouchListener(touchListener); クリック時の動き まとめると、ACTION_DOWN > ACTION_UP > onCheckedChanged > onClick --- OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fc70 action=0 ...} OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fc70 action=1 ...} OnCheckedChangeListener.onCheckedChanged(): view: ToggleButton, false OnClickListener.onClick() view: ToggleButton --- 長押し時の動き まとめると、ACTION_DOWN > ACTION_MOVE > onLongClick > ACTION_UP > onCheckedChanged > onClick --- OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fb60 action=0 ...} OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fb60 action=2 ...] OnLongClickListener.onLongClick() view: ToggleButton OnTouchListener.onTouch() view: ToggleButton, event: MotionEvent{46c1fb60 action=1 ...] OnCheckedChangeListener.onCheckedChanged(): view: ToggleButton, false OnClickListener.onClick() view: ToggleButton --- △ ToggleButton クラス java.lang.Object + android.view.View + android.widget.TextView + android.widget.Button + android.widget.CompoundButton + android.widget.ToggleButton トグル・ボタンをあらわすクラス。何も指定しなければ、ボタンに表示されるテキストは 「OFF」と「ON」で、図形としてはライトが消えている状態とついている状態。 ・主なメソッド --- CharSequence getTextOff() CharSequence getTextOn() void setChecked(boolean checked) void setTextOff(CharSequence textOff) void setTextOn(CharSequence textOn) void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener listener) --- (●) ラジオボタン チェックボックスのように、チェックしている状態とチェックしていない状態の 2 つの 状態を持つビュー。 ただし、グループ化することで、そのグループの中ではひとつだけがチェックしている状 態になる。 グループ化は RadioGroup クラスを使う。 RadioGroup group1 = (RadioGroup) findViewById(R.id.radioGroup1); group1.setOnCheckedChangeListener(new OnCheckRadioGroupListener()); group1.setOnClickListener(clickListener); // 動かないだけ group1.setOnLongClickListener(clickListener); // 動かないだけ group1.setOnTouchListener(touchListener); // 動かないだけ 選択したときには、とくに OnClickListener.onClick()、OnLongClickListener .onLongClick()、OnTouchListener.onTouch() が動いていない。これは、RadioGroup で はなく、RadioButton オブジェクトで設定するもの。 選択時の動き --- OnCheckedChangeListener.onCheckedChanged(): かりんとう OnCheckedChangeListener.onCheckedChanged(): さつまいも --- △ RadioGroup クラス java.lang.Object + android.view.View + android.view.ViewGroup + android.widget.LinearLayout + android.widget.RadioGroup RadioButton オブジェクトを持つクラス。 ・主なメソッド --- void check(int id) void clearCheck() int getCheckedRadioButtonId() チェックされているボタンがなければ -1 が返る void setOnCheckedChangeListener(RadioGroup.OnCheckedChangeListener listener) --- △ RadioButton クラス java.lang.Object + android.view.View + android.widget.TextView + android.widget.Button + android.widget.CompoundButton + android.widget.RadioButton 個個のラジオボタンを管理するクラス。 ・主なメソッド --- void toggle() --- △ RadioGroup.OnCheckedChangeListener インターフェース android.widget.RadioGroup.OnCheckedChangeListener ラジオボタンのチェック状態が変化したときのリスナー。 ・メソッド --- abstract void onCheckedChanged(RadioGroup group, int checkedId) --- checkedId: チェックされた ラジオボタンのリソース ID (●) スピナー 選択リスト。複数の項目からひとつを選択できるのは、ラジオボタンと同じ。 アプリ起動時に、このリスナーのハンドラーが動く。 --- OnItemSelectedListener.onItemSelected() parent: Spinner, view: null, 0: アイスクリーム --- Spinner spinner1 = (Spinner) findViewById(R.id.spinner1); spinner1.setOnItemSelectedListener(new OnItemSelectedSpinnerListener()); // spinner1.setOnClickListener(clickListener); // RuntimeException) // spinner1.setOnLongClickListener(clickListener); // RuntimeException spinner1.setOnTouchListener(touchListener); 上のサンプルコードにあるように、setOnClickListener()、setOnLongClickListener() を設定すると、実行時に RuntimeException がスローされる。 選択時の動き まとめると、ACTION_DOWN > ACTION_UP > onItemSelected --- OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c1fc70 action=0 ...} OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c41208 action=1 ...} OnItemSelectedListener.onItemSelected() parent: Spinner, view: null, 1: かりんとう OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c41208 action=0 ...} OnTouchListener.onTouch() view: Spinner, event: MotionEvent{46c41208 action=1 ...} OnItemSelectedListener.onItemSelected() parent: Spinner, view: null, 2: さつまいも --- △ Spinner クラス java.lang.Object + android.view.View + android.view.ViewGroup + android.widget.AdapterView<T extends android.widget.Adapter> + android.widget.AbsSpinner + android.widget.Spinner 選択リストを管理するクラス。 ・主なメソッド --- int getBaseline() CharSequence getPrompt() void onClick(DialogInterface dialog, int which) boolean performClick() void setAdapter(SpinnerAdapter adapter) void setEnabled(boolean enabled) void setGravity(int gravity) void setOnItemClickListener(AdapterView.OnItemClickListener l) void setPrompt(CharSequence prompt) void setPromptId(int promptId) --- △ AdapterView クラス java.lang.Object + android.view.View + android.view.ViewGroup + android.widget.AdapterView<T extends android.widget.Adapter> 配列やリストからの値を管理できるタイプのビューのスーパークラス。 直接のサブクラスではないが、つぎのクラスがこのクラスを継承している。 GridView ListView Spinner ・項目選択関係のメソッド --- Object getSelectedItem() long getSelectedItemId() int getSelectedItemPosition() --- なお、このビューには、setOnClickListener() や setOnLongClickListener() で設定す るリスナーは設定できない。実行すると、つぎのメッセージの例外がスロー。 java.lang.RuntimeException: Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead △ AdapterView.OnItemSelectedListener インターフェース 項目を選択したときのリスナー・インターフェース。 アプリケーション起動時に最初に表示されている項目に対して、まず、このリスナーが動 く。 android.widget.AdapterView.OnItemSelectedListener ・メソッド --- abstract void onItemSelected(AdapterView<?> parent, View view, int position, long id) abstract void onNothingSelected(AdapterView<?> parent) --- 以上
[Android] データの取り扱い - ArrayAdapter ================================================================================ データベースから取得したデータを、ListView を使って表示する。 このとき、ArrayAdapter クラスをカスタマイズすることで、オブジェクトの配列やリス ト (List) から、自動的に値を ListView に展開してリスト表示することができる。 基本的な設定・手順は以下のとおり。 ・表示したいレイアウト(main.xml)で、ListView を設定 ・1 行分のレイアウト(row.xml)を設定 ・オブジェクトを設定するためのクラス(ArrayAdapter を継承)を作成 ・コンストラクターで、オブジェクト配列(リスト)を指定 ・getView() メソッドをオーバーライドして、1 行分の項目を紐付け ・ListView.setAdapter() メソッドで、アダプターを ListView に設定 ▲ アクティビティ 変更点は、以下の 2 箇所のみ。 ・ArrayAdapter のサブクラス(ItemListAdapter)のコンストラクターで、row.xml とオブ ジェクトのリストを紐付け ItemListAdapter<Item> adapter = new ItemListAdapter<Item>(this, R.layout.row, itemList); // (1) ・ListView.setAdapter() メソッドで、アダプターを ListView に設定 ListView listView = (ListView) findViewById(R.id.listView1); listView.setAdapter(adapter); // (2) □ Database02Activity.java --- package jp.marunomaruno.android.database; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; public class Database02Activity extends Activity { private EditText name = null; private EditText comment = null; private DatabaseHelper dbHelper = null; private int TOAST_DURATION = Toast.LENGTH_SHORT; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (EditText) findViewById(R.id.name); comment = (EditText) findViewById(R.id.comment); dbHelper = new DatabaseHelper(this); } @Override public void onPause() { super.onPause(); if (dbHelper != null) { // dbHelper.close(); } } public void onFindButtonClick(View view) { List<Item> itemList = dbHelper.select(name.getText()); if (itemList.size() <= 0) { Toast.makeText(this, getString(R.string.notFoundMessage), TOAST_DURATION).show(); return; } name.setText(itemList.get(0).getName()); comment.setText(itemList.get(0).getComment()); ItemListAdapter adapter = new ItemListAdapter(this, itemList); // (1) ListView listView = (ListView) findViewById(R.id.listView1); listView.setAdapter(adapter); // (2) Toast.makeText( this, String.format(getString(R.string.findMultipleMessage), itemList .size()), TOAST_DURATION).show(); } public void onSaveButtonClick(View view) { long id = dbHelper.insert(name.getText(), comment.getText()); name.setText(""); comment.setText(""); if (id > 0) { Toast.makeText(this, String.format(getString(R.string.saveMessage), id), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notSaveMessage), TOAST_DURATION).show(); } } public void onUpdateButtonClick(View view) { int count = dbHelper.update(name.getText(), comment.getText()); if (count > 0) { Toast.makeText(this, String.format(getString(R.string.updateMessage), count), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notUpdateMessage), TOAST_DURATION).show(); } } public void onRemoveButtonClick(View view) { int count = dbHelper.delete(name.getText()); name.setText(""); comment.setText(""); if (count > 0) { Toast.makeText(this, String.format(getString(R.string.removeMessage), count), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notRemoveMessage), TOAST_DURATION).show(); } } } --- (1) ArrayAdapter のサブクラス(ItemListAdapter)のコンストラクターで、オブジェクト のリストを設定 ItemListAdapter adapter = new ItemListAdapter(this, itemList); // (1) (2) ListView.setAdapter() メソッドで、アダプターを ListView に設定 ListView listView = (ListView) findViewById(R.id.listView1); listView.setAdapter(adapter); // (2) ・ListView クラスのアダプターの設定メソッド --- void setAdapter(ListAdapter adapter) --- ▲ アダプター ListView の各行に、リストや配列の要素を割り当てるためのアダプター。 項目が複数あるような場合は、ArrayAdapter クラスを継承して、カスタマイズしたアダ プター・クラスを作って対応する。 手順としては、getView() メソッドをオーバーライドして、そこに、各項目のデータを割 り当てることになる。 □ ItemListAdapter --- package jp.marunomaruno.android.database; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; public class ItemListAdapter extends ArrayAdapter<Item> { // (1) public ItemListAdapter(Context context, List<Item> objects) { // (2) super(context, 0, objects); // (3) } public ItemListAdapter(Context context, Item[] objects) { // (4) super(context, 0, objects); } public ItemListAdapter(Context context, int resource, int textViewResourceId, List<Item> objects) { super(context, resource, textViewResourceId, objects); } public ItemListAdapter(Context context, int resource, int textViewResourceId, Item[] objects) { super(context, resource, textViewResourceId, objects); } public ItemListAdapter(Context context, int resource, int textViewResourceId) { super(context, resource, textViewResourceId); } public ItemListAdapter(Context context, int textViewResourceId, List<Item> objects) { super(context, textViewResourceId, objects); } public ItemListAdapter(Context context, int textViewResourceId, Item[] objects) { super(context, textViewResourceId, objects); } public ItemListAdapter(Context context, int textViewResourceId) { super(context, textViewResourceId); } @Override public View getView(int position, View convertView, ViewGroup parent) { // (5) if (convertView == null) { LayoutInflater layoutInflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); // (6) convertView = layoutInflater.inflate(R.layout.row, null); // (7) } Item item = getItem(position); // (8) TextView id = (TextView) convertView.findViewById(R.id.idInList); // (9) id.setText(String.valueOf(item.getId())); // (10) TextView name = (TextView) convertView.findViewById(R.id.nameInList); name.setText(item.getName()); TextView comment = (TextView) convertView .findViewById(R.id.commentInList); comment.setText(item.getComment()); return convertView; // (11) } } --- public class ItemListAdapter extends ArrayAdapter<Item> { // (1) ArrayAdapter クラスを継承して作る。 ArrayAdapter クラスは、総称型となっているので、今回は、Item 型に限定して作る。 ・ArrayAdapter クラス オブジェクトの配列やリストと、行のレイアウトの項目を紐付けるためのアダプター・ク ラス。 java.lang.Object - android.widget.BaseAdapter - android.widget.ArrayAdapter<T> ・主なコンストラクター --- ArrayAdapter(Context context, int textViewResourceId, T[] objects) ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) ArrayAdapter(Context context, int textViewResourceId, List<T> objects) ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> obje cts) --- ・主なメソッド --- void add(T object) void addAll(Collection<? extends T> collection) void addAll(T... items) void clear() int getCount() T getItem(int position) long getItemId(int position) int getPosition(T item) void insert(T object, int index) void remove(T object) --- (2)(3)(4) 独自コンストラクターの定義 ArrayAdapter クラスのコンストラクターで、使わない引数(textViewResourceId)の分 を除いたコンストラクターを作っておく。基本的に、オブジェクトのリストか、オブジェ クトの配列を受け取れるようにすればよい。 public ItemListAdapter(Context context, List<Item> objects) { // (2) public ItemListAdapter(Context context, Item[] objects) { // (4) ArrayAdapter クラスのコンストラクターを呼び出す。このとき、textViewResourceId は 指定されていないので、0 にしておく。 super(context, 0, objects); // (3) (5) 行項目を設定するメソッドのオーバーライド public View getView(int position, View convertView, ViewGroup parent) { // (5) (6)(7) ビューを取得 アプリケーションが起動した直後の状態なんかでは、convertView が null になっている。 このため、LayoutInflater の inflate() メソッドを使って、ビューのオブジェクトを取 得する必要がある。 LayoutInflater layoutInflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); // (6) convertView = layoutInflater.inflate(R.layout.row, null); // (7) Context.LAYOUT_INFLATER_SERVICE は、レイアウトの動的な描画のために Inflater オブ ジェクトを生成するのに使う定数。 --- View inflate(int resource, ViewGroup root) --- ・LayoutInflater クラス 動的にレイアウトの XML ファイルのオブジェクトを読み込むためのクラス java.lang.Object - android.view.LayoutInflater ・主なメソッド --- View inflate(int resource, ViewGroup root) View inflate(XmlPullParser parser, ViewGroup root) View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) View inflate(int resource, ViewGroup root, boolean attachToRoot) --- (8) position 番目のオブジェクトを取得 getItem() メソッドを使って、リストや配列の position 番目のオブジェクトを取得する。 Item item = getItem(position); // (8) (9)(10) テキストに設定する TextView id = (TextView) convertView.findViewById(R.id.idInList); // (9) id.setText(String.valueOf(item.getId())); // (10) (11) ビューを返す return convertView; // (11) ▲ データベース関係 □ DatabaseHelper.java ※前回のサンプルと同じ ▲エンティティ □ Item.java ※前回のサンプルと同じ ▲レイアウト テーブルの行の一覧を表示するための ListView 要素が追加されている。 □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="1" > <EditText android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/nameHint" > <requestFocus > </requestFocus> </EditText> <EditText android:id="@+id/comment" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/commentHint" android:inputType="textMultiLine" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/findButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onFindButtonClick" android:text="@string/findButton" /> <Button android:id="@+id/saveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onSaveButtonClick" android:text="@string/saveButton" /> <Button android:id="@+id/updateButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onUpdateButtonClick" android:text="@string/updateButton" /> <Button android:id="@+id/removeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onRemoveButtonClick" android:text="@string/removeButton" /> </LinearLayout> <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" /> <!-- (1) --> </LinearLayout> --- (1) リスト・ビュー <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" /> <!-- (1) --> 1 行分のデータは、つぎのファイルで定義している。 □ res/layout/row.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/idInList" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/nameInList" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/commentInList" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> --- ▲ 文字列 ※前回のサンプルと同じ 以上
[Android] データの取り扱い - データベース(SQLite) ================================================================================ データベース(SQLite)を使って、データの照会・保存・変更・削除を行う。 データ(列)は、つぎの 2 つ。 ・名前 ・コメント なお、データベースのテーブルを使うにあたり、このデータを区別するための主キーとし て、ID 列をつける。なお、SQLite では、この「PRIMARY KEY AUTOINCREMENT」の属性を 持った列は、「_id」という列名にする。こうしておくことで、CursorAdapter クラスを 使って ListView にデータを表示させることができる。 ※ただし、CursorAdapter を使うのは、V3.x 以降では推奨されていないようだ。 データベースのテーブル生成文は以下のとおり。 CREATE TABLE item ( _id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT , comment TEXT ) このサンプル・アプリケーションではつぎのことができる。 ・照会 「名前」欄に記述されたものと一致するものをすべて取得するが、表示するのは最初 に取得したものだけ。 ・保存 「名前」「コメント」欄に記述された内容をデータベースのテーブルに保存する。 ・変更 「名前」欄に記述されたものと一致するものすべての「コメント」を、すべて変更す る。 ・削除 「名前」欄に記述されたものと一致するものすべてを削除する。 データベースを扱うクラスとして、DatabaseHelper クラスを作る。これは、 SQLiteOpenHelper クラス(抽象クラス)を継承して作る。 次のメソッドのオーバーライドが必要。 abstract void onCreate(SQLiteDatabase db) abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 今回のクラス --- Database01Activity アクティビティ DatabaseHelper データベース操作 Item エンティティ --- ▲ アクティビティ データベースに関する操作はすべて DatabaseHelper オブジェクトで行っている。 □ Database01Activity.java --- package jp.marunomaruno.android.database; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.Toast; public class Database01Activity extends Activity { private EditText name = null; private EditText comment = null; private DatabaseHelper dbHelper = null; // (1) private int TOAST_DURATION = Toast.LENGTH_SHORT; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (EditText) findViewById(R.id.name); comment = (EditText) findViewById(R.id.comment); dbHelper = new DatabaseHelper(this); // (2) } @Override public void onPause() { super.onPause(); if (dbHelper != null) { // dbHelper.close(); // 呼ばないほうがよい // (3) } } public void onFindButtonClick(View view) { List<Item> itemList = dbHelper.select(name.getText()); // (4) if (itemList.size() <= 0) { Toast.makeText(this, getString(R.string.notFoundMessage), TOAST_DURATION).show(); return; } name.setText(itemList.get(0).getName()); comment.setText(itemList.get(0).getComment()); Toast.makeText( this, (itemList.size() == 1 ? String .format(getString(R.string.findMessage)) : String .format(getString(R.string.findMultipleMessage), itemList.size())), TOAST_DURATION).show(); } public void onSaveButtonClick(View view) { long id = dbHelper.insert(name.getText(), comment.getText()); // (5) name.setText(""); comment.setText(""); if (id > 0) { Toast.makeText(this, String.format(getString(R.string.saveMessage), id), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notSaveMessage), TOAST_DURATION).show(); } } public void onUpdateButtonClick(View view) { int count = dbHelper.update(name.getText(), comment.getText()); // (6) if (count > 0) { Toast.makeText(this, String.format(getString(R.string.updateMessage), count), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notUpdateMessage), TOAST_DURATION).show(); } } public void onRemoveButtonClick(View view) { int count = dbHelper.delete(name.getText()); // (7) name.setText(""); comment.setText(""); if (count > 0) { Toast.makeText(this, String.format(getString(R.string.removeMessage), count), TOAST_DURATION).show(); } else { Toast.makeText(this, getString(R.string.notRemoveMessage), TOAST_DURATION).show(); } } } --- (1)(2) データベースにアクセスするオブジェクト private DatabaseHelper dbHelper = null; // (1) DatabaseHelper オブジェクトで、データベースを取得しようとしたときに、データベー スがない場合、onCreate() メソッドが動く。 また、データベースのバージョンが変わったときは、onUpgrade() メソッドが動く。 データベースのバージョンは、SQLiteOpenHelper のコンストラクターの引数 int version で指定する。この値が、現在のバージョンが変わったら、このonUpgrade() メソッドが動くことになる。 dbHelper = new DatabaseHelper(this); // (2) (3) データベースを閉じる ただし、これは自分では閉じない方がよいらしい。 // dbHelper.close(); // 呼ばないほうがよい // (3) 参考 「SQLiteDatabase.closeは明示で呼ぶな、Cursor.closeは明示で呼べ」 SQLiteを使う場合の注意点 http://d.hatena.ne.jp/ukiki999/20100524/p1 (4) name で指定されたデータを取得(照会)する 結果は、List オブジェクトで戻る。 List<Item> itemList = dbHelper.select(name.getText()); // (4) (5) name, comment データを挿入する 結果は、主キーの値が戻る。挿入できなかったときは、-1. long id = dbHelper.insert(name.getText(), comment.getText()); // (5) (6) name で指定されたデータを更新する 結果は、更新した行数。 int count = dbHelper.update(name.getText(), comment.getText()); // (6) (7) name で指定されたデータを削除する 結果は、削除した行数。 int count = dbHelper.delete(name.getText()); // (7) ▲ データベース関係 データベースを操作するためのクラス。 SQLiteOpenHelper クラスを継承して、その抽象メソッドであるつぎのメソッドをオー バーライドする。そして、必要に応じて CRUD メソッドを記しておく。 onCreate(SQLiteDatabase db) onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) データベースの CRUD 操作は、getReadableDatabase() メソッド、 getWritableDatabase() メソッドによって、SQLiteDatabase オブジェクトが取得できる ので、このオブジェクトの中のメソッドによる。 □ DatabaseHelper.java --- package jp.marunomaruno.android.database; import java.util.ArrayList; import java.util.List; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.text.Editable; /** * データベースを扱うためのヘルパー・クラス。 * * @author marunomaruno */ public class DatabaseHelper extends SQLiteOpenHelper { // (1) private Context context; private static final String DATABASE_NAME = "item.db"; private static final int DATABASE_VERSION = 1; // (2) public static final String TABLE_NAME = "item"; public static final String ID = "_id"; public static final String NAME = "name"; public static final String COMMENT = "comment"; public static final String[] COLUMN_NAMES = { ID, NAME, COMMENT, }; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); // (3) this.context = context; } @Override public void onCreate(SQLiteDatabase db) { // (4) final String SQL = String.format(context .getString(R.string.createTableFormat), TABLE_NAME, ID, NAME, COMMENT); // (5) System.out.println("DatabaseHelper.onCreate() SQL = " + SQL); db.execSQL(SQL); // (6) } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // (7) db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); // (8) onCreate(db); } /** * 指定されたデータをデータベースに挿入する。 * * @param name * 名前 * @param comment * コメント * @return 主キーの値 */ public long insert(String name, String comment) { String nullColumnHack = null; // NOT NULL列に値が指定されていない場合のデフォルト値 ContentValues values = new ContentValues(); // 挿入する列名と値 // (9) values.put(NAME, name); // (10) values.put(COMMENT, comment); return getWritableDatabase().insert(TABLE_NAME, nullColumnHack, values); // (11) } /** * 指定されたデータをデータベースに挿入する。 * * @param name * 名前 * @param comment * コメント * @return 主キーの値 */ public long insert(Editable name, Editable comment) { return insert(name.toString(), comment.toString()); } /** * 名前で指定されたデータを変更する。 * * @param name * 名前 * @param comment * コメント * @return 変更した行数 */ public int update(String name, String comment) { ContentValues values = new ContentValues(); // 更新する列名と値 values.put(COMMENT, comment); String whereClause = NAME + " = ?"; // 選択条件 // (12) String[] whereArgs = new String[] { name }; // パラメーター・マーカーの値 // (13) return getWritableDatabase().update(TABLE_NAME, values, whereClause, whereArgs); // (14) } /** * 名前で指定されたデータを変更する。 * * @param name * 名前 * @param comment * コメント * @return 変更した行数 */ public int update(Editable name, Editable comment) { return update(name.toString(), comment.toString()); } /** * 名前で指定されたデータを削除する。 * * @param name * 名前 * @return 削除した行数 */ public int delete(String name) { String whereClause = NAME + " = ?"; // 選択条件 String[] whereArgs = new String[] { name }; // パラメーター・マーカーの 値 return getWritableDatabase().delete(TABLE_NAME, whereClause, whereArgs); // (15) } /** * 名前で指定されたデータを削除する。 * * @param name * 名前 * @return 削除した行数 */ public int delete(Editable name) { return delete(name.toString()); } /** * 名前で指定されたデータを照会する。 * * @param name * 名前 * @return 照会したデータのリスト */ public List<Item> select(String name) { String selection = NAME + " = ?"; // 選択条件 String[] selectionArgs = { name }; // パラメーター・マーカーの値 String groupBy = null; // GROUP BY 句 String having = null; // HAVING 句 String orderBy = null; // ORDER BY 句 Cursor cursor = getReadableDatabase().query(TABLE_NAME, COLUMN_NAMES, selection, selectionArgs, groupBy, having, orderBy); // (16) List<Item> itemList = new ArrayList<Item>(); if (!cursor.moveToFirst()) { // 最初の行をさす // (17) cursor.close(); return itemList; } do { itemList.add(new Item(cursor.getLong(cursor.getColumnIndex(ID)), cursor.getString(cursor.getColumnIndex(NAME)), cursor .getString(cursor.getColumnIndex(COMMENT)))); // (18 ) } while (cursor.moveToNext()); // (19) cursor.close(); // (20) return itemList; } /** * 名前で指定されたデータを照会する。 * * @param name * 名前 * @return 照会したデータのリスト */ public List<Item> select(Editable name) { return select(name.toString()); } } --- (1) データベースにアクセスするクラス SQLiteOpenHelper クラスを継承する。 public class DatabaseHelper extends SQLiteOpenHelper { // (1) ・SQLiteOpenHelper クラス データベースと接続するためのクラス。 SQLite では、JDBC と違い、Connection オブジェクトを作るわけではなく、データベー ス自体の管理は、この SQLiteOpenHelper オブジェクトを通して、生成・バージョンアッ プ・接続などを行う。 java.lang.Object - android.database.sqlite.SQLiteOpenHelper ・コンストラクター --- SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) --- name はデータベース名。データベースは、「/data/data/パッケージ名/database/」に構 築される。 factory は、独自カーソルを定義した場合に使う。標準でよければ null。 version は、データベースのバージョン。既存のデータベースのバージョンと違う場合、 旧バージョンとの値の差によって、 onDowngrade() onUpgrade() メソッドが動く。なお、onUpgrade() メソッドは抽象メソッドなので、オーバーライドが 必要であるが、onDowngrade() メソッドは具象メソッドなので、必要なときにオーバーラ イドすればよい。 ・メソッド --- synchronized void close() String getDatabaseName() synchronized SQLiteDatabase getReadableDatabase() synchronized SQLiteDatabase getWritableDatabase() abstract void onCreate(SQLiteDatabase db) void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) void onOpen(SQLiteDatabase db) abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) --- データベースへの接続は、getReadableDatabase() メソッド、getWritableDatabase() メ ソッドで行う。 (2)(3) このデータベースのバージョン 今回は、バージョン 1 にしておく。データベースの構成が変わったときは、2 以上にし て、データベースを作り直す。 private static final int DATABASE_VERSION = 1; // (2) バージョンの指定は、スーパークラス SQLiteOpenHelper のコンストラクターの引数で指 定する。 super(context, DATABASE_NAME, null, DATABASE_VERSION); // (3) (4) データベースがなかったときの処理 データベースに接続するときに、データベースがなかったときにこのメソッドが呼ばれる。 これは、SQLiteOpenHelper クラスの抽象メソッド。 public void onCreate(SQLiteDatabase db) { // (4) (5)(6) データベースとテーブルを生成する CREATE TABLE 文を記している。文の形式はリソースとして定義している。 final String SQL = String.format(context .getString(R.string.createTableFormat), TABLE_NAME, ID, NAME, COMMENT); // (5) 実際の文は以下のとおり。 CREATE TABLE item ( _id INTEGER PRIMARY KEY AUTOINCREMENT , name TEXT , comment TEXT ) SQL 文を実行する。 db.execSQL(SQL); // (6) (7) データベースのバージョンがあがったときの処理 これは、SQLiteOpenHelper クラスの抽象メソッド。 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // (7) (8) テーブルがあれば削除して作り直す db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); // (8) onCreate(db); (9)(10) 挿入する列名と値を設定する テーブルに挿入や更新する場合、列名と値のペアで管理する。名前と値のペアというと、 Map オブジェクトがあるが、SQLite では、ContentValues オブジェクトを使う。使い方 は Map と同じで、put() メソッドを使って値を設定。get() メソッドで、値を取得する。 なお、値の型によって、getAsXxx() メソッドもある。 ContentValues values = new ContentValues(); // 挿入する列名と値 // (9) values.put(NAME, name); // (10) □ ContentValues クラス SQLite の DML を使いやすくするのに適した形のマップ(Map)。 ・android.content.ContentValues ・取得・設定関係のメソッド --- Object get(String key) Boolean getAsBoolean(String key) Byte getAsByte(String key) byte[] getAsByteArray(String key) Double getAsDouble(String key) Float getAsFloat(String key) Integer getAsInteger(String key) Long getAsLong(String key) Short getAsShort(String key) String getAsString(String key) void put(String key, Byte value) void put(String key, Integer value) void put(String key, Float value) void put(String key, Short value) void put(String key, byte[] value) void put(String key, String value) void put(String key, Double value) void put(String key, Long value) void put(String key, Boolean value) void putAll(ContentValues other) void putNull(String key) --- ・その他のメソッド --- void clear() boolean containsKey(String key) boolean equals(Object object) int hashCode() Set<String> keySet() void remove(String key) int size() String toString() Set<Entry<String, Object>> valueSet() --- (11) name, comment データを挿入する データを挿入するので、getWritableDatabase() メソッドを使って、書き込みができる モードで、データベースを開く。このメソッドの結果は、SQLiteDatabase オブジェクト。 SQLiteDatabase クラスには、CRUD 系のメソッドが用意されているので、それらを使って テーブルを操作する。 return getWritableDatabase().insert(TABLE_NAME, nullColumnHack, values); // (11) --- long insert(String table, String nullColumnHack, ContentValues values) --- 戻り値は、挿入したときの rowID。INTEGER 型の主キー制約列が定義されていればその値 (のはず)。挿入エラーのときは -1. String table テーブル名 String nullColumnHack NOT NULL 列に値が指定されていない場合のデフォルト値。 ContentValues values 列名と値がマッピングされたオブジェクト (12)(13)(14) name で指定されたデータを更新する 選択条件には、JDBC と同じように、パラメーター・マーカー「?」が使える。 パラメーター・マーカー「?」に対する値は、String 配列値として別に用意する。 String whereClause = NAME + " = ?"; // 選択条件 // (12) String[] whereArgs = new String[] { name }; // パラメーター・マーカーの値 // (13) テーブルの更新なので、getWritableDatabase() メソッドを使う。 return getWritableDatabase().update(TABLE_NAME, values, whereClause, whereArgs); // (14) --- int update(String table, ContentValues values, String whereClause, String[] whereArgs) --- 戻り値は、更新した行数 (15) name で指定されたデータを削除する update() メソッドと同じように、選択条件にはパラメーター・マーカー「?」が使える。 return getWritableDatabase().delete(TABLE_NAME, whereClause, whereArgs); // (15) --- int delete(String table, String whereClause, String[] whereArgs) --- 戻り値は、削除した行数 (16) name で指定されたデータを取得(照会)する ここでは、照会だけなので、読み取り専用で、getReadableDatabase() メソッドを使えば よい。戻り値は Cursor オブジェクト。 Cursor cursor = getReadableDatabase().query(TABLE_NAME, COLUMN_NAMES, selection, selectionArgs, groupBy, having, orderBy); // (16) --- Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) Cursor rawQuery(String sql, String[] selectionArgs) 引数 boolean distinct DISTINCT(true)かALL(false)か String table テーブル名 String[] columns 射影される列名(nullのとき、すべての列) String selection WHERE句(パラメーターマーカー(?)の指定も可能) String[] selectionArgs パラメーターマーカー(?)を置き換える文字列 String groupBy GROUP BY句 String having HAVING句 String orderBy ORDER BY句 String limit LIMIT句 String sql SQL文(パラメーターマーカー含む) --- (17) 取得したテーブルの最初の行をさす カーソルを使って取得したテーブルを処理する。 まずは、最初の行にカーソルを移動する。 if (!cursor.moveToFirst()) { // 最初の行をさす // (17) JDBC と違って、単純に next() メソッドを使うだけでできるわけではないので注意が必 要。moveToFirst() メソッドで、先頭行に移動し、moveToNext() メソッドで、つぎの行 に移動する、というパターン。全体としては、つぎのような感じでロジックを組む。 --- if (!cursor.moveToFirst()) { // 最初の行をさす cursor.close(); return ...; } do { // 行の処理 } while (cursor.moveToNext()); // つぎの行に移動する cursor.close(); --- ・行の移動関係のメソッド --- boolean move(int offset) boolean moveToFirst() boolean moveToLast() boolean moveToNext() boolean moveToPosition(int position) boolean moveToPrevious() --- (18) 行のデータから Item オブジェクトを生成する itemList.add(new Item(cursor.getLong(cursor.getColumnIndex(ID)), cursor.getString(cursor.getColumnIndex(NAME)), cursor .getString(cursor.getColumnIndex(COMMENT)))); // (18) ・ データ取得関係のメソッド --- byte[] getBlob(int columnIndex) double getDouble(int columnIndex) float getFloat(int columnIndex) int getInt(int columnIndex) long getLong(int columnIndex) short getShort(int columnIndex) String getString(int columnIndex) --- 引数としては列番号だけなので、列名から取得したいときは、 getColumnIndex(String columnName) と組み合わせる必要がある。 (19) つぎの行に移動する } while (cursor.moveToNext()); // (19) (20) カーソルを閉じる 使い終わったカーソルは閉じる。 cursor.close(); // (20) 閉じていない場合は、つぎのエラーが出る。 E/Cursor(14725): Finalizing a Cursor that has not been deactivated or closed. ▲エンティティ 単純に、id、name、comment を持っているだけのクラス。 JavaBeans の要件は満たすようにしている。 □ Item.java --- package jp.marunomaruno.android.database; import java.io.Serializable; /** * データベースの項目を管理するエンティティ・クラス。 * * @author marunomaruno */ public class Item implements Serializable { private static final long serialVersionUID = 1L; public static final int NONE_ID = -1; private long id; private String name; private String comment; public Item(long id, String name, String comment) { this.id = id; this.name = name; this.comment = comment; } public Item(String name, String comment) { this(NONE_ID, name, comment); } public Item() { assert true; // 何もしない } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } @Override public String toString() { return String.format("[%d, %s, %s]", id, name, comment); } } --- ▲レイアウト □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="1" > <EditText android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/nameHint" > <requestFocus > </requestFocus> </EditText> <EditText android:id="@+id/comment" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/commentHint" android:inputType="textMultiLine" > </EditText> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/findButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onFindButtonClick" android:text="@string/findButton" > </Button> <Button android:id="@+id/saveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onSaveButtonClick" android:text="@string/saveButton" > </Button> <Button android:id="@+id/updateButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onUpdateButtonClick" android:text="@string/updateButton" > </Button> <Button android:id="@+id/removeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:onClick="onRemoveButtonClick" android:text="@string/removeButton" > </Button> </LinearLayout> </LinearLayout> --- ▲ 文字列 --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Database01</string> <string name="nameHint">名前</string> <string name="commentHint">コメント</string> <string name="saveButton">保存</string> <string name="findButton">検索</string> <string name="removeButton">削除</string> <string name="updateButton">変更</string> <string name="saveMessage">ID %d で保存しました</string> <string name="findMessage">データがありました。</string> <string name="findMultipleMessage"> %d 件のデータがありました。最初の1件を表示します</string> <string name="removeMessage">%d 件のデータを削除しました</string> <string name="updateMessage">%d 件のデータを変更しました</string> <string name="notSaveMessage">データを保存できませんでした</string> <string name="notFoundMessage">データはありませんでした</string> <string name="notRemoveMessage">データを削除できませんでした</string> <string name="notUpdateMessage">データを変更できませんでした</string> <string name="createTableFormat"> CREATE TABLE %1$s ( %2$s INTEGER PRIMARY KEY AUTOINCREMENT , %3$s TEXT , %4$s TEXT ) </string> <!-- (1) --> </resources> --- (1) テーブルを生成する SQL 文のフォーマット <string name="createTableFormat"> CREATE TABLE %1$s ( %2$s INTEGER PRIMARY KEY AUTOINCREMENT , %3$s TEXT , %4$s TEXT ) </string> <!-- (1) --> ※ この部分は、国際化とは意味が違うので、別の XML ファイルにした方がよいかもしれ ない。 以上
[Android] JavaScript との連携 - WebView ================================================================================ WebView を使うことで、レイアウトの中で、HTML を表示することができる。 自分のアプリケーションから独立しているブラウザーでは、アプリケーションから HTML を制御することはできないが、アプリケーション内の WebView を使えば、アプリケーシ ョンから HTML の制御は可能になる。たとえば、JavaScript と Java のプログラムとを 連携させることができる。 JavaScript との連携方法としては次の 2 つがある。 (1) JavaScript から Java のメソッド呼び出し (2) Java から JavaScript の関数呼び出し どちらも、以下の処理を行う必要がある。 ・WebView オブジェクトを取得し、HTML の中で JavaScript を使えるようにする 上記を行ったのちの、それぞれの概要は以下のとおり。 (1) JavaScript から Java のメソッド呼び出し ・JavaScript から使える Java のメソッドのオブジェクトを登録 WebView.addJavascriptInterface(オブジェクト, インターフェース名); (2) Java から JavaScript の関数呼び出し ・つぎのメソッドを使う WebView.loadUrl("javascript:関数名(引数リスト)"); JavaScript を扱う上での注意点として、以下をあげる。 ・alert や prompt を使うときには設定が必要 WebView.setWebChromeClient(WebChromeClient オブジェクト); ・JavaScript インターフェースのメソッドで、アクティビティのビューを操作する場合 はハンドラー Handler オブジェクトが必要 Handler.post(new Runnable() { public void run() { // ビューの操作 } }); WebView を使う上での注意点 ・マニフェストに次の権限を設定する <uses-permission android:name="android.permission.INTERNET"/> ▲ アクティビティ 上記の概要に基づいたクラス。 □ WebViewJavaScript01Activity.java --- package jp.marunomaruno.android.webviewjavascript; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; public class WebViewJavaScript01Activity extends Activity { private WebView webView1; // (1) /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Handler handler = new Handler(); // (2) webView1 = (WebView) findViewById(R.id.webView1); webView1.loadUrl("file:///android_asset/index.html"); // (3) WebSettings settings = webView1.getSettings(); // (4) settings.setJavaScriptEnabled(true); // (5) webView1.addJavascriptInterface(new JavaScriptInterfaceFunctions(this, handler), "AndroidFunctions"); // (6) webView1.setWebChromeClient(new WebChromeClient()); // (7) } public void onClickHandler(View view) { webView1.loadUrl("javascript:showText()"); // (8) } } --- (1) WebView オブジェクト private WebView webView1; // (1) △ WebView クラス HTML をアプリケーション内から表示するためのクラス。 java.lang.Object + android.view.View + android.view.ViewGroup + android.widget.AbsoluteLayout + android.webkit.WebView ・主なメソッド --- void addJavascriptInterface(Object obj, String interfaceName) WebSettings getSettings() void loadData(String data, String mimeType, String encoding) void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) void loadUrl(String url) void loadUrl(String url, Map<String, String> extraHeaders) void setDownloadListener(DownloadListener listener) void setWebChromeClient(WebChromeClient client) void setWebViewClient(WebViewClient client) void stopLoading() --- ・履歴関係のメソッド --- boolean canGoBack() boolean canGoBackOrForward(int steps) boolean canGoForward() void goBack() void goBackOrForward(int steps) void goForward() --- ・画面のズーム関係のメソッド --- boolean canZoomIn() boolean canZoomOut() boolean zoomIn() boolean zoomOut() --- (2) ハンドラー・オブジェクトの生成 JavaScript から呼ばれたメソッドで、アクティビティのビューを操作できるようにする ために、Handler クラスのオブジェクトを生成しておく。 Handler handler = new Handler(); // (2) △ Handler クラス Handler クラスは、マルチスレッドで、スレッド間でメッセージをやり取りするのを実現 するためのクラス。 java.lang.Object + android.os.Handler 詳細は、以下のURLを参照のこと。 Android の Handler とは何か? http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html --- ・AndroidのUI操作はシングル・スレッド モデル ・ユーザビリティ向上の為にはマルチスレッドが必要 ・Handlerで実現 ・Handlerを使わない場合に起きる例外は実行スレッドのチェックで発生 ・Handlerを使うと、UI Threadの持つキューにジョブを登録できる ・キューはUI Threadにより実行される ・別スレッドからUI Threadに処理を登録するのでスレッドチェックで例外が発生しない --- ・メッセージを送るメソッド --- final boolean post(Runnable r) final boolean postAtFrontOfQueue(Runnable r) final boolean postAtTime(Runnable r, Object token, long uptimeMillis) final boolean postAtTime(Runnable r, long uptimeMillis) final boolean postDelayed(Runnable r, long delayMillis) final boolean sendEmptyMessage(int what) final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) final boolean sendEmptyMessageDelayed(int what, long delayMillis) final boolean sendMessage(Message msg) final boolean sendMessageAtFrontOfQueue(Message msg) boolean sendMessageAtTime(Message msg, long uptimeMillis) final boolean sendMessageDelayed(Message msg, long delayMillis) --- ・メッセージを受け取るメソッド --- void handleMessage(Message msg) --- (3) HTML を読み込む webView1.loadUrl("file:///android_asset/index.html"); // (3) プロジェクトの assets フォルダは、「/android_asset」でアプリケーション内から参照 できる。 (5) HTML で JavaScript が使えるようにする WebSettings settings = webView1.getSettings(); // (4) settings.setJavaScriptEnabled(true); // (5) △ WebSettings クラス WebView オブジェクトに関する設定関係のオブジェクト。 java.lang.Object + android.webkit.WebSettings ・主なメソッド --- synchronized void setJavaScriptEnabled(boolean flag) JavaScript 有効無効の設定 void setSaveFormData(boolean save) フォームデータ保存の有効無効の設定 void setSavePassword(boolean save) パスワード保存の有効無効の設定 void setSupportZoom(boolean support) ズームの有効無効の設定 --- (6) JavaScript から Java のメソッドを使えるようにする webView1.addJavascriptInterface(new JavaScriptInterfaceFunctions(this, handler), "AndroidFunctions"); // (6) 形式 --- void addJavascriptInterface(Object JavaScriptインターフェース・オブジェクト, String インターフェース名) --- JavaScript 側からは、インターフェース名.メソッド名() で呼び出せる。 (7) JavaScript で、alert や prompt が使えるようにする webView1.setWebChromeClient(new WebChromeClient()); // (7) △ WebChromeClient クラス JavaScript で使う prompt や alert は、Android のダイアログになる。これらをサポー トするクラス。デフォルトで用意されているが、必要に応じて、このクラスのメソッドを オーバーライドしてカスタマイズする。 java.lang.Object + android.webkit.WebChromeClient ・JavaScript 関係の主なメソッド --- boolean onJsAlert(WebView view, String url, String message, JsResult result) boolean onJsConfirm(WebView view, String url, String message, JsResult result) boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) boolean onJsTimeout() --- (8) JavaScript の関数を呼び出す webView1.loadUrl("javascript:showText()"); // (8) ・形式 --- webView1.loadUrl("javascript:関数名(引数リスト)"); --- ▲ JavaScript から使う関数を定義したクラス 単なる Java のクラスとして作ればよい。 ただし、アクティビティのビューを操作するときは、ハンドラー・オブジェクトが必要。 □ JavaScriptInterfaceFunctions.java --- package jp.marunomaruno.android.webviewjavascript; import android.app.Activity; import android.content.Context; import android.os.Handler; import android.widget.TextView; import android.widget.Toast; public class JavaScriptInterfaceFunctions { private Context context; private Handler handler; // (1) public JavaScriptInterfaceFunctions(Context context, Handler handler) { this.context = context; this.handler = handler; } public void showToast(String message) { Toast.makeText(context, getMessage(message), Toast.LENGTH_LONG).show(); } public void setTextView(final String message) { handler.post(new Runnable() { // (2) public void run() { // (3) TextView textView1 = (TextView) ((Activity) context) .findViewById(R.id.textView1); textView1.setText(getMessage(message)); } }); } public String getMessage(String message) { return String.format("<span class='html'>☆%s☆</span> %s", message, getMessage()); } public String getMessage() { return String.format("<span class='java'>★%s★</span>", "これはJavaの文字列"); } } --- (1) JavaScript から呼ばれたメソッドで、アクティビティのビューを操作できるように する コンストラクターの引数をそのまま設定する。 private Handler handler; // (1) (2)(3) テキスト・ビューに値を設定する ハンドラーの post() メソッドを使って実行する。 handler.post(new Runnable() { // (2) public void run() { // (3) TextView textView1 = (TextView) ((Activity) context) .findViewById(R.id.textView1); textView1.setText(getMessage(message)); } }); ▲ HTML ファイル 通常の HTML ファイル。 JavaScript から、Android の Java メソッドを呼ぶには、インターフェース名が必要。 インターフェース名.メソッド名(引数リスト) □ assets/index.html --- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <style TYPE="text/css"> #area { background-color: lightgreen; } .html { color: red; } .java { color: green; } </style> <script type="text/javascript"> // Android のトースト表示 function showToast(message){ AndroidFunctions.showToast(message); // (1) } // Android のテキストビューに設定 function setTextView(message){ AndroidFunctions.setTextView(message); } // メッセージを組み立て function makeMessage(message){ var message = AndroidFunctions.getMessage(message); // (2) document.getElementById("area").innerHTML = message; } // メッセージ取得 function getMessage(){ var message = AndroidFunctions.getMessage(); // (3) document.getElementById("area").innerHTML = message; } // メッセージをプロンプトから取得する function messageFromPrompt(){ var message = prompt("文字列を入力してください。", ""); // (4) alert(message); // (5) } // Android から呼ばれる function showText(){ document.getElementById("area").innerHTML = "Javaから呼ばれたJavaScriptの関 数"; } </script> <title>Insert title here</title> </head> <body> <p> <input type="button" value="Androidのトーストを表示" onclick="showToast('HTMLの文字列')"/> </p> <p> <input type="button" value="Androidのテキストビューに表示" onclick="setTextView('HTMLの文字列')"/> </p> <p> <input type="button" value="Androidからメッセージを組み合わせる" onclick="makeMessage('HTMLの文字列')"/> </p> <p> <input type="button" value="プロンプトから文字列を読んでアラート表示" onclick="messageFromPrompt()"/> </p> <p> <input type="button" value="Androidからメッセージを得る" onclick="getMessage()"/> </p> <p> <div id="area">ここに文字列が入る</div> </p> </body> </html> --- (1) Java 側のメソッドの呼び出し AndroidFunctions.showToast(message); // (1) Java 側では以下のような設定をしている。 webView1.addJavascriptInterface(new JavaScriptInterfaceFunctions(this, handler), "AndroidFunctions"); (2)(3) Java 側のメソッドはオーバーロード可能 var message = AndroidFunctions.getMessage(message); // (2) var message = AndroidFunctions.getMessage(); // (3) (4)(5) prompt や alert を使う var message = prompt("文字列を入力してください。", ""); // (4) alert(message); // (5) Java 側では以下のような設定をしている。 webView1.setWebChromeClient(new WebChromeClient()); ▲レイアウト □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <WebView android:id="@+id/webView1" android:layout_width="match_parent" android:layout_height="400dp" /> <!-- (1) --> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onClickHandler" android:text="JavaScriptの呼び出し" /> </LinearLayout> --- (1) WebView <WebView android:id="@+id/webView1" android:layout_width="match_parent" android:layout_height="400dp" /> <!-- (1) --> ▲ マニフェスト --- <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.marunomaruno.android.webviewjavascript" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <uses-permission android:name="android.permission.INTERNET"/> <!-- (1) --> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".WebViewJavaScript01Activity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> --- (1) WebView を使うときは以下の設定が必要 <uses-permission android:name="android.permission.INTERNET"/> <!-- (1) --> 以上
2012-02-06 [Android] 15 パズル (3) - 経過時間(Chronometer) ================================================================================ 15 パズルに、パズルを解き始めてからの経過時間を表示する。 経過時間は、Chronometer クラスを使う。 経過時間測定の開始・終了は、Chronometer クラスの start()、stop() メソッドを使う。 また、表示は res/layout/main.xml で、Chronometer 要素を使う。 ベースになるプロジェクトは前回の 前回の「15 パズル (2) - カスタム・ボタン」 http://blog.goo.ne.jp/marunomarunogoo/d/20120204 である。 今回変更したクラスはつぎの 2 つ。 FifteenPuzzleActivity アクティビティ。Chronometer の開始・終了を追加 FifteenPuzzleBoard ボード。パズルの終了判定追加 ■ アクティビティ Chronometer の開始・終了の処理を追加する。 終了時は、アラート・ダイアログでその経過時間を表示する。 □ FifteenPuzzleActivity.java --- package jp.marunomaruno.android.fifteenpuzzle; import android.app.Activity; import android.app.AlertDialog; import android.os.Bundle; import android.os.SystemClock; import android.view.View; import android.widget.Chronometer; public class FifteenPuzzleActivity extends Activity { private FifteenPuzzleBoard board; // 15パズルのボード /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); board = new FifteenPuzzleBoard(this); } /** * 数字ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton(View v) { boolean isComplete = board.onClickNumberBlock((NumberButton) v); // (1) if (isComplete) { // 経過時間測定の終了 Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer); // (2) chronometer.stop(); // (3) System.out .println(String.format(getString(R.string.finishMessage), (SystemClock.elapsedRealtime() - chronometer .getBase()) / 1000)); // 完成のダイアログを表示 new AlertDialog.Builder(this) .setMessage( String.format(getString(R.string.finishMessage), (SystemClock.elapsedRealtime() - chronometer .getBase()) / 1000)) // (4) .setPositiveButton("OK", null) .show(); } } /** * 開始ボタン・クリック時のハンドラー * @param v */ public void onClickStartButton(View v) { board.reset(); // 経過時間測定の開始 Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer); chronometer.setBase(SystemClock.elapsedRealtime()); // (5) chronometer.start(); // (6) } } --- (1) 数字ブロックをクリックしたときにパズルの完成も判断 boolean isComplete = board.onClickNumberBlock((NumberButton) v); // (1) (2) クロノメーターを取得する Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer); // (2) ・Chronometer クラス シンプルなタイマーを実装する View クラスのサブクラス。 java.lang.Object + android.view.View + android.widget.TextView + android.widget.Chronometer 基本的な使い方は、setBase() で、現在時刻を設定し、ここからクロノメーターを start() で開始する。 やめるときは stop() でやめて、やめた時点の現在時刻から getBase() で開始時点の時 刻を引けば、実行していた時間がわかる。 なお、現在時刻は、SystemClock クラスを使う。 ・メソッド --- long getBase() 基の時間を返す void setBase(long base) カウントアップする基になる時間を設定する String getFormat() 書式文字列を返す void setFormat(String format) 書式文字列を設定する。%s は、"MM:SS" または "H:MM:SS" 形式になる Chronometer.OnChronometerTickListener getOnChronometerTickListener() クロノメーターの変更のリスナーを取得する void setOnChronometerTickListener(Chronometer.OnChronometerTickListener listener) クロノメーターの変更のリスナーを設定する void start() 開始する void stop() 停止する --- ・SystemClock クラス 間隔または経過時間の測定に使うシステム時計のクラス。 java.lang.Object + android.os.SystemClock メソッド --- static long currentThreadTimeMillis() 現在のスレッドの実行時間(ミリ秒) static long elapsedRealtime() CPU停止時間なども含めたシステム・ブートからの時間(ミリ秒) static boolean setCurrentTimeMillis(long millis) 現在の時間(ミリ秒) static void sleep(long ms) 指定されたミリ秒スリープ(例外をスローしない) static long uptimeMillis() CPU停止時間などを含めないシステム・ブートからの時間(ミリ秒) --- (3) クロノメーターを停止する chronometer.stop(); // (3) (4) 経過時間をダイアログで表示する chronometer.setBase() を行ったのが、SystemClock.elapsedRealtime() なので、経過時 間は停止時点の SystemClock.elapsedRealtime() から 開始時点の SystemClock.elapsedRealtime() を引く。開始時点の SystemClock.elapsedRealtime() は、setBase() で、クロノメーター・オブジェクトに設定済み。 new AlertDialog.Builder(this) .setMessage( String.format(getString(R.string.finishMessage), (SystemClock.elapsedRealtime() - chronometer .getBase()) / 1000)) // (4) .setPositiveButton("OK", null) .show(); (5)(6) クロノメーターを開始する 経過時間を後で取得するために、setBase() で、現在の時刻を設定しておく。 chronometer.setBase(SystemClock.elapsedRealtime()); // (5) chronometer.start(); // (6) ■ ボード 数字ブロックが移動するたびに、パズルの完成を判定する。 判定のメソッドとして、isComplete() を設ける。 変更・追加は、つぎのメソッドのみ。 public boolean onClickNumberBlock(NumberButton button) 完成すれば true を返す private boolean isComplete() 完成したかどうか(完成すれば true を返す) □ FifteenPuzzleBoard.java --- package jp.marunomaruno.android.fifteenpuzzle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import jp.marunomaruno.android.util.ArrayCollectionUtil; import jp.marunomaruno.android.util.SymmetricGroupUtil; import android.app.Activity; import android.content.Context; public class FifteenPuzzleBoard { public Context context; public static final int ORDER = 4; // 4次の正方行列 private NumberButton[] numberButtons; private int emptyBlockIndex = ORDER * ORDER; public FifteenPuzzleBoard(Context context) { this.context = context; Activity activity = (Activity) context; numberButtons = new NumberButton[] { null, // ボード上の番号と合わせるため、インデックス0をダミーとする (NumberButton) activity.findViewById(R.id.numberButton1), (NumberButton) activity.findViewById(R.id.numberButton2), (NumberButton) activity.findViewById(R.id.numberButton3), (NumberButton) activity.findViewById(R.id.numberButton4), (NumberButton) activity.findViewById(R.id.numberButton5), (NumberButton) activity.findViewById(R.id.numberButton6), (NumberButton) activity.findViewById(R.id.numberButton7), (NumberButton) activity.findViewById(R.id.numberButton8), (NumberButton) activity.findViewById(R.id.numberButton9), (NumberButton) activity.findViewById(R.id.numberButton10), (NumberButton) activity.findViewById(R.id.numberButton11), (NumberButton) activity.findViewById(R.id.numberButton12), (NumberButton) activity.findViewById(R.id.numberButton13), (NumberButton) activity.findViewById(R.id.numberButton14), (NumberButton) activity.findViewById(R.id.numberButton15), (NumberButton) activity.findViewById(R.id.numberButton16), }; // 最後のブロックを空とする numberButtons[emptyBlockIndex].setText(R.string.emptyText); log("initialize() "); } /** * 空ブロックが指定されたインデックスに入っているかどうか判定する。 * @param indexes * @return 空ブロックが指定されたインデックスに入っていればtrue */ private boolean isEmptyBlockIndexIn(int... indexes) { assert indexes.length > 0; return ArrayCollectionUtil.isKeyIn(emptyBlockIndex, indexes); } /** * 数字ボタン・クリック時のハンドラー * @param v * @return パズル終了時は true */ public boolean onClickNumberBlock(NumberButton button) { // このブロックに対して、水平方向に有効な空ブロックの位置 final int[][] HORIZONTAL_INDEXES = new int[][]{ null, {2, 3, 4}, {1, 3, 4}, {1, 2, 4}, {1, 2, 3}, {6, 7, 8}, {5, 7, 8}, {5, 6, 8}, {5, 6, 7}, {10, 11, 12}, {9, 11, 12}, {9, 10, 12}, {9, 10, 11}, {14, 15, 16}, {13, 15, 16}, {13, 14, 16}, {13, 14, 15}, }; // このブロックに対して、垂直方向に有効な空ブロックの位置 final int[][] VERTICAL_INDEXES = new int[][]{ null, {5, 9, 13}, {6, 10, 14}, {7, 11, 15}, {8, 12, 16}, {1, 9, 13}, {2, 10, 14}, {3, 11, 15}, {4, 12, 16}, {1, 5, 13}, {2, 6, 14}, {3, 7, 15}, {4, 8, 16}, {1, 5, 9}, {2, 6, 10}, {3, 7, 11}, {4, 8, 12}, }; System.out.printf("onClick() %s", button.toString()); // 空の場合、何もしない if (isEmptyBlock(button)) { System.out.println(": empty"); return false; // (1) } int blockIndex = button.getBlockIndex(); if (isEmptyBlockIndexIn(HORIZONTAL_INDEXES[blockIndex])) { rotateHorizontal(blockIndex); System.out.println(); // 最後にlogを出すための改行 return isComplete(); // (2) } if (isEmptyBlockIndexIn(VERTICAL_INDEXES[blockIndex])) { rotateVertical(blockIndex); System.out.println(); // 最後にlogを出すための改行 return isComplete(); } System.out.println(); // 最後にlogを出すための改行 return false; } /** * パズルが完成かどうか * @return 完成のとき true */ private boolean isComplete() { // (3) // インデックス 1~15 の中に空ブロックがあればまだ完成していない for (int i = 1; i < numberButtons.length - 1; i++) { if (numberButtons[i].getText().toString().equals( context.getString(R.string.emptyText))) { return false; } } // インデックス 1~15 の中で、数字の順番が逆なのがあればまだ完成していない for (int i = 1; i < numberButtons.length - 2; i++) { if (Integer.parseInt(numberButtons[i].getText().toString()) >= Integer .parseInt(numberButtons[i + 1].getText().toString())) { return false; } } return true; } /** * 垂直にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateVertical(int index) { if (index < emptyBlockIndex) { rotateDown(index); } else { rotateUp(index); } } /** * 水平にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateHorizontal(int index) { if (index < emptyBlockIndex) { rotateRight(index); } else { rotateLeft(index); } } /** * 左にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateLeft(int index) { System.out.printf("rotateLeft() %d %d%n", index, emptyBlockIndex); rotateAscending(index, 1); } /** * 右にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateRight(int index) { System.out.printf("rotateRight() %d %d%n", index, emptyBlockIndex); rotateDescending(index, 1); } /** * 下にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateDown(int index) { System.out.printf("rotateDown() %d %d%n", index, emptyBlockIndex); rotateDescending(index, ORDER); } /** * 上にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateUp(int index) { System.out.printf("rotateUp() %d %d%n", index, emptyBlockIndex); rotateAscending(index, ORDER); } /** * 降順にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateDescending(int index, int step) { for (int i = emptyBlockIndex; i > index; i -= step) { numberButtons[i].setText(numberButtons[i - step].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * 昇順にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateAscending(int index, int step) { for (int i = emptyBlockIndex; i < index; i += step) { numberButtons[i].setText(numberButtons[i + step].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * このブロックが空かどうかを判断する。 * @return 空の場合true */ public boolean isEmptyBlock(NumberButton button) { return button.getBlockIndex() == emptyBlockIndex; } /** * ボードをリセットする */ public void reset() { // 1~15 までの乱数を生成する List<Integer> numberList = new ArrayList<Integer>(); numberList.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); int[] numberArray; do { Collections.shuffle(numberList); numberArray = ArrayCollectionUtil.toArray(numberList); } while (SymmetricGroupUtil.sgn(numberArray) != 1); // 解けない問題(遇置換でない)なら作り直し if (Boolean.parseBoolean(context.getResources().getString(R.string.debug))) { System.out.println("DEBUG MODE"); numberArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; } // 数字ブロックを設定する for (int i = 1; i < numberArray.length + 1; i++) { numberButtons[i].setText(String.valueOf(numberArray[i - 1])); } // 空ブロックを設定する numberButtons[ORDER * ORDER].setText(R.string.emptyText); emptyBlockIndex = ORDER * ORDER; log("onClickStartButton() "); } private void log(String message) { System.out.println(message); System.out.print(toString()); } @Override public String toString() { return String.format( "[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n", numberButtons[1].getText(), numberButtons[2].getText(), numberButtons[3].getText(), numberButtons[4].getText(), numberButtons[5].getText(), numberButtons[6].getText(), numberButtons[7].getText(), numberButtons[8].getText(), numberButtons[7].getText(), numberButtons[10].getText(), numberButtons[11].getText(), numberButtons[12].getText(), numberButtons[13].getText(), numberButtons[14].getText(), numberButtons[15].getText(), numberButtons[16].getText()); } } --- (1)(2) onClickNumberBlock の戻り ブロックの移動がない場合は、単純に false を返す。 return false; // (1) ブロックの移動があった場合は、完成かどうか判断して、その結果を返す。 return isComplete(); // (2) (3) パズルが完成かどうか private boolean isComplete() { // (3) ロジックは、単に、ブロック 1 ~ 15 の間に空ブロックがなくて、番号の昇順に並んで いれば、パズル完成、としている。 ■レイアウト クロノメーター (Chronometer) 要素を追加した。それ以外は前回からの変更なし。 □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:orientation="vertical" > <Button android:id="@+id/start_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="onClickStartButton" android:text="@string/start" /> <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" > <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton1" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="1" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton2" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="2" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton3" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="3" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton4" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="4" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton5" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="5" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton6" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="6" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton7" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="7" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton8" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="8" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton9" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="9" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton10" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="10" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton11" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="11" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton12" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="12" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton13" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="13" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton14" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="14" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton15" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="15" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton16" android:layout_width="0dip" android:layout_height="90px" android:layout_margin="1px" android:layout_weight="1" android:onClick="onClickNumberButton" android:text="@string/emptyText" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> </TableLayout> <Chronometer android:id="@+id/chronometer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:format="@string/chronometerFormat" /> <!-- (1) --> </LinearLayout> --- (1) クロノメーター 表示形式は、res/values/strings.xml で指定。 <Chronometer android:id="@+id/chronometer" android:layout_width="wrap_content" android:layout_height="wrap_content" android:format="@string/chronometerFormat" /> <!-- (1) --> ■ データ □ res/values/strings.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="debug">true</string> <string name="app_name">15パズル</string> <string name="start">スタート</string> <string name="emptyText">★</string> <string name="chronometerFormat">経過時間 %s</string> <string name="finishMessage">%d 秒で完成しました。</string> </resources> --- 以上
EclipseではじめるAndroidプログラミング入門―SDK2.1~2.3/4.0対応
http://www.shuwasystem.co.jp/products/7980html/3199.html
著者 掌田津耶乃
価格 2940円(税込)(本体2800円)
ISBN 978-4-7980-3199-6
発売日 2011/12/22
判型 B5変
色数 2色
ページ数 504
CD/DVD -
対象読者 初級
シリーズ -
目次
Chapter 1 Android開発の基礎知識
1.1 EclipseとAndroid開発
1.1.1 Androidとは?
1.1.2 AndroidはJavaで開発する
1.1.3 Androidのシステム構成
1.1.4 Eclipseとは?
1.1.5 Eclipseの特徴
1.1.6 Android Development Tools(ADT)
1.1.7 Android SDKについて
1.1.8 開発に必要なソフトウェアを整理する
1.1.9 JDKの入手
1.1.10 JDKのインストール
1.1.11 Mac OS XのJDKについて
1.1.12 Eclipse IDE for Java EE Developersの入手
1.1.13 Pleiadesの入手とインストール
1.1.14 Android SDKの入手とインストール
1.1.15 SDKパッケージのインストール
1.2 Eclipseの基本操作
1.2.1 Eclipseを起動する
1.2.2 エクスプローラー
1.2.3 アウトライン
1.2.4 エディター
1.2.5 タスクリスト
1.2.6 「問題」ビュー
1.2.7 Javadoc/宣言
1.2.8 コンソール
1.2.9 パースペクティブについて
1.2.10 ADTをインストールする
1.2.11 ADTの設定を行う
1.2.12 SDKパッケージの管理
1.2.13 AVDマネージャの起動
1.2.14 仮想デバイスの作成
1.2.15 「設定」ウインドウについて
1.2.16 「一般」設定について
1.2.17 「Android」設定について
1.2.18 「Java」設定について
1.2.19 「インストール/更新」設定について
1.2.20 <ソース>メニューについて
1.2.21 <リファクタリング>メニューについて
Chapter 2 GUIを使ったアプリの基本
2.1 Android開発の基本を覚える
2.1.1 新規プロジェクトの作成
2.1.2 プロジェクトの内容をチェックする
2.1.3 エミュレーターで実行する
2.1.4 実機にインストールして実行する
2.1.5 実行構成について
2.2 プロジェクトの基本構成
2.2.1 プロジェクトのファイルを確認する
2.2.2 Activityクラスについて
2.2.3 onCreateメソッドについて
2.2.4 Rクラスについて
2.2.5 main.xmlとレイアウト・エディター
2.2.6 main.xmlのソースコードをチェック
2.2.7 レイアウト用タグの属性
2.2.8 strings.xmlについて
2.2.9 マニフェストファイルについて
2.2.10 AndroidManifest.xmlのソースコード
2.3 基本GUIを利用する
2.3.1 ボタンを作成する
2.3.2 ボタン名にリソースを設定する
2.3.3 main.xmlの変更を確認する
2.3.4 strings.xmlをチェックする
2.3.5 Activityクラスの修正
2.3.6 doActionメソッドの処理
2.3.7 イベントリスナーによるイベント処理
2.3.8 EditTextを利用する
2.3.9 EditTextのテキスト操作
2.3.10 トーストを表示しよう
2.3.11 コードによるGUI作成
Chapter 3 基本ウィジェットをマスターする
3.1 選択用ウィジェット
3.1.1 CheckBoxの利用
3.1.2 ToggleButtonについて
3.1.3 RadioButtonとRadioGroup
3.2 Spinnerによるリスト
3.2.1 Spinnerによるリスト表示
3.2.2 Spinnerのイベント処理
3.2.3 ArrayAdapterでSpinner項目を生成する
3.2.4 SeekBarによるスライダーの利用
3.2.5 OnSeekBarChangeListenerによるイベント処理
3.2.6 一定間隔ごとにSeekBarを設定するには?
3.3 より柔軟な入力と表示
3.3.1 RatingBarについて
3.3.2 ProgressBarについて
3.3.3 入力フィールドについて
3.3.4 プログラム内からのタイプ操作
3.4 ウィジェットのイベント処理
3.4.1 長押しイベントについて
3.4.2 画面の回転
Chapter 4 ダイアログとメニュー
4.1 ダイアログを利用する
4.1.1 AlertDialogを使う
4.1.2 ダイアログのボタン設定
4.1.3 ボタンの処理をまとめる
4.1.4 ダイアログにリストを表示する
4.1.5 ラジオボタンによるリストの選択
4.1.6 リストの複数項目の選択
4.2 特殊なダイアログ
4.2.1 TimePickerとDatePicker
4.2.2 DatePickerDialogを利用する
4.2.3 TimePickerDialogを利用する
4.2.4 ProgressDialogを利用する
4.3 メニューの利用
4.3.1 オプションメニューの利用
4.3.2 メニュー関連イベント用のメソッド
4.3.3 コンテキストメニューについて
4.3.4 コンテキストメニュー用メソッド
Chapter 5 レイアウトを考える
5.1 レイアウトとコンテナ
5.1.1 レイアウトとは?
5.1.2 レイアウトの種類
5.1.3 RelativeLayoutによるレイアウト
5.1.4 TableLayoutを利用する
5.1.5 内部にレイアウトを組み込む
5.1.6 スクロール表示とScrollView
5.1.7 GridViewについて
5.2 より高度なインターフェイス
5.2.1 スライディングドロワー「SlidingDrawer」
5.2.2 SlidingDrawerの開閉イベント処理
5.2.3 タブパネルの仕組み
5.2.4 タブパネルを作る
5.2.5 TabHostのコーディング
5.2.6 タブ切り替えのイベント処理
5.3 ListViewによるリスト表示
5.3.1 ListViewを使う
5.3.2 コードから項目を設定する
5.3.3 リスト項目の追加・削除
5.3.4 ListActivityを使う
5.3.5 選択モードについて
5.3.6 CheckTextViewによるリスト項目
5.3.7 CheckTextViewによるリスト項目
5.3.8 リスト項目のカスタマイズ
Chapter 6 インテントをマスターする
6.1 インテントの基本をマスターする
6.1.1 アクティビティとインテント
6.1.2 明示的インテントと暗示的インテント
6.1.3 アクティビティの切り替え
6.1.4 HelloAppActivityの修正
6.1.5 OtherActivityの作成
6.1.6 アクティビティのライフサイクル
6.1.7 暗示的インテントについて
6.2 インテントを活用する
6.2.1 ACTION_SENDアクションを使う
6.2.2 Intentから値を受け取る
6.2.3 Intentにオブジェクトを渡す
6.2.4 オブジェクトをIntentから受け取る
6.2.5 Intentから返り値を受け取る
6.3 ステータスバーの利用
6.3.1 ノーティフィケーション
6.3.2 ノーティフィケーションを作ってみる
6.3.3 ノーティフィケーション利用の流れ
6.3.4 主なプロパティを指定する
Chapter 7 グラフィックの描画
7.1 Viewクラスとグラフィック描画
7.1.1 Viewと描画の仕組み
7.1.2 MyViewクラスの作成
7.1.3 MyViewのソースコードを作成する
7.1.4 main.xmlにMyViewを追加する
7.1.5 onDrawメソッドの描画処理
7.1.6 主な図形の描画メソッド
7.1.7 図形のプロパティを設定する
7.1.8 カラーを使いこなす
7.1.9 テキストの描画
7.2 グラフィック機能を使いこなす
7.2.1 ActivityからMyViewを制御する
7.2.2 タッチして描画する
7.2.3 イメージの描画
7.2.4 パスを使ったクリッピング
7.2.5 座標変換について
7.2.6 座標軸の保存とリストア
7.3 SurfaceViewによる高速描画
7.3.1 SurfaceViewとは?
7.3.2 SurfaceViewクラスの基本ソースコード
7.3.3 描画コードを作成する
7.3.4 描画処理の流れを整理する
7.3.5 ユーザー操作による表示の更新
7.3.6 タイマースレッドとScheduledExecutorService
7.3.7 ScheduledExecutorService利用の流れ
Chapter 8 ハードウェアアクセス
8.1 センサーの利用
8.1.1 センサーとSensorEventListener
8.1.2 センサー利用の基本
8.1.3 センサー利用クラスの基本コード
8.1.4 センサーを使う
8.1.5 センサー利用の流れを整理する
8.1.6 GPSを利用する
8.1.7 GPSを使ったプログラムの実際
8.2 カメラの利用
8.2.1 カメラとSurfaceView
8.2.2 カメラを使ったサンプルの作成
8.2.3 カメラのプレビューを表示する
8.2.4 CameraとSurfaceViewの流れ
8.2.5 カメラで撮影をする
8.2.6 撮影の流れを整理する
8.2.7 Intentでカメラアプリと連携する
8.2.8 カメラアプリからプレビュー画像を取得する
Chapter 9 データの利用
9.1 データアクセス
9.1.1 Androidのデータ管理
9.1.2 テキストファイルのアクセス
9.1.3 テキストファイルへのアクセス
9.1.4 SQLiteの利用
9.1.5 main.xmlの準備
9.1.6 SQLiteOpenHelperクラスの作成
9.1.7 Activityクラスの作成
9.1.8 データの追加・削除・検索
9.2 設定画面による値保存
9.2.1 設定画面とPreferenceActivity
9.2.2 設定用ファイルの用意
9.2.3 PreferenceActivityクラスの作成
9.2.4 設定画面を利用する
9.2.5 設定を変更したときの処理
9.2.6 Activityから設定値を利用する
Chapter 10 アプリ以外のプログラム
10.1 サービス
10.1.1 サービスとは?
10.1.2 サービスの基本構造
10.1.3 Serviceクラスの作成
10.1.4 MyService/MyBinderの実装
10.1.5 ActivityからMyServiceを利用する
10.1.6 ServiceConnectionとBroadcastReceiver
10.1.7 サービスの機能を外部から呼び出す
10.2 ウィジェットの作成
10.2.1 ウィジェットとは?
10.2.2 XMLファイルの作成
10.2.3 レイアウトとマニフェストの修正
10.2.4 AppWidgetProviderクラスの作成
10.2.5 サービスとの連携
10.2.6 サービスを実装する
10.2.7 サービスの処理の流れを整理する
10.3 タブレット・アプリとフラグメント
10.3.1 タブレットと画面の分断問題
10.3.2 フラグメントの基本
10.3.3 プロジェクトを作成する
10.3.4 レイアウトの作成
10.3.5 マニフェストファイルの作成
10.3.6 HelloFragmentAppActivityの作成
10.3.7 TitlesFragmentクラスの作成
10.3.8 DetailsFragmentクラスの作成
Chapter 11 これから先の世界
11.1 学習の指針
11.1.1 まだやっていないこと
11.1.2 分断化問題について
11.1.3 Android 4.0について
11.1.4 Android Marketについて
http://www.shuwasystem.co.jp/products/7980html/3199.html
著者 掌田津耶乃
価格 2940円(税込)(本体2800円)
ISBN 978-4-7980-3199-6
発売日 2011/12/22
判型 B5変
色数 2色
ページ数 504
CD/DVD -
対象読者 初級
シリーズ -
目次
Chapter 1 Android開発の基礎知識
1.1 EclipseとAndroid開発
1.1.1 Androidとは?
1.1.2 AndroidはJavaで開発する
1.1.3 Androidのシステム構成
1.1.4 Eclipseとは?
1.1.5 Eclipseの特徴
1.1.6 Android Development Tools(ADT)
1.1.7 Android SDKについて
1.1.8 開発に必要なソフトウェアを整理する
1.1.9 JDKの入手
1.1.10 JDKのインストール
1.1.11 Mac OS XのJDKについて
1.1.12 Eclipse IDE for Java EE Developersの入手
1.1.13 Pleiadesの入手とインストール
1.1.14 Android SDKの入手とインストール
1.1.15 SDKパッケージのインストール
1.2 Eclipseの基本操作
1.2.1 Eclipseを起動する
1.2.2 エクスプローラー
1.2.3 アウトライン
1.2.4 エディター
1.2.5 タスクリスト
1.2.6 「問題」ビュー
1.2.7 Javadoc/宣言
1.2.8 コンソール
1.2.9 パースペクティブについて
1.2.10 ADTをインストールする
1.2.11 ADTの設定を行う
1.2.12 SDKパッケージの管理
1.2.13 AVDマネージャの起動
1.2.14 仮想デバイスの作成
1.2.15 「設定」ウインドウについて
1.2.16 「一般」設定について
1.2.17 「Android」設定について
1.2.18 「Java」設定について
1.2.19 「インストール/更新」設定について
1.2.20 <ソース>メニューについて
1.2.21 <リファクタリング>メニューについて
Chapter 2 GUIを使ったアプリの基本
2.1 Android開発の基本を覚える
2.1.1 新規プロジェクトの作成
2.1.2 プロジェクトの内容をチェックする
2.1.3 エミュレーターで実行する
2.1.4 実機にインストールして実行する
2.1.5 実行構成について
2.2 プロジェクトの基本構成
2.2.1 プロジェクトのファイルを確認する
2.2.2 Activityクラスについて
2.2.3 onCreateメソッドについて
2.2.4 Rクラスについて
2.2.5 main.xmlとレイアウト・エディター
2.2.6 main.xmlのソースコードをチェック
2.2.7 レイアウト用タグの属性
2.2.8 strings.xmlについて
2.2.9 マニフェストファイルについて
2.2.10 AndroidManifest.xmlのソースコード
2.3 基本GUIを利用する
2.3.1 ボタンを作成する
2.3.2 ボタン名にリソースを設定する
2.3.3 main.xmlの変更を確認する
2.3.4 strings.xmlをチェックする
2.3.5 Activityクラスの修正
2.3.6 doActionメソッドの処理
2.3.7 イベントリスナーによるイベント処理
2.3.8 EditTextを利用する
2.3.9 EditTextのテキスト操作
2.3.10 トーストを表示しよう
2.3.11 コードによるGUI作成
Chapter 3 基本ウィジェットをマスターする
3.1 選択用ウィジェット
3.1.1 CheckBoxの利用
3.1.2 ToggleButtonについて
3.1.3 RadioButtonとRadioGroup
3.2 Spinnerによるリスト
3.2.1 Spinnerによるリスト表示
3.2.2 Spinnerのイベント処理
3.2.3 ArrayAdapterでSpinner項目を生成する
3.2.4 SeekBarによるスライダーの利用
3.2.5 OnSeekBarChangeListenerによるイベント処理
3.2.6 一定間隔ごとにSeekBarを設定するには?
3.3 より柔軟な入力と表示
3.3.1 RatingBarについて
3.3.2 ProgressBarについて
3.3.3 入力フィールドについて
3.3.4 プログラム内からのタイプ操作
3.4 ウィジェットのイベント処理
3.4.1 長押しイベントについて
3.4.2 画面の回転
Chapter 4 ダイアログとメニュー
4.1 ダイアログを利用する
4.1.1 AlertDialogを使う
4.1.2 ダイアログのボタン設定
4.1.3 ボタンの処理をまとめる
4.1.4 ダイアログにリストを表示する
4.1.5 ラジオボタンによるリストの選択
4.1.6 リストの複数項目の選択
4.2 特殊なダイアログ
4.2.1 TimePickerとDatePicker
4.2.2 DatePickerDialogを利用する
4.2.3 TimePickerDialogを利用する
4.2.4 ProgressDialogを利用する
4.3 メニューの利用
4.3.1 オプションメニューの利用
4.3.2 メニュー関連イベント用のメソッド
4.3.3 コンテキストメニューについて
4.3.4 コンテキストメニュー用メソッド
Chapter 5 レイアウトを考える
5.1 レイアウトとコンテナ
5.1.1 レイアウトとは?
5.1.2 レイアウトの種類
5.1.3 RelativeLayoutによるレイアウト
5.1.4 TableLayoutを利用する
5.1.5 内部にレイアウトを組み込む
5.1.6 スクロール表示とScrollView
5.1.7 GridViewについて
5.2 より高度なインターフェイス
5.2.1 スライディングドロワー「SlidingDrawer」
5.2.2 SlidingDrawerの開閉イベント処理
5.2.3 タブパネルの仕組み
5.2.4 タブパネルを作る
5.2.5 TabHostのコーディング
5.2.6 タブ切り替えのイベント処理
5.3 ListViewによるリスト表示
5.3.1 ListViewを使う
5.3.2 コードから項目を設定する
5.3.3 リスト項目の追加・削除
5.3.4 ListActivityを使う
5.3.5 選択モードについて
5.3.6 CheckTextViewによるリスト項目
5.3.7 CheckTextViewによるリスト項目
5.3.8 リスト項目のカスタマイズ
Chapter 6 インテントをマスターする
6.1 インテントの基本をマスターする
6.1.1 アクティビティとインテント
6.1.2 明示的インテントと暗示的インテント
6.1.3 アクティビティの切り替え
6.1.4 HelloAppActivityの修正
6.1.5 OtherActivityの作成
6.1.6 アクティビティのライフサイクル
6.1.7 暗示的インテントについて
6.2 インテントを活用する
6.2.1 ACTION_SENDアクションを使う
6.2.2 Intentから値を受け取る
6.2.3 Intentにオブジェクトを渡す
6.2.4 オブジェクトをIntentから受け取る
6.2.5 Intentから返り値を受け取る
6.3 ステータスバーの利用
6.3.1 ノーティフィケーション
6.3.2 ノーティフィケーションを作ってみる
6.3.3 ノーティフィケーション利用の流れ
6.3.4 主なプロパティを指定する
Chapter 7 グラフィックの描画
7.1 Viewクラスとグラフィック描画
7.1.1 Viewと描画の仕組み
7.1.2 MyViewクラスの作成
7.1.3 MyViewのソースコードを作成する
7.1.4 main.xmlにMyViewを追加する
7.1.5 onDrawメソッドの描画処理
7.1.6 主な図形の描画メソッド
7.1.7 図形のプロパティを設定する
7.1.8 カラーを使いこなす
7.1.9 テキストの描画
7.2 グラフィック機能を使いこなす
7.2.1 ActivityからMyViewを制御する
7.2.2 タッチして描画する
7.2.3 イメージの描画
7.2.4 パスを使ったクリッピング
7.2.5 座標変換について
7.2.6 座標軸の保存とリストア
7.3 SurfaceViewによる高速描画
7.3.1 SurfaceViewとは?
7.3.2 SurfaceViewクラスの基本ソースコード
7.3.3 描画コードを作成する
7.3.4 描画処理の流れを整理する
7.3.5 ユーザー操作による表示の更新
7.3.6 タイマースレッドとScheduledExecutorService
7.3.7 ScheduledExecutorService利用の流れ
Chapter 8 ハードウェアアクセス
8.1 センサーの利用
8.1.1 センサーとSensorEventListener
8.1.2 センサー利用の基本
8.1.3 センサー利用クラスの基本コード
8.1.4 センサーを使う
8.1.5 センサー利用の流れを整理する
8.1.6 GPSを利用する
8.1.7 GPSを使ったプログラムの実際
8.2 カメラの利用
8.2.1 カメラとSurfaceView
8.2.2 カメラを使ったサンプルの作成
8.2.3 カメラのプレビューを表示する
8.2.4 CameraとSurfaceViewの流れ
8.2.5 カメラで撮影をする
8.2.6 撮影の流れを整理する
8.2.7 Intentでカメラアプリと連携する
8.2.8 カメラアプリからプレビュー画像を取得する
Chapter 9 データの利用
9.1 データアクセス
9.1.1 Androidのデータ管理
9.1.2 テキストファイルのアクセス
9.1.3 テキストファイルへのアクセス
9.1.4 SQLiteの利用
9.1.5 main.xmlの準備
9.1.6 SQLiteOpenHelperクラスの作成
9.1.7 Activityクラスの作成
9.1.8 データの追加・削除・検索
9.2 設定画面による値保存
9.2.1 設定画面とPreferenceActivity
9.2.2 設定用ファイルの用意
9.2.3 PreferenceActivityクラスの作成
9.2.4 設定画面を利用する
9.2.5 設定を変更したときの処理
9.2.6 Activityから設定値を利用する
Chapter 10 アプリ以外のプログラム
10.1 サービス
10.1.1 サービスとは?
10.1.2 サービスの基本構造
10.1.3 Serviceクラスの作成
10.1.4 MyService/MyBinderの実装
10.1.5 ActivityからMyServiceを利用する
10.1.6 ServiceConnectionとBroadcastReceiver
10.1.7 サービスの機能を外部から呼び出す
10.2 ウィジェットの作成
10.2.1 ウィジェットとは?
10.2.2 XMLファイルの作成
10.2.3 レイアウトとマニフェストの修正
10.2.4 AppWidgetProviderクラスの作成
10.2.5 サービスとの連携
10.2.6 サービスを実装する
10.2.7 サービスの処理の流れを整理する
10.3 タブレット・アプリとフラグメント
10.3.1 タブレットと画面の分断問題
10.3.2 フラグメントの基本
10.3.3 プロジェクトを作成する
10.3.4 レイアウトの作成
10.3.5 マニフェストファイルの作成
10.3.6 HelloFragmentAppActivityの作成
10.3.7 TitlesFragmentクラスの作成
10.3.8 DetailsFragmentクラスの作成
Chapter 11 これから先の世界
11.1 学習の指針
11.1.1 まだやっていないこと
11.1.2 分断化問題について
11.1.3 Android 4.0について
11.1.4 Android Marketについて
2012-01-04 [Android] 15 パズル (2) - カスタム・ボタン ================================================================================ 前回の「15 パズル (1) - テーブル・レイアウト」 http://blog.goo.ne.jp/marunomarunogoo/e/36e4574a26b3362719a348dcdaf003d3 は、16 個の数字ボタンすべてに別々のハンドラーを設定していた。 このサンプルは、上記のサンプルに対し、次のようにしている。 ・数字ボタンに対するハンドラーをまとめた ・ボタン・クラスを継承したカスタム・ボタンを作る ・4x4 のボードに対応するクラスを作り、そこで、ボタンの移動などを管理する 今回作ったクラスはつぎの 3 つ。 FifteenPuzzleActivity アクティビティ FifteenPuzzleBoard ボード NumberButton 数字ブロック(カスタム・ボタン) なお、前回も使ったつぎのクラスに変更はない。 ArrayCollectionUtil 配列やコレクション関係のユーティリティ SymmetricGroupUtil 対称群と置換のユーティリティ ■ アクティビティ ハンドラーを定義して、ボードのメソッドを呼び出すだけになった。 □ FifteenPuzzleActivity.java --- package jp.marunomaruno.android.fifteenpuzzle; import android.app.Activity; import android.os.Bundle; import android.view.View; public class FifteenPuzzleActivity extends Activity { private FifteenPuzzleBoard board; // 15パズルのボード // (1) /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); board = new FifteenPuzzleBoard(this); // (2) } /** * 数字ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton(View v) { board.onClickNumberBlock((NumberButton) v); // (3) } /** * 開始ボタン・クリック時のハンドラー * @param v */ public void onClickStartButton(View v) { board.reset(); // (4) } } --- (1)(2) 15パズルのボード 4x4 のボードのオブジェクト。数字をずらすロジックなどは、こちらにある。 private FifteenPuzzleBoard board; // 15パズルのボード // (1) board = new FifteenPuzzleBoard(this); // (2) (3) 数字ボタン・クリック時のハンドラー呼び出し board.onClickNumberBlock((NumberButton) v); // (3) (4) 開始ボタン・クリック時のハンドラー呼び出し board.reset(); // (4) ■ ボード 4x4 のボードを管理するクラス。数字ボタンをクリックしたときの動きなどを規定してい る。 □ FifteenPuzzleBoard.java --- package jp.marunomaruno.android.fifteenpuzzle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import jp.marunomaruno.android.util.ArrayCollectionUtil; import jp.marunomaruno.android.util.SymmetricGroupUtil; import android.app.Activity; import android.content.Context; public class FifteenPuzzleBoard { public Context context; public static final int ORDER = 4; // 4次の正方行列 private NumberButton[] numberButtons; private int emptyBlockIndex = ORDER * ORDER; public FifteenPuzzleBoard(Context context) { this.context = context; Activity activity = (Activity) context; numberButtons = new NumberButton[] { null, // ボード上の番号と合わせるため、インデックス0をダミーとする (NumberButton) activity.findViewById(R.id.numberButton1), (NumberButton) activity.findViewById(R.id.numberButton2), (NumberButton) activity.findViewById(R.id.numberButton3), (NumberButton) activity.findViewById(R.id.numberButton4), (NumberButton) activity.findViewById(R.id.numberButton5), (NumberButton) activity.findViewById(R.id.numberButton6), (NumberButton) activity.findViewById(R.id.numberButton7), (NumberButton) activity.findViewById(R.id.numberButton8), (NumberButton) activity.findViewById(R.id.numberButton9), (NumberButton) activity.findViewById(R.id.numberButton10), (NumberButton) activity.findViewById(R.id.numberButton11), (NumberButton) activity.findViewById(R.id.numberButton12), (NumberButton) activity.findViewById(R.id.numberButton13), (NumberButton) activity.findViewById(R.id.numberButton14), (NumberButton) activity.findViewById(R.id.numberButton15), (NumberButton) activity.findViewById(R.id.numberButton16), }; // 最後のブロックを空とする numberButtons[emptyBlockIndex].setText(R.string.emptyText); log("initialize() "); } /** * 空ブロックが指定されたインデックスに入っているかどうか判定する。 * @param indexes * @return 空ブロックが指定されたインデックスに入っていればtrue */ private boolean isEmptyBlockIndexIn(int... indexes) { assert indexes.length > 0; return ArrayCollectionUtil.isKeyIn(emptyBlockIndex, indexes); } /** * 数字ボタン・クリック時のハンドラー * @param v */ public void onClickNumberBlock(NumberButton button) { // このブロックに対して、水平方向に有効な空ブロックの位置 final int[][] HORIZONTAL_INDEXES = new int[][]{ // (1) null, {2, 3, 4}, {1, 3, 4}, {1, 2, 4}, {1, 2, 3}, {6, 7, 8}, {5, 7, 8}, {5, 6, 8}, {5, 6, 7}, {10, 11, 12}, {9, 11, 12}, {9, 10, 12}, {9, 10, 11}, {14, 15, 16}, {13, 15, 16}, {13, 14, 16}, {13, 14, 15}, }; // このブロックに対して、垂直方向に有効な空ブロックの位置 final int[][] VERTICAL_INDEXES = new int[][]{ // (2) null, {5, 9, 13}, {6, 10, 14}, {7, 11, 15}, {8, 12, 16}, {1, 9, 13}, {2, 10, 14}, {3, 11, 15}, {4, 12, 16}, {1, 5, 13}, {2, 6, 14}, {3, 7, 15}, {4, 8, 16}, {1, 5, 9}, {2, 6, 10}, {3, 7, 11}, {4, 8, 12}, }; System.out.printf("onClick() %s", button.toString()); // 空の場合、何もしない if (isEmptyBlock(button)) { System.out.println(": empty"); return; } int blockIndex = button.getBlockIndex(); if (isEmptyBlockIndexIn(HORIZONTAL_INDEXES[blockIndex])) { // (3) rotateHorizontal(blockIndex); // (4) System.out.println(); // 最後にlogを出すための改行 return; } if (isEmptyBlockIndexIn(VERTICAL_INDEXES[blockIndex])) { rotateVertical(blockIndex); // (5) System.out.println(); // 最後にlogを出すための改行 return; } System.out.println(); // 最後にlogを出すための改行 } /** * 垂直にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateVertical(int index) { if (index < emptyBlockIndex) { rotateDown(index); } else { rotateUp(index); } } /** * 水平にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateHorizontal(int index) { if (index < emptyBlockIndex) { rotateRight(index); } else { rotateLeft(index); } } /** * 左にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateLeft(int index) { System.out.printf("rotateLeft() %d %d%n", index, emptyBlockIndex); rotateAscending(index, 1); } /** * 右にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateRight(int index) { System.out.printf("rotateRight() %d %d%n", index, emptyBlockIndex); rotateDescending(index, 1); } /** * 下にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateDown(int index) { System.out.printf("rotateDown() %d %d%n", index, emptyBlockIndex); rotateDescending(index, ORDER); } /** * 上にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateUp(int index) { System.out.printf("rotateUp() %d %d%n", index, emptyBlockIndex); rotateAscending(index, ORDER); } /** * 降順にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateDescending(int index, int step) { for (int i = emptyBlockIndex; i > index; i -= step) { numberButtons[i].setText(numberButtons[i - step].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * 昇順にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateAscending(int index, int step) { for (int i = emptyBlockIndex; i < index; i += step) { numberButtons[i].setText(numberButtons[i + step].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * このブロックが空かどうかを判断する。 * @return 空の場合true */ public boolean isEmptyBlock(NumberButton button) { return button.getBlockIndex() == emptyBlockIndex; } /** * ボードをリセットする */ public void reset() { // 1~15 までの乱数を生成する List<Integer> numberList = new ArrayList<Integer>(); numberList.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); int[] numberArray; do { Collections.shuffle(numberList); numberArray = ArrayCollectionUtil.toArray(numberList); } while (SymmetricGroupUtil.sgn(numberArray) != 1); // 解けない問題(遇置換でない)なら作り直し // 数字ブロックを設定する for (int i = 1; i < numberArray.length + 1; i++) { numberButtons[i].setText(String.valueOf(numberArray[i - 1])); } // 空ブロックを設定する numberButtons[ORDER * ORDER].setText(R.string.emptyText); emptyBlockIndex = ORDER * ORDER; log("onClickStartButton() "); } private void log(String message) { System.out.println(message); System.out.print(toString()); } @Override public String toString() { return String.format( "[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n[%s %s %s %s]%n", numberButtons[1].getText(), numberButtons[2].getText(), numberButtons[3].getText(), numberButtons[4].getText(), numberButtons[5].getText(), numberButtons[6].getText(), numberButtons[7].getText(), numberButtons[8].getText(), numberButtons[7].getText(), numberButtons[10].getText(), numberButtons[11].getText(), numberButtons[12].getText(), numberButtons[13].getText(), numberButtons[14].getText(), numberButtons[15].getText(), numberButtons[16].getText()); } } --- (1) このブロックに対して、水平方向に有効な空ブロックの位置の定義 最初はダミーで null を入れている。 final int[][] HORIZONTAL_INDEXES = new int[][]{ // (1) null, {2, 3, 4}, ... (中略) ... {13, 14, 15}, }; たとえば、自身が 2 のときは、水平方向に動かす場合、1 または 3, 4 に空ブロックがあることになる。 (2) このブロックに対して、垂直方向に有効な空ブロックの位置の定義 final int[][] VERTICAL_INDEXES = new int[][]{ // (2) null, {5, 9, 13}, ... (中略) ... {4, 8, 12}, }; (3)(4) 水平方向に空ブロックがあれば、水平方向にローテートする if (isEmptyBlockIndexIn(HORIZONTAL_INDEXES[blockIndex])) { // (3) rotateHorizontal(blockIndex); // (4) (5) 垂直方向に空ブロックがあれば、垂直方向にローテートする rotateVertical(blockIndex); // (5) なお、水平方向、垂直方向のローテートは、自身と空ブロックの位置関係により、 右・左ローテート、上・下ローテートすることになる。 また、左・上のローテートは、空ブロックを番号の大きい方向に持っていく(昇順)ロー テート、右・下のローテートは、空ブロックを番号の小さい方向に持っていく(降順)ロー テートとなっているので、ロジックはこの昇順・降順ローテートが担当する。 ■ 数字ブロック(ボタン) Button クラスを継承したカスタム・ボタンのクラス。 現在のブロックの位置を示すインデックスを管理する。 □ NumberButton.java --- package jp.marunomaruno.android.fifteenpuzzle; import android.content.Context; import android.util.AttributeSet; import android.widget.Button; /** * 数字ボタン。 * 2つの番号で管理する。 * ・ブロック・インデックス: 生成時のインデックスで、変更されない * ・番号: ブロックに表示される番号。 * @author marunomaruno */ public class NumberButton extends Button { // (1) /** * 空ブロックを示す番号 */ public static final int EMPTY_NUMBER = 0; // (2) private static int maxNumber = 0; // 最大の番号 // (3) private int blockIndex; // このブロックのインデックス(生成時のまま変更なし)// (4) public NumberButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } public NumberButton(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public NumberButton(Context context) { super(context); initialize(); } private void initialize() { maxNumber++; // (5) blockIndex = maxNumber; setText(String.valueOf(blockIndex)); } /** * このブロックの番号を設定する。 * @param newNumber 新しい番号 */ public void setNumber(int newNumber) { if (newNumber != EMPTY_NUMBER) { // (6) setText(String.valueOf(newNumber)); } else { setText(getResources().getString(R.string.emptyText)); } } /** * 番号を取得する。 * @return このブロックの番号。数値でない場合は0. */ public int getNumber() { try { return Integer.parseInt(getText().toString()); // (7) } catch (NumberFormatException e) { return EMPTY_NUMBER; } } /** * このブロックのインデックスを取得する。 * @return このブロックのインデックス */ public int getBlockIndex() { return blockIndex; } @Override public String toString() { return String.format("NB[%d, %s]", blockIndex, getText()); } } --- (1) Button クラスを継承 public class NumberButton extends Button { // (1) (2) 空ブロックを示す番号 public static final int EMPTY_NUMBER = 0; // (2) (3)(5) 最大の番号 このインスタンスを生成するときにインデックスとして番号を振っていくので、現在の最 大の番号を静的に保持する。 private static int maxNumber = 0; // 最大の番号 // (3) maxNumber++; // (5) (4) このブロックのインデックス このブロックのインデックス。これは、生成時のまま変更しない。 private int blockIndex; // このブロックのインデックス(生成時のまま変更な し)// (4) (6) このブロックの番号を設定 空ブロックでなければ、引数の値を設定する。 空ブロックのときは、リソースで指定された空ブロックの記号を設定する。 if (newNumber != EMPTY_NUMBER) { // (6) setText(String.valueOf(newNumber)); } else { setText(getResources().getString(R.string.emptyText)); } (7) このブロックの番号を取得 数値でない場合は 0 にする。 try { return Integer.parseInt(getText().toString()); // (7) } catch (NumberFormatException e) { return EMPTY_NUMBER; } ■レイアウト カスタム・ボタンを使っているので、ボタンのクラス名は、完全修飾名としてつぎを指定 する。 jp.marunomaruno.android.fifteenpuzzle.NumberButton 数字ブロッククリック時のハンドラーを同じものにした。 □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:orientation="vertical" > <Button android:id="@+id/start_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="onClickStartButton" android:text="@string/start" /> <TableLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" > <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton1" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="1" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton2" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="2" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton3" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="3" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton4" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="4" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton5" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="5" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton6" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="6" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton7" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="7" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton8" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="8" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton9" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="9" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton10" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="10" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton11" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="11" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton12" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="12" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton13" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="13" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton14" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="14" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton15" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="15" android:textAppearance="?android:attr/textAppearanceLarge" /> <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton16" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="@string/emptyText" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> </TableLayout> </LinearLayout> --- (1) カスタム・ボタンの指定 <jp.marunomaruno.android.fifteenpuzzle.NumberButton android:id="@+id/numberButton1" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton" android:text="1" android:textAppearance="?android:attr/textAppearanceLarge" /> 以上
※この記事はつぎの続き http://blog.goo.ne.jp/marunomarunogoo/e/36e4574a26b3362719a348dcdaf003d3 ■ ユーティリティ ▲ ArrayCollectionUtil Android SDKで用意されていない配列・コレクション関係のユーティリティ。 メソッド --- static int[] copyOf(int[] original) 配列を深いコピーする。 static boolean isKeyIn(int key, int... numbers) キーが数字の列の中に入っているかどうか判定する。 static int sequentialSearch(int[] a, int key) 順次検索で、指定された int 値の配列から指定された値の範囲を検索する。 static int[] toArray(List<Integer> list) Integer 型のリストから int 型の配列をつくり、設定する。 --- □ ArrayCollectionUtil.java --- package jp.marunomaruno.android.util; import java.util.List; /** * Android SDKで用意されていない配列・コレクション関係のユーティリティ * @author marunomaruno */ public class ArrayCollectionUtil { private ArrayCollectionUtil() { } /** * 配列を深いコピーする。 * ※java.util.Arrays.copyOf(int[] original, int newLength) の代用 * @param original コピー元配列 * @return 深いコピーした配列 */ public static int[] copyOf(int[] original) { int[] target = new int[original.length]; for (int i = 0; i < target.length; i++) { target[i] = original[i]; } return target; } /** * Integer 型のリストから int 型の配列をつくり、設定する。 * @param list Integer 型のリスト * @return Integer 型のリストを基に作った int 型配列 */ public static int[] toArray(List<Integer> list) { int[] array = new int[list.size()]; for (int i = 0; i < array.length; i++) { array[i] = list.get(i); } return array; } /** * キーが数字の列の中に入っているかどうか判定する。 * @param key 検索する数 * @param numbers 検索配列 * @return 数が指定された数字列に入っていればtrue */ public static boolean isKeyIn(int key, int... numbers) { return sequentialSearch(numbers, key) != -1; } /** * 順次検索で、指定された int 値の配列から指定された値の範囲を検索する。 * @param key 検索する数 * @param a 検索配列 * @return 見つかったときのインデックス。見つからなければ -1. */ public static int sequentialSearch(int [] a, int key) { for (int i : a) { if (key == i) { return i; } } return -1; } } --- ▲ SymmetricGroupUtil 対称群関係のユーティリティ。 [Java] 対称群と置換のユーティリティ http://blog.goo.ne.jp/marunomarunogoo/d/20120106 メソッド --- static int[] getPermutation(int size) 指定された要素数の置換を取得する。 static boolean isPermutation(int[] permutation) 指定された配列が置換になっているかどうかを確認する。 static int sgn(int[] permutation) 指定された置換の符号を取得する。 --- □ SymmetricGroupUtil.java --- package jp.marunomaruno.android.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * 対称群関係のユーティリティ。 * * @author marunomaruno * @version 1.0, 2012-01-06 */ public class SymmetricGroupUtil { private SymmetricGroupUtil() { } /** * 指定された置換の符号を取得する。 * * @param permutation * 置換を意味する配列 * @return 置換の符号 */ public static int sgn(int[] permutation) { System.out.printf("a=%s; [", Arrays.toString(permutation)); // 置換群かどうかを確認する if (!isPermutation(permutation)) { throw new IllegalArgumentException( "指定された配列は、置換群になっていません。"); } // ゼロ相対の添字と合わせるので、値を1引いたものにする int[] sigma = new int[permutation.length]; for (int i = 0; i < permutation.length; i++) { sigma[i] = permutation[i] - 1; } // 転倒数を数える int count = 0; for (int i = 0; i < sigma.length; i++) { // すでに循環置換として抽出したものは除く if (sigma[i] < 0) { continue; } // 巡回置換の(長さ-1)を加える count += (length(sigma, i) - 1); } System.out.printf("]; count=%d%n", count); return (count % 2 == 0) ? 1 : -1; // 本来は、(int) Math.pow(-1, count); } /** * 指定された置換の、指定された添字からの巡回置換の長さを取得する。 * なお、引数の置換は、中身が変更される可能性がある。 * * @param permutation * 置換 * @param from * 添字 * @return 指定された置換の、指定された添字からの巡回置換の長さ */ private static int length(int[] permutation, int from) { int length = 0; System.out.print(String.format("[%d", permutation[from] + 1)); int i = permutation[from]; while (permutation[from] >= 0 && i != from) { System.out.print(String.format(", %d", permutation[i] + 1)); length++; // つぎの循環置換の添字を取得し、循環置換に採用した要素を消す int k = i; i = permutation[i]; permutation[k] = -1; // 要素を消す } length++; System.out.printf("](%d), ", length); return length; } /** * 指定された配列が置換になっているかどうかを確認する。 * この置換は、次の条件を満たしている配列。 * ・要素数 n が 1 以上 * ・値は 1 ~ n までのそれぞれがひとつずつ入っている * * @param permutation * 置換の配列 * @return 置換になっていれば true */ public static boolean isPermutation(int[] permutation) { // ひとつ以上の要素を持つ if (permutation.length < 1) { return false; } // 置換をコピーして、ソートする // int[] sigma = Arrays.copyOf(permutation, permutation.length); int[] sigma = ArrayCollectionUtil.copyOf(permutation); Arrays.sort(sigma); // それぞれの添字がその要素値と違えば、置換にはなっていない for (int i = 0; i < sigma.length; i++) { if (sigma[i] != i + 1) { return false; } } return true; } /** * 指定された要素数の置換を取得する。 * * @param size * 要素数 * @return 置換 */ public static int[] getPermutation(int size) { // ひとつ以上の要素を持つ if (size < 1) { throw new IllegalArgumentException(String.format( "置換の要素数は1以上です。(%d)%n", size)); } List<Integer> sigma = new ArrayList<Integer>(size); for (int i = 0; i < size; i++) { sigma.add(i + 1); } Collections.shuffle(sigma); // シャッフルする int[] permutation = new int[size]; for (int i = 0; i < size; i++) { permutation[i] = sigma.get(i); } assert isPermutation(permutation); // 事後条件 return permutation; } } --- ■ レイアウト 16 個のボタンを 4x4 で配置する。それぞれのボタンに対して、ハンドラーをつける。 □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:orientation="vertical" > <Button android:id="@+id/start_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="onClickStartButton" android:text="@string/start" /> <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" > <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/numberButton1" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton1" android:text="1" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton2" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton2" android:text="2" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton3" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton3" android:text="3" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton4" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton4" android:text="4" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/numberButton5" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton5" android:text="5" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton6" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton6" android:text="6" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton7" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton7" android:text="7" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton8" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton8" android:text="8" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/numberButton9" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton9" android:text="9" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton10" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton10" android:text="10" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton11" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton11" android:text="11" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton12" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton12" android:text="12" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > <Button android:id="@+id/numberButton13" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton13" android:text="13" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton14" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton14" android:text="14" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton15" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton15" android:text="15" android:textAppearance="?android:attr/textAppearanceLarge" /> <Button android:id="@+id/numberButton16" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton16" android:text="@string/emptyText" android:textAppearance="?android:attr/textAppearanceLarge" /> </TableRow> </TableLayout> </LinearLayout> --- (1) テーブルレイアウト テーブル全体を <TableLayout> 要素で囲み、各行を <TableRow> 要素で囲む。各行の中の列は、そのままコンポーネントを書けばよい。 <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" > (2) テーブルの行 <TableRow android:layout_width="wrap_content" android:layout_height="wrap_content" > (3) ボタン 画面の幅全体になるように、 android:layout_width="0dip" android:layout_weight="1" としている。 また、ハンドラーはそれぞれ指定。 文字の大きさは android:textAppearance="?android:attr/textAppearanceLarge" として、大きくしている。 <Button android:id="@+id/numberButton1" android:layout_width="0dip" android:layout_weight="1" android:layout_height="90px" android:layout_margin="1px" android:onClick="onClickNumberButton1" android:text="1" android:textAppearance="?android:attr/textAppearanceLarge" /> 「?android:attr」は、android.R.attr クラスの定数。 textAppearance (テキストの見た目)関係の定数 --- int textAppearance int textAppearanceButton int textAppearanceInverse int textAppearanceLarge int textAppearanceLargeInverse int textAppearanceLargePopupMenu int textAppearanceListItem int textAppearanceListItemSmall int textAppearanceMedium int textAppearanceMediumInverse int textAppearanceSearchResultSubtitle int textAppearanceSearchResultTitle int textAppearanceSmall int textAppearanceSmallInverse int textAppearanceSmallPopupMenu --- 以上
2012-02-03 [Android] 15 パズル (1) - テーブル・レイアウト ================================================================================ 15 パズル。 テーブル・レイアウトを使って、15 個の数字ボタンと、空白ボタンを配置。 アルゴリズムはべた。 解ける問題のみを生成する。これには、 [Java] 対称群と置換のユーティリティ http://blog.goo.ne.jp/marunomarunogoo/d/20120106 を使う。15パズルが解けるのは偶置換のときなので、ここで作ったユーティリティ SymmetricGroupUtil を使う。 なお、このユーティリティと合わせて、配列やコレクション関係のユーティリティ ArrayCollectionUtil も追加する。 今回作ったクラスはつぎの 3 つ。 FifteenPuzzleActivity アクティビティ ArrayCollectionUtil 配列やコレクション関係のユーティリティ(*)(**) SymmetricGroupUtil 対称群と置換のユーティリティ(*) (*) ユーティリティ関係として、パッケージは jp.marunomaruno.android.util とした。 (**) 上記「[Java] 対称群と置換のユーティリティ」とほぼ同じ ■ アクティビティ すべてこのアクティビティのクラスでロジックを構築。 1~15 のボタンと、空のブロックに対するボタン 16 個に対して、すべてハンドラー・メ ソッドをつけた。 基本的な考えは、ボタンの位置はすべてハンドラー・メソッドになっているので、これで わかる。空ブロックの位置を覚えておき、数字ブロックと空ブロックの間のブロックを ローテートする。 □ FifteenPuzzleActivity --- package jp.marunomaruno.android.fifteenpuzzle; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import jp.marunomaruno.android.util.ArrayCollectionUtil; import jp.marunomaruno.android.util.SymmetricGroupUtil; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; /** * 15パズルのアクティビティ * @author marunomaruno */ public class FifteenPuzzleActivity extends Activity { public static final int ORDER = 4; // 4次の正方行列 // (1) private Button[] numberButtons; // 数字ボタン // (2) private int emptyBlockIndex = ORDER * ORDER; // 空ブロックのインデックス // (3) /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); numberButtons = new Button[] { null, // ボード上の番号と合わせるため、インデックス0をダミーとする // (4) (Button) findViewById(R.id.numberButton1), (Button) findViewById(R.id.numberButton2), (Button) findViewById(R.id.numberButton3), (Button) findViewById(R.id.numberButton4), (Button) findViewById(R.id.numberButton5), (Button) findViewById(R.id.numberButton6), (Button) findViewById(R.id.numberButton7), (Button) findViewById(R.id.numberButton8), (Button) findViewById(R.id.numberButton9), (Button) findViewById(R.id.numberButton10), (Button) findViewById(R.id.numberButton11), (Button) findViewById(R.id.numberButton12), (Button) findViewById(R.id.numberButton13), (Button) findViewById(R.id.numberButton14), (Button) findViewById(R.id.numberButton15), (Button) findViewById(R.id.numberButton16), }; log("initialize() "); } /** * インデックス 1 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton1(View v) { // (5) final int BLOCK_INDEX = 1; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { // (6) return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(2, 3, 4)) { // (7) rotateRight(BLOCK_INDEX); // (8) return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(5, 9, 13)) { rotateDown(BLOCK_INDEX); // (9) return; } } /** * インデックス 2 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton2(View v) { final int BLOCK_INDEX = 2; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(1)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(3, 4)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(6, 10, 14)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 3 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton3(View v) { final int BLOCK_INDEX = 3; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(1, 2)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(4)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(7, 11, 15)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 4 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton4(View v) { final int BLOCK_INDEX = 4; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(1, 2, 3)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(8, 12, 16)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 5 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton5(View v) { final int BLOCK_INDEX = 5; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(6, 7, 8)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(1)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(9, 13)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 6 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton6(View v) { final int BLOCK_INDEX = 6; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(5)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(7, 8)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(2)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(10, 14)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 7 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton7(View v) { final int BLOCK_INDEX = 7; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(5, 6)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(8)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(3)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(11, 15)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 8ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton8(View v) { final int BLOCK_INDEX = 8; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(5, 6, 7)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(4)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(12, 16)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 9 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton9(View v) { final int BLOCK_INDEX = 9; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(10, 11, 12)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(1, 5)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(13)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 10 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton10(View v) { final int BLOCK_INDEX = 10; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(9)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(11, 12)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(2, 6)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(14)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 11 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton11(View v) { final int BLOCK_INDEX = 11; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(9, 10)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(12)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(3, 7)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(15)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 12 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton12(View v) { final int BLOCK_INDEX = 12; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(9, 10, 11)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(4, 8)) { rotateUp(BLOCK_INDEX); return; } // 空ブロックが下にあるとき、下にローテートする if (isEmptyBlockIndexIn(16)) { rotateDown(BLOCK_INDEX); return; } } /** * インデックス 13 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton13(View v) { final int BLOCK_INDEX = 13; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(14, 15, 16)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(1, 5, 9)) { rotateUp(BLOCK_INDEX); return; } } /** * インデックス 14 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton14(View v) { final int BLOCK_INDEX = 14; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(13)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(15, 16)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(2, 6, 10)) { rotateUp(BLOCK_INDEX); return; } } /** * インデックス 15 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton15(View v) { final int BLOCK_INDEX = 15; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(13, 14)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが右にあるとき、右にローテートする if (isEmptyBlockIndexIn(16)) { rotateRight(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(3, 7, 11)) { rotateUp(BLOCK_INDEX); return; } } /** * インデックス 16 ボタン・クリック時のハンドラー * @param v */ public void onClickNumberButton16(View v) { final int BLOCK_INDEX = 16; Button button = (Button) v; System.out.printf("onClickNumberButton%d() %s %d%n", BLOCK_INDEX, button.getText(), emptyBlockIndex); // このブロックが空なら何もしない if (emptyBlockIndex == BLOCK_INDEX) { return; } // 空ブロックが左にあるとき、左にローテートする if (isEmptyBlockIndexIn(13, 14, 15)) { rotateLeft(BLOCK_INDEX); return; } // 空ブロックが上にあるとき、上にローテートする if (isEmptyBlockIndexIn(4, 8, 12)) { rotateUp(BLOCK_INDEX); return; } } /** * 左にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateLeft(int index) { System.out.printf("rotateLeft() %d %d%n", index, emptyBlockIndex); for (int i = emptyBlockIndex; i < index; i++) { numberButtons[i].setText(numberButtons[i + 1].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * 右にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateRight(int index) { System.out.printf("rotateRight() %d %d%n", index, emptyBlockIndex); for (int i = emptyBlockIndex; i > index; i--) { numberButtons[i].setText(numberButtons[i - 1].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * 下にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateDown(int index) { System.out.printf("rotateDown() %d %d%n", index, emptyBlockIndex); for (int i = emptyBlockIndex; i > index; i -= ORDER) { numberButtons[i].setText(numberButtons[i - ORDER].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * 上にローテートする。 * @param index 空ブロックを置く位置 */ private void rotateUp(int index) { System.out.printf("rotateUp() %d %d%n", index, emptyBlockIndex); for (int i = emptyBlockIndex; i < index; i += ORDER) { numberButtons[i].setText(numberButtons[i + ORDER].getText()); } numberButtons[index].setText(R.string.emptyText); emptyBlockIndex = index; } /** * 空ブロックが指定されたインデックスに入っているかどうか判定する。 * @param indexes * @return 空ブロックが指定されたインデックスに入っていればtrue */ private boolean isEmptyBlockIndexIn(int... indexes) { assert indexes.length > 0; return ArrayCollectionUtil.isKeyIn(emptyBlockIndex, indexes); // (10) } /** * 開始ボタン・クリック時のハンドラー * @param v */ public void onClickStartButton(View v) { // (11) // 1~15 までの乱数を生成する List<Integer> numberList = new ArrayList<Integer>(); numberList.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); // 解ける問題を生成する int[] numberArray; do { Collections.shuffle(numberList); numberArray = ArrayCollectionUtil.toArray(numberList); } while (SymmetricGroupUtil.sgn(numberArray) != 1); // 解けない問題(遇置換でない)なら作り直し // (12) // 数字ブロックを設定する for (int i = 1; i < numberArray.length + 1; i++) { // (13) numberButtons[i].setText(String.valueOf(numberArray[i - 1])); // (14) } // 空ブロックを設定する numberButtons[ORDER * ORDER].setText(R.string.emptyText); emptyBlockIndex = ORDER * ORDER; log("onClickStartButton() "); } private void log(String message) { System.out.println(message); for (int i = 1; i < numberButtons.length; i += 4) { System.out.printf("[%s %s %s %s]%n", numberButtons[i + 0].getText(), numberButtons[i + 1] .getText(), numberButtons[i + 2].getText(), numberButtons[i + 3].getText()); } } } --- (1) 4 次の正方行列の次数の定数定義 15 パズルなので、4x4 の正方行列としてデータを扱う。ただし、実際には 16 個の要素 を持つ配列としてデータを保持する。 正方行列は以下のようなインデックスを持つとする。 [ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12] [13 14 15 16] public static final int ORDER = 4; // 4 次の正方行列 // (1) (2)(4) 数字ボタンの配列 private Button[] numberButtons; // 数字ボタン // (2) ボード上のボード上の番号と合わせるため、インデックス 0 をダミーとする。Button 型 なので、null を入れておく。 numberButtons = new Button[] { null, // ボード上の番号と合わせるため、インデックス0をダミーとする // (4) (Button) findViewById(R.id.numberButton1), (Button) findViewById(R.id.numberButton2), (Button) findViewById(R.id.numberButton3), (Button) findViewById(R.id.numberButton4), (Button) findViewById(R.id.numberButton5), (Button) findViewById(R.id.numberButton6), (Button) findViewById(R.id.numberButton7), (Button) findViewById(R.id.numberButton8), (Button) findViewById(R.id.numberButton9), (Button) findViewById(R.id.numberButton10), (Button) findViewById(R.id.numberButton11), (Button) findViewById(R.id.numberButton12), (Button) findViewById(R.id.numberButton13), (Button) findViewById(R.id.numberButton14), (Button) findViewById(R.id.numberButton15), (Button) findViewById(R.id.numberButton16), }; (3) 空ブロックのインデックス パズルの初期状態は、インデックス 16 の位置に空ブロックがあるので、空ブロックのイ ンデックスを 16 に設定する。 private int emptyBlockIndex = ORDER * ORDER; // 空ブロックのインデックス // (3) (5) インデックス 1 ボタン・クリック時のハンドラー インデックス 1~16 までに対するボタン・クリック時のハンドラーをすべて定義する。 public void onClickNumberButton1(View v) { // (5) (6) このブロックが空なら何もしない 空ブロックの場合は、何もせずに終わる。 if (emptyBlockIndex == BLOCK_INDEX) { // (6) return; } (7)(8) 空ブロックが右にあるとき、右にローテートする 自身が 1 のとき、空ブロックが右にあるかどうかは、空ブロックのインデックスが 2, 3, 4 のいずれかの場合である。 これは、プライベートなメソッドをつくって、これを確認している。なお、空ブロックの インデックスは、インスタンス変数 emptyBlockIndex がもっているので、この引数には 指定していない。 if (isEmptyBlockIndexIn(2, 3, 4)) { // (7) 空ブロックが自ブロックの右側にあれば、右にローテートする。 rotateRight(BLOCK_INDEX); // (8) 仮に、自身が 1 で、空ブロックが 3 であれば、この行は次のようになる。 [ 1 2 3 4] -> [ 3 1 2 4] (9) 空ブロックが下にあるとき、下にローテートする (8)と同様に、空ブロックが下にあれば、下にローテートする。 rotateDown(BLOCK_INDEX); // (9) 仮に、自身が 1 で、空ブロックが 9 であれば、この列は次のようになる。 [ 1 ...] [ 9 ...] [ 5 ...] -> [ 1 ...] [ 9 ...] [ 5 ...] [13 ...] [13 ...] その他、左ローテート、上ローテートがある。 仮に、自身が 11 で、空ブロックが 9 であれば、左ローテートの行は次のようになる。 [ 9 10 11 12] -> [10 11 9 12] また、自身が 13 で、空ブロックが 5 であれば、上ローテートの列は次のようになる。 [ 1 ...] [ 1 ...] [ 5 ...] -> [ 9 ...] [ 9 ...] [13 ...] [13 ...] [ 5 ...] (10) 空ブロックが指定されたインデックスに入っているかどうか判定する。 これは、ArrayCollectionUtil クラスの isKeyIn() メソッドを使う。 return ArrayCollectionUtil.isKeyIn(emptyBlockIndex, indexes); // (10) (11) 開始ボタン・クリック時のハンドラー public void onClickStartButton(View v) { // (11) (12) 解ける問題を生成する Collections.shuffle() を使って、ランダムに 1~15 までを設定する。ただし、このとき、 これらの値が遇置換でない場合は 15 パズルとして解けないので作り直す。 do { Collections.shuffle(numberList); numberArray = ArrayCollectionUtil.toArray(numberList); } while (SymmetricGroupUtil.sgn(numberArray) != 1); // 解けない問題(遇置換でない)なら作り直し // (12) (13)(14) 数字ブロックを設定する ランダムな数字列はインデックス 0 からであるのに対し、数字ボタンのブロックはイン デックス 1 からなので、調整しながら数字をブロックに設定する。 for (int i = 1; i < numberArray.length + 1; i++) { // (13) numberButtons[i].setText(String.valueOf(numberArray[i - 1])); // (14) } ■ 設定データ □ res/values/strings.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">15パズル</string> <string name="start">スタート</string> <string name="emptyText">★</string> </resources> --- ※この記事は続く http://blog.goo.ne.jp/marunomarunogoo/d/20120203 以上
[Android] エミュレーター(ADV)で日本語を使う
================================================================================
1. 日本語で表示する
エミュレーターの Settings アイコンから設定する。
つぎで日本語が表示できるようになる。
[Settings] > [Language & keyboard] > [Select language] > [日本語] を選択
2. 日本語を入力する
さらに 同じ [Select language] のメニューの中に入っている
[Androidキーボードと谷歌...法] (※漢字変換が簡単でないので途中省略)
を OFF にする。こうすることで、携帯電話形式のキーボードが出る。
以上