[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 にする。こうすることで、携帯電話形式のキーボードが出る。
以上
[AppInventor] App Inventor のソースを取得する ================================================================================ 1. ソースつぎのサイトから取得する。 app-inventor-releases http://code.google.com/p/app-inventor-releases/ ここに、説明がついている。 2. つぎのサイトから Mercurial をダウンロードして、設定する。 Mercurial downloads http://mercurial.selenic.com/downloads/ Mercurial 2.0.2 Inno Setup installer - x86 Windows - does not require admin rights インストーラーが、PATH の設定まで行ってくれる。 ※Mercurial(マーキュリアル)は、クロスプラットフォームの分散型バージョン管理シ ステム。 Mercurial はコマンドラインプログラムで、すべてのコマンドは hgで始まる。 (Mercurial - Wikipedia、http://ja.wikipedia.org/wiki/Mercurial より) ※Mercurial の使い方のチュートリアル http://mercurial.selenic.com/wiki/JapaneseTutorial 3. ソースの取得 つぎのコマンドで取得する。 --- > hg clone https://code.google.com/p/app-inventor-releases/ destination directory: app-inventor-releases requesting all changes adding changesets adding manifests adding file changes added 1 changesets with 1718 changes to 1718 files updating to branch default 1718 files updated, 0 files merged, 0 files removed, 0 files unresolved --- ソースは、カレント・ディレクトリー直下の以下のディレクトリーに入る。 app-inventor-releases
グラフィックス (3) 円の軌跡で線を描く ================================================================================ 次のサンプル。 ・円の軌跡を使って線を描く ・クリア・ボタンをつける 「クリア・ボタンをつける」で、再び、レイアウト(res/layout/main.xml) を使うように なる。 ■ 円の軌跡で線を描く 円の軌跡を覚えておき、それをすべて描画することで、見た目としては線を描いているよ うにする。もちろん、指を早く動かすと、単なる点点点にしかならない。 軌跡は、タッチして動かしているときのイベントからとられる座標を PointF オブジェク トとして、List を使って保持しておく。 アクティビティや、レイアウト(res/layout/main.xml) は、前回と同じである。 グラフィックス (1) http://blog.goo.ne.jp/marunomarunogoo/d/20111016 ◆アクティビティ 円の軌跡になる点のリストをインスタンス変数にして、これを使って、イベントが起きる たびにすべての点を再描画している。 □ GraphicsView.java --- package jp.marunomaruno.android.graphics; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Paint.Style; import android.util.AttributeSet; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; /** * 円の軌跡を描く。 * @author marunomaruno * @see Graphics05Activity */ public class GraphicsView extends View { private Paint paint; private List<PointF> pointList; // 円の軌跡になる点のリスト // (1) private float radius; // 円の半径 public GraphicsView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } public GraphicsView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public GraphicsView(Context context) { super(context); initialize(); } private void initialize() { // ペイントオブジェクトを設定する paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.BLUE); paint.setStyle(Style.FILL); // 軌跡を描画する初期値を設定する pointList = new ArrayList<PointF>(); // (2) radius = 10; } @Override protected void onDraw(Canvas canvas) { // 格子を描画する drawGrid(canvas, 50); // 軌跡を描画する for (PointF point : pointList) { // (3) canvas.drawCircle(point.x, point.y, radius, paint); // (4) } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 指をタッチした // (5) case MotionEvent.ACTION_MOVE: // 指を動かしている // (6) pointList.add(new PointF(event.getX(), event.getY())); // (7) break; case MotionEvent.ACTION_UP: // 指を離した assert true; // 何もしない break; default: assert true; // 何もしない break; } invalidate(); return true; } /** * 画面に格子を描画する。 * @param canvas * @param interval 格子を描く間隔 */ private void drawGrid(final Canvas canvas, int interval) { // 画面のサイズを取得する WindowManager manager = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); // 格子を描画する Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setStrokeWidth(1); for (int i = 0; i < Math.max(width, height); i += interval) { canvas.drawText(Integer.toString(i), i, paint.getTextSize(), paint); canvas.drawLine(i, 0, i, height, paint); canvas.drawText(Integer.toString(i), 0, i, paint); canvas.drawLine(0, i, width, i, paint); } } } --- (1) 円の軌跡になる点のリスト 点の座標は、PointF クラスのオブジェクトとして持つ。 private List<PointF> pointList; // 円の軌跡になる点のリスト // (1) ・PointF クラス java.lang.Object + android.graphics.PointF ・主なフィールド --- public float x public float y --- public なので、直接、このインスタンス変数を使うことができる。 コードとしては、かなりすっきりした見た目になる。 ・コンストラクター --- PointF() PointF(float x, float y) PointF(Point p) --- ・主なメソッド --- final boolean equals(float x, float y) final float length() 原点 (0, 0) からの距離 static float length(float x, float y) (0, 0) - (x, y) の距離 final void negate() final void offset(float dx, float dy) (cx, cy) - (dx, dy) の距離 final void set(float x, float y) final void set(PointF p) --- (2) 軌跡を描画する初期値を設定する 空のリストを準備する。 pointList = new ArrayList<PointF>(); // (2) (3)(4) 軌跡を描画する 座標リストから座標をひとつづつ取り出して、それをもとに描画する。 for (PointF point : pointList) { // (3) canvas.drawCircle(point.x, point.y, radius, paint); // (4) } (5)(6)(7) ハンドラーの処理 指がタッチしたときと、動いているときのイベントの座標をリストに追加する。 case MotionEvent.ACTION_DOWN: // 指をタッチした // (5) case MotionEvent.ACTION_MOVE: // 指を動かしている // (6) pointList.add(new PointF(event.getX(), event.getY())); // (7) ■ クリアボタンをつける 線を描くだけではなく、描いた線をすべて消せるように、クリアボタンをつける。 ボタンを描画域(カスタム・ビュー)の上につけることもできるが、描画域とは別にボタン があったほうが使い勝手はよい。 したがって、今までのサンプルとは違い、res/layout/main.xml で、クリアボタンとカス タムビューで画面をレイアウトする。 res/layout/main.xml で今まで LinearLayout や、Button などといった要素を書いてき たが、これも Android が用意している View クラスのサブクラスである。 自分で作っているカスタムビューのクラスも、View クラスのサブクラスとして、 res/layout/main.xml で、Button などの標準で用意されているビューのコンポーネント と同じように使える。ただし、標準ではないので、要素名は完全修飾名を使う。 ◆アクティビティ □ Graphics051Activity --- package jp.marunomaruno.android.graphics; import android.app.Activity; import android.os.Bundle; import android.view.View; public class Graphics051Activity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // (1) } /** * 画面ををクリアする。 * @param view */ public void onClickClearButton(View view) { GraphicsView graphicsView = (GraphicsView) findViewById(R.id.graphicsView); graphicsView.clear(); // (2) } } --- (1) レイアウトの割り当て setContentView(R.layout.main); // (1) (2) 画面をクリアする graphicsView.clear(); // (2) ◆カスタムビュー クリアするメソッド clear() を追加しただけ。 □ GraphicsView.java --- package jp.marunomaruno.android.graphics; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Paint.Style; import android.util.AttributeSet; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; /** * 円の軌跡を描く。クリアボタン付き。 * @author marunomaruno * @see Graphics051Activity */ public class GraphicsView extends View { private Paint paint; private List<PointF> pointList; // 円の軌跡になる点のリスト private float radius; // 円の半径 public GraphicsView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } public GraphicsView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public GraphicsView(Context context) { super(context); initialize(); } private void initialize() { // ペイントオブジェクトを設定する paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.BLUE); paint.setStyle(Style.FILL); // 軌跡を描画する初期値を設定する pointList = new ArrayList<PointF>(); radius = 10; } @Override protected void onDraw(Canvas canvas) { // 格子を描画する drawGrid(canvas, 50); // 軌跡を描画する for (PointF point : pointList) { canvas.drawCircle(point.x, point.y, radius, paint); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 指をタッチした case MotionEvent.ACTION_MOVE: // 指を動かしている pointList.add(new PointF(event.getX(), event.getY())); break; case MotionEvent.ACTION_UP: // 指を離した assert true; // 何もしない break; default: assert true; // 何もしない break; } invalidate(); return true; } /** * 画面に格子を描画する。 * @param canvas * @param interval 格子を描く間隔 */ private void drawGrid(final Canvas canvas, int interval) { // 画面のサイズを取得する WindowManager manager = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); // 格子を描画する Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setStrokeWidth(1); for (int i = 0; i < Math.max(width, height); i += interval) { canvas.drawText(Integer.toString(i), i, paint.getTextSize(), paint); canvas.drawLine(i, 0, i, height, paint); canvas.drawText(Integer.toString(i), 0, i, paint); canvas.drawLine(0, i, width, i, paint); } } /** * 軌跡をクリアする。 */ public void clear() { // (1) initialize(); // (2) invalidate(); // (3) } } --- (1)-(3) 軌跡をクリアする public void clear() { // (1) 座標のリストをクリアするので、初期値かメソッドを呼ぶ。 initialize(); // (2) 画面を無効にして描画しなおす。 invalidate(); // (3) ◆レイアウト □ 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" > <Button android:id="@+id/clearButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="onClickClearButton" android:text="@string/clearButton" /> <jp.marunomaruno.android.graphics.GraphicsView android:id="@+id/graphicsView" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> --- (1) カスタムビューの指定 要素名は、カスタム・ビュー・クラスの完全修飾名を記す。 <jp.marunomaruno.android.graphics.GraphicsView android:id="@+id/graphicsView" android:layout_width="fill_parent" android:layout_height="fill_parent" /> ◆文字列 □ res/values/strings.xml --- <resources> <string name="app_name">Graphics051</string> <string name="clearButton">クリア</string> </resources> --- 以上
[Android] グラフィックス (2) 指を追うように円を動かす - タッチ・イベント ================================================================================ 画面に描いた円が、タッチした指を追って動くように描画するサンプル。 タッチしたことに対して、何か処理を行うので、タッチ・イベントに対するハンドラーを つくる。ハンドラーは、View クラスの onTouchEvent() メソッドをオーバーライドする。 アクティビティや、レイアウト(res/layout/main.xml) は、前回と同じである。 グラフィックス (1) http://blog.goo.ne.jp/marunomarunogoo/d/20111016 ◆ 描画関係のクラス □ GraphicsView.java --- package jp.marunomaruno.android.graphics; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Style; import android.util.AttributeSet; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; /** * 指を追うように円動かす。 * @author marunomaruno * @see Graphics04Activity */ public class GraphicsView extends View { private Paint paint; private float cx; // 図形を描画する X 座標 // (1) private float cy; // 図形を描画する Y 座標 // (2) private float radius; // 円の半径 // (3) public GraphicsView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialize(); } public GraphicsView(Context context, AttributeSet attrs) { super(context, attrs); initialize(); } public GraphicsView(Context context) { super(context); initialize(); } private void initialize() { // ペイントオブジェクトを設定する paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.BLUE); // (4) paint.setStyle(Style.FILL); // (5) // 丸を描画する初期値を設定する cx = 100; cy = 200; radius = 50; } @Override protected void onDraw(Canvas canvas) { // 格子を描画する drawGrid(canvas, 50); // 円を描画する canvas.drawCircle(cx, cy, radius, paint); // (6) } @Override public boolean onTouchEvent(MotionEvent event) { // (7) switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 指をタッチした // (8) assert true; // 何もしない break; case MotionEvent.ACTION_MOVE: // 指を動かしている // (9) cx = event.getX(); // (10) cy = event.getY(); // (11) break; case MotionEvent.ACTION_UP: // 指を離した // (12) assert true; // 何もしない break; default: assert true; // 何もしない break; } invalidate(); // (13) return true; // (14) } /** * 画面に格子を描画する。 * @param canvas * @param interval 格子を描く間隔 */ private void drawGrid(final Canvas canvas, int interval) { // 画面のサイズを取得する WindowManager manager = (WindowManager) getContext().getSystemService( Context.WINDOW_SERVICE); Display display = manager.getDefaultDisplay(); int width = display.getWidth(); int height = display.getHeight(); // 格子を描画する Paint paint = new Paint(); // (15) paint.setColor(Color.WHITE); paint.setStrokeWidth(1); for (int i = 0; i < Math.max(width, height); i += interval) { canvas.drawText(Integer.toString(i), i, paint.getTextSize(), paint); canvas.drawLine(i, 0, i, height, paint); canvas.drawText(Integer.toString(i), 0, i, paint); canvas.drawLine(0, i, width, i, paint); } } } --- (1)(2) 図形を描画する座標の保持 onTouchEvent() で取得した座標を、onDraw() メソッドで使うので、インスタンス変数に しておく。 private float cx; // 図形を描画する X 座標 // (1) private float cy; // 図形を描画する Y 座標 // (2) (3) 円の半径 private float radius; // 円の半径 // (3) ただし、これはプログラム中で変更しないのであれば、定数でもよい。 (4)(5) ペイントオブジェクトを設定する 今回は、色を青、円の中は塗りつぶすことにする。 paint.setColor(Color.BLUE); // (4) paint.setStyle(Style.FILL); // (5) (6) 円を描画する drawCircle() メソッドを使って円を描く。 canvas.drawCircle(cx, cy, radius, paint); // (6) (7) タッチイベントのハンドラー・メソッド View クラスの次のメソッドをオーバーライドする。 public boolean onTouchEvent(MotionEvent event) { // (7) 引数は MotionEvent オブジェクト。 返り値は、イベントの処理が終わった(true)か終わっていないか(false)。 ・MotionEvent クラス 指やマウス、ペン、トラックボールの動きのイベント。 java.lang.Object + android.view.InputEvent + android.view.MotionEvent ・Action 関係で、指の動き関係(と思われる)の定数 --- int ACTION_DOWN タッチしたとき(指をつけたとき) int ACTION_MOVE 指を動かしているとき(ACTION_DOWN と ACTION_UP の間) int ACTION_UP 指を離したとき --- ・主なメソッド --- final int getAction() final int getPointerCount() final int getPointerId(int pointerIndex) final float getX(int pointerIndex) final float getY(int pointerIndex) final float getY() --- (8)(9)(12) アクションによる処理 上記 getAction() メソッドにより、どのアクションかを取得した後、アクションごとに そのアクションに対する処理をケースわけしていく。 case MotionEvent.ACTION_DOWN: // 指をタッチした // (8) case MotionEvent.ACTION_MOVE: // 指を動かしている // (9) case MotionEvent.ACTION_UP: // 指を離した // (12) (10)(11) 指を動かしているときに、その位置の座標を取得する タッチしている位置は、event オブジェクトの getX()、getY() メソッドを使って取得す る。 cx = event.getX(); // (10) cy = event.getY(); // (11) (13) 再描画 ビュー全体を一度無効にすることで、再描画する。 invalidate(); // (13) ・View クラスのオーバーロードされている invalidate() メソッド --- void invalidate(Rect dirty) void invalidate(int l, int t, int r, int b) void invalidate() void invalidateDrawable(Drawable drawable) --- (14) アクションは処理済とする return true; // (14) この返り値が false のままだと、未処理となるので、つぎのイベントを拾えない。 今回は、MotionEvent を拾うハンドラーは、このメソッドなので、true にしておく。 (15) 格子描画用のペイント・オブジェクト 格子に使う使う色や線の太さなど、描きたい図形と違う場合があるので、格子用のペイン ト・オブジェクトを作っておく。 Paint paint = new Paint(); // (15)
[Android][SQLite] DBViewer プラグインのインストールと設定 ================================================================================ ■ 1. 必要なファイルをダウンロード ・SQLiteJDBC ドライバーのダウンロード SQLiteJDBC http://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC http://www.xerial.org/maven/repository/artifact/org/xerial/sqlite-jdbc/3.7.2/ から、 sqlite-jdbc-3.7.2.jar をダウンロード これを適当なフォルダに入れておく。 たとえば、 %JAVA_HOME%¥jre¥lib¥ext に入れておくと、他の Java アプリケーションなどでも利用できる。 %JAVA_HOME% は、JavaSE をインストールしたホームディレクトリー。 ・Eclipse プラグインの DBViewer をダウンロード DBViewer Plugin for Eclipse 開発プロジェクト http://www.ne.jp/asahi/zigen/home/plugin/dbviewer/about_jp.html http://sourceforge.jp/projects/dbviewer/releases/49262 から、 zigen.plugin.db_1.2.2.v20101009.jar をダウンロード これは、 %ECLIPSE_HOME%¥plugins に入れておく。 %ECLIPSE_HOME%は、Eclipse をインストールしたホームディレクトリー。 ■ 2. Eclipse での設定 □ データベースを定義 ・つぎの操作で、DBツリー・ビューを表示する [Windows] - [Show View] - [Other...] - [DBViewer Plugin] - [DBツリー・ビュー] ・データベースの定義 [DBViewer Plugin]を右クリックして [登録] 以下を設定 --- データベース定義名 任意に指定(プロジェクト名とデータベース名を組み合わせると わかりやすい) JDBCドライバーのパス [ファイルの追加]で、ダウンロードした JAR ファイルを指定 --- 上記を設定したら [次へ] 以下を設定 --- JDBCドライバー org.sqlight.JDBC ... 自動で設定されている 接続文字列 jdbc:sqlite:【データベースのパス】 --- DDMSのファイル・エクスプローラーからダウンロードしたものは、デフォルトで [C:¥Documents and Settings¥【ユーザー名】] に入るので、ここのパスをそのまま接続文字列の[データベースのパス]にする。 たとえば、 jdbc:sqlite:C:¥Documents and Settings¥user¥SampleDB ■ 参考 URL インストールについては以下を参照 SQLiteデータベースの中身を見る[DBViewer] http://www.andro-dev.com/201104/sqlitedbviewer.php
センサー (3) 方位 ================================================================================ ■ 方位を取得する 方位は、それ専用のセンサーがあるのではなく、加速度センサーと磁気センサーの値から 算出する。この 2 つのセンサーを使うこと以外は、いままでのサンプルと同じである。 Sensor.TYPE_ORIENTATION は、非推奨になっているので、これは使わない。 ◆ アクティビティのクラス。 □ OrientationSensor01Activity.java --- package jp.marunomaruno.android.orientationsensor; import android.app.Activity; import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.widget.TextView; import jp.marunomaruno.android.orientationsensor.R; /** * 方位を表示する。 * @author marunomaruno * @version 1.0, 2011-12-01 */ public class OrientationSensor01Activity extends Activity { // TextView private TextView orientationTextView; // 方位センサーの表示域 private SensorManager sensorManager; private SensorEventListener sensorEventListener; // onPause()が呼ばれたときに、すべてのリスナーを解除するため @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); orientationTextView = (TextView) findViewById(R.id.sensor_value); sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); } @Override protected void onResume() { super.onResume(); sensorEventListener = new OrientationSensorListener(orientationTextView); // (1) // 加速度センサーを取得して登録する for (Sensor sensor : sensorManager .getSensorList(Sensor.TYPE_ACCELEROMETER)) { sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL); } // 磁気センサーを取得して登録する for (Sensor sensor : sensorManager .getSensorList(Sensor.TYPE_MAGNETIC_FIELD)) { sensorManager.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL); } } @Override public void onPause() { super.onPause(); // すべてのリスナーを解除する sensorManager.unregisterListener(sensorEventListener); } } --- (1) 方位センサーのリスナー・オブジェクトを生成する sensorEventListener = new OrientationSensorListener(orientationTextView); // (1) そして、このオブジェクトを、加速度センサー、磁気センサーに登録しておく。 ◆ センサーのリスナーのクラス。 センサーに対するリスナーのクラス。 これは、加速度センサーと磁気センサーの 2 つのセンサーに登録される。 方位は、この 2 つのセンサーの値を基に取得する。 □ AcceleraterSensorListener.java --- package jp.marunomaruno.android.orientationsensor; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.widget.TextView; import jp.marunomaruno.android.orientationsensor.R; /** * 方位センサーのリスナー * @author maruno * @version 1.0, 2011-12-01 * @since 1.0 */ public class OrientationSensorListener implements SensorEventListener { private TextView textView; // 表示域 private float[] accelerometerValues; // 加速度センサーの値 // (1) private float[] magneticFieldValues; // 磁気センサーの値 // (2) public OrientationSensorListener(TextView textView) { this.textView = textView; } @Override public void onSensorChanged(SensorEvent event) { float[] orientationValues = new float[3]; // 方位の値 // (3) switch (event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: // 加速度センサーが変化したとき accelerometerValues = clone(event.values); // (4) break; case Sensor.TYPE_MAGNETIC_FIELD: // 磁気センサーが変化したとき magneticFieldValues = clone(event.values); // (5) break; } if (accelerometerValues != null && magneticFieldValues != null) { float[] inRotationMatrix = new float[16]; // 入力用の回転行列 // (6) float[] outRotationMatrix = new float[16]; // 出力用の回転行列 // (7) SensorManager.getRotationMatrix(inRotationMatrix, null, accelerometerValues, magneticFieldValues); // (8) SensorManager.remapCoordinateSystem(inRotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, outRotationMatrix); // (9) SensorManager.getOrientation(outRotationMatrix, orientationValues); // (10) // TextViewに表示する textView.setText(""); // ラジアンを表示 textView.append(String .format(textView.getResources().getString( R.string.sensor_value_format), textView .getResources().getString(R.string.orientation), orientationValues[0], orientationValues[1], orientationValues[2], textView.getResources() .getString(R.string.OrientationUnit))); textView.append("¥n"); // 度を表示 textView.append(String.format(textView.getResources().getString( R.string.sensor_value_format), textView.getResources() .getString(R.string.orientation), toOrientationDegrees(orientationValues[0]), toOrientationDegrees(orientationValues[1]), toOrientationDegrees(orientationValues[2]), textView .getResources() .getString(R.string.OrientationUnit2))); textView.append("¥n"); // 方位を表示 textView.append(String.format(textView.getResources().getString( R.string.orientation_value_format), toOrientationString(orientationValues[0]), toOrientationDegrees(orientationValues[0]), textView .getResources() .getString(R.string.OrientationUnit2))); textView.append("¥n"); } } /** * 配列の深いコピーを行う。 * ※Android2.2 の Arrays クラスには copyOf() メソッドがない。 * @param source ソースの配列 * @return 深いコピーを行った結果 */ private float[] clone(float[] source) { // (11) float[] target = new float[source.length]; for (int i = 0; i < target.length; i++) { target[i] = source[i]; } return target; // return Arrays.copyOf(sourse, sourse.length); // 本来はこれでOK } /** * 方位の角度にする。方位の角度は、0以上360未満。 * @param rad ラジアン。ただし、-π以上π未満。 * @return 方位の角度 */ private float toOrientationDegrees(float rad) { // (12) return (float) (rad >= 0 ? Math.toDegrees(rad) : 360 + Math .toDegrees(rad)); // (13) } /** * 方位をあらわす文字列を取得する。 * 北 0 rad * 東 π/2 rad * 南 π rad * 西 -π/2 rad * @param azimuth 方位 * @return 方位をあらわす文字列 */ private String toOrientationString(float azimuth) { // (14) double[] ORIENTATION_RANGE = { -(Math.PI * 3 / 4), // 南 -(Math.PI * 1 / 4), // 西 +(Math.PI * 1 / 4), // 北 +(Math.PI * 3 / 4), // 東 // Math.PI, // 南 }; // (15) for (int i = 0; i < ORIENTATION_RANGE.length; i++) { if (azimuth < ORIENTATION_RANGE[i]) { return textView.getResources().getStringArray( R.array.orientationNames)[i]; // (16) } } return textView.getResources().getStringArray(R.array.orientationNames)[0]; } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { assert true; // 何もしない } } --- (1)(2) 加速度センサー・磁気センサーの値を保持 それぞれ、3 次元の値なので、配列で保持する。 private float[] accelerometerValues; // 加速度センサーの値 // (1) private float[] magneticFieldValues; // 磁気センサーの値 // (2) (3) 方位の値 方位も、3 次元の値になるので、これを確保する変数を用意する。 float[] orientationValues = new float[3]; // 方位の値 // (3) (4)(5)(6)(7) 加速度センサー・磁気センサーの値から方位を取得するための準備 計算に使うので、配列の深いコピーで、値をコピーする。 accelerometerValues = clone(event.values); // (4) magneticFieldValues = clone(event.values); // (5) ※Android2.2 では、深いコピーを行う Arrays.copyOf() メソッドがないので、自分で実 装する。本来は、 accelerometerValues = Arrays.copyOf(event.values, event.values.length); // (4) magneticFieldValues = Arrays.copyOf(event.values, event.values.length); // (5) でよい。 4x4 の回転行列を用意する。実際には、要素数 16 の 1 次元配列で作る。 float[] inRotationMatrix = new float[16]; // 入力用の回転行列 // (6) float[] outRotationMatrix = new float[16]; // 出力用の回転行列 // (7) (8)(9)(10) 加速度センサー・磁気センサーの値から方位を取得する この一連の操作によって、accelerometerValues、magneticFieldValues の値は、最終的 に orientationValues として方位が取得できる。 加速度センサー・磁気センサーの値から、回転行列 inRotationMatrix を取得する。 SensorManager.getRotationMatrix(inRotationMatrix, null, accelerometerValues, magneticFieldValues); // (8) 座標系を回転行列 inRotationMatrix を使って変換し、outRotationMatrix に入れる。 今回は、画面をみるときに、たて画面で、垂直に近い形(手前に傾いている)で見ること を想定。 画面の傾き具合は、Display.getRotation() を使って取得するが、この部分はかなり複雑 になるので、今回はこのまま。 ※他のサイトでは、引数(..., SensorManager.AXIS_X, SensorManager.AXIS_Y, ...) が よいともあるが、わたしの実機では下記の引数の方がよい感じ。 SensorManager.remapCoordinateSystem(inRotationMatrix, SensorManager.AXIS_X, SensorManager.AXIS_Z, outRotationMatrix); // (9) 回転行列 outRotationMatrix からデバイスの向き orientationValues を取得する。 SensorManager.getOrientation(outRotationMatrix, orientationValues); // (10) ※回転行列などの詳細は現在調査中。 ・SensorManager クラスの static な取得系のメソッド --- static float getAltitude(float p0, float p) 大気圧と海面の気圧から高度(m)を取得 static void getAngleChange(float[] angleChange, float[] R, float[] prevR) 2 つの回転行列の間の角度を取得 static float getInclination(float[] I) getRotationMatrix() で取得した傾き行列から傾きを取得 static float[] getOrientation(float[] R, float[] values) 回転行列からデバイスの向きを取得 static void getQuaternionFromVector(float[] Q, float[] rv) 回転ベクトルから 4 元数を取得(ヘルパー) static boolean getRotationMatrix(float[] R, float[] I, float[] gravity, float[] geomagnetic) 回転行列を取得 static void getRotationMatrixFromVector(float[] R, float[] rotationVector) 回転ベクトルから回転行列を取得(ヘルパー) static boolean remapCoordinateSystem(float[] inR, int X, int Y, float[] outR) 座標系を回転行列を使って変換 --- ※ただし、使い方(訳も)についてはまだ、不明な部分が大半。 ・remapCoordinateSystem() で使う定数 --- int AXIS_MINUS_X int AXIS_MINUS_Y int AXIS_MINUS_Z int AXIS_X int AXIS_Y int AXIS_Z --- (11) 配列の深いコピーを行う private float[] clone(float[] source) { // (11) ※Android2.2 では、まだ、これを実現する Arrays.copyOf() メソッドがないので、自分 で実装する。 (12)(13) 方位の角度にする。方位の角度は、0以上360未満。 private float toOrientationDegrees(float rad) { // (12) rad は、-π ~ +πの範囲なので、これを度に変換し、値の範囲が 0 ~ 360 になるよう に調整する。 return (float) (rad >= 0 ? Math.toDegrees(rad) : 360 + Math .toDegrees(rad)); // (13) (14)(15)(16) 方位をあらわす文字列を取得する。 private String toOrientationString(float azimuth) { // (14) 真北、真東、真南、真西がそれぞれ 0, π/2, -π, -π/2 なので、北、東、南、西のそ れぞれの範囲は、以下のようになる。 北 -π/4 ~ π/4 東 π/4 ~ 3π/4 南 -π ~ -3π/4, 3π/4 ~ π 西 -π/4 ~ π/4 double[] ORIENTATION_RANGE = { -(Math.PI * 3 / 4), // 南 -(Math.PI * 1 / 4), // 西 +(Math.PI * 1 / 4), // 北 +(Math.PI * 3 / 4), // 東 // Math.PI, // 南 }; // (15) この範囲で、配列を作って、文字を割り当てる。 return textView.getResources().getStringArray( R.array.orientationNames)[i]; // (16) リソースでは、string-array 要素を使って、配列として、東西南北の文字列を持つよう にしている。 <string-array name="orientationNames"> <item>@string/south</item> <item>@string/west</item> <item>@string/north</item> <item>@string/east</item> </string-array> これは、getStringArray() メソッドで、ここで記述した順番の String[] 型の配列で値 が返ってくる。 ■ レイアウト センサー(1) のサンプルと同じ。 □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <!-- センサー値 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/orientation" android:id="@+id/sensor_value" /> </LinearLayout> --- ■ 文字列の定数 プログラム中で配列を使っているので、それに対する string-array 要素を設定している ファイル arrays.xml も準備する。string.xml の中に入れてもよいが、配列として文字 列を 2 次的に利用しているので、別ファイルにした。 □ res/values/string.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">OrientationSensor01</string> <string name="orientation">方位: (方位角(azimuth), ピッチ(Pitch), 回転(Roll))</string> <string name="OrientationUnit">ラジアン</string> <string name="OrientationUnit2">度</string> <string name="sensor_value_format">%1$s = (%2$.1f, %3$.1f, %4$.1f) (%5$s)</string> <string name="orientation_value_format">方位: %1$s (%2$.1f %3$s)</string> <string name="east">東(45 ~ 135)</string> <string name="west">西(225 ~ 315)</string> <string name="south">南(135 ~ 225)</string> <string name="north">北(0 ~ 45, 315 ~ 360)</string> </resources> --- □ res/values/arrays.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="orientationNames"> <item>@string/south</item> <item>@string/west</item> <item>@string/north</item> <item>@string/east</item> </string-array> </resources> --- 東西南北の文字列を配列で持っておく。 プログラム中でも配列を使うので、リソースも、string-array 要素を使って、配列とし て管理する。
位置情報 (4) / ファイル・システム (2) 外部メディアに位置情報を書き込む ================================================================================ このアプリケーションは、つぎつぎと変化する位置情報を、SD カードのファイルに書き 込む。GpsLogger01 は、メモリー内のファイルであったが、これは SD カードを使うのが 違う点である。 また、利用者の指定によって、ファイルの内容を表示したり、削除した りするのは、GpsLogger01 と同じ。 コード --- GpsLogger01Activity.java アクティビティ GpsLocationListener.java ロケーションのリスナー TextViewPrintStream.java System.out オブジェクトの置き換え(SyystemOut01 と同じ) LogDao.java ログの入出力を行うユーティリティのクラス --- ◆ アクティビティのクラス 採ったログを表示するための「ログ表示」ボタン、ログファイルをクリアする「ログクリ ア」ボタンが追加になっているので、これに対するハンドラー・メソッド onClickLogPrintButton() onClickLogClearButton() を追加している。メソッドは、単に LogDao のメソッドを呼び出しているだけである。 ファイル名は、先のサンプル GpsLogger01 は、固定だったが、これは、ログを採る開始 日時がファイル名の一部に入る。 ログファイルの扱いは、LogDao クラスなので、このアクティビティ・クラスはほとんど 変更がない。ログファイルにタイムスタンプが入っているので、それを取得するコードが 入った程度である。タイムスタンプに対するゲッター・メソッドが追加になっている。 □ Gps02Activity.java --- package jp.marunomaruno.android.gpslogger; import java.io.FileNotFoundException; import java.io.IOException; import android.app.Activity; import android.location.Criteria; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; /** * @author marunomaruno * @version 1.0, 2011-12-14 * @since 1.0 */ public class GpsLogger02Activity extends Activity { private boolean isRunning = false; // 現在起動中かどうか private static final String IS_RUNNING = "isRunning"; private long timestamp; // 開始したときのタイムスタンプ // (1) private static final String TIMESTAMP = "timestamp"; private LocationManager locationManager; private LocationListener sensorEventListener; // onPause()が呼ばれたときに、すべてのリスナーを解除するため @Override public void onCreate(Bundle savedInstanceState) { Log.d(getString(R.string.logTag), String.format("onCreate() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onCreate(savedInstanceState); setContentView(R.layout.main); // 標準出力を置き換え(標準出力(LogCat)+テキストビュー) TextView sysout = (TextView) findViewById(R.id.sysout); System.setOut(new TextViewPrintStream(System.out, sysout)); System.setErr(new TextViewPrintStream(System.err, sysout)); locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); } @Override protected void onResume() { Log.d(getString(R.string.logTag), String.format("onResume() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onResume(); Button startStopButton = (Button) findViewById(R.id.startStopButton); Log.d(getString(R.string.logTag), "起動中 = " + isRunning); if (isRunning) { // 起動中のとき、終了ボタンを表示する startStopButton.setText(R.string.stopButton); } else { startStopButton.setText(R.string.startButton); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { Log.d(getString(R.string.logTag), String.format("onRestoreInstanceState() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onRestoreInstanceState(savedInstanceState); isRunning = savedInstanceState.getBoolean(IS_RUNNING); timestamp = savedInstanceState.getLong(TIMESTAMP); } @Override protected void onSaveInstanceState(Bundle outState) { Log.d(getString(R.string.logTag), String.format("onSaveInstanceState() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onSaveInstanceState(outState); outState.putBoolean(IS_RUNNING, isRunning); outState.putLong(TIMESTAMP, timestamp); } /** * 開始/終了ボタン押下時 * @param view */ public void onClickStartStopButton(View view) { Log.d(getString(R.string.logTag), String.format("onClickStartButton() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); Button startStopButton = (Button) view; if (isRunning) { // 起動中のとき、とめる // すべてのリスナーを解除する removeListeners(); // 開始ボタンにする startStopButton.setText(R.string.startButton); System.out.println(getString(R.string.stopButton)); System.out.println(String.format( getString(R.string.sensor_value_format_for_timestamp), getString(R.string.startButton), timestamp)); System.out.println(String.format( getString(R.string.sensor_value_format_for_timestamp), getString(R.string.stopButton), System .currentTimeMillis())); } else { // 起動していないとき、 // GPSロケーションを取得して登録する setProviderAndListener(); // 停止ボタンにする startStopButton.setText(R.string.stopButton); timestamp = System.currentTimeMillis(); // タイムスタンプを取得する // (2) try { System.out.printf(getString(R.string.startMessageFormat), LogDao.getFile(this)); } catch (IOException e) { e.printStackTrace(); } System.out.println(); } isRunning = !isRunning; // 起動中状態を逆にする } private void setProviderAndListener() { // プロバイダーを取得する条件を作成する Criteria fineAndLowPower = new Criteria(); fineAndLowPower.setAccuracy(Criteria.ACCURACY_FINE); fineAndLowPower.setPowerRequirement(Criteria.POWER_LOW); // プロバイダーを取得する String provider = locationManager.getBestProvider(fineAndLowPower, true); Log.d(getString(R.string.logTag), "provider = " + provider); TextView providerNameTextView = (TextView) findViewById(R.id.provider_name); providerNameTextView.setText(provider); // プロバイダーがみつからなければ、NETWORK_PROVIDER を設定しておく if (provider == null) { provider = LocationManager.NETWORK_PROVIDER; providerNameTextView.setText(R.string.no_provider); } // 位置情報のリスナーを取得して登録する sensorEventListener = GpsLocationListener.getInstance(this); locationManager.requestLocationUpdates( provider, 0, 0, sensorEventListener); } /** * ログをクリアする。 * @param view */ public void onClickLogClearButton(View view) { try { LogDao.delete(this); } catch (IOException e) { e.printStackTrace(); } } /** * ログを表示する。 * @param view */ public void onClickLogPrintButton(View view) { try { LogDao.print(this); } catch (FileNotFoundException e) { System.out.println(getString(R.string.logNotFound)); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } private void removeListeners() { if (sensorEventListener != null) { locationManager.removeUpdates(sensorEventListener); } } public long getTimestamp() { // (3) return timestamp; } } --- (1)(3) 開始したときのタイムスタンプとそのゲッター private long timestamp; // 開始したときのタイムスタンプ // (1) public long getTimestamp() { // (3) (2) タイムスタンプの取得 timestamp = System.currentTimeMillis(); // タイムスタンプを取得する // (2) ◆ センサーのリスナーのクラス。 以下の GpsLogger01 プロジェクトと同じ。 位置情報 (3) / ファイル・システム (1) メモリー内のファイルに位置情報を書き込む http://blog.goo.ne.jp/marunomarunogoo/d/20120110 ◆ ログを管理するクラス 外部メディアを使うので、ローカルのときとは少し違う。 ただし、基本は一緒で、ログへの出力は PrintWriter オブジェクト、ログの取得は、 BufferedReader オブジェクトを作るのは変わらない。 外部メディア関係を扱うクラスは Environement クラス。 ここが、メモリー内のファイルとは違う部分である。 □ LogDao.java --- package jp.marunomaruno.android.gpslogger; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import android.content.Context; import android.location.Location; import android.os.Environment; /** * 位置情報ログにアクセスするクラス。 * すべてのメソッドはstatic。 * @author marunomaruno */ public class LogDao { public static final String SEPARATER_CHARACTER = ","; private LogDao() { } /** * ログファイルを取得する。 * このファイルがない場合は、必要なディレクトリーも含めてファイルを生成する。 * @param context コンテキスト * @return ログファイル * @throws IOException */ public static File getFile(Context context) throws IOException { String status = Environment.getExternalStorageState(); // (1) if (!status.equals(Environment.MEDIA_MOUNTED)) { // (2) throw new IOException(context.getString(R.string.mediaDisable)); } File file = new File(String.format(context .getString(R.string.logFilePathFormat), Environment.getExternalStorageDirectory(), // (3) context.getPackageName(), context.getString(R.string.logFilePrefix), ((GpsLogger02Activity) context).getTimestamp(), context.getString(R.string.logFileExtension))); // (4) File directory = file.getParentFile(); if (!directory.exists()) { // (5) directory.mkdirs(); } return file; } /** * locationのデータをログファイルにCSV形式で書き出す。 * @param context * @param location * @throws UnsupportedEncodingException * @throws FileNotFoundException * @throws IOException */ public static void write(Context context, Location location) throws UnsupportedEncodingException, FileNotFoundException, IOException { PrintWriter out = new PrintWriter(new OutputStreamWriter( new FileOutputStream(getFile(context), true), context .getString(R.string.logFileEncoding))); // (6) out.printf("¥"%1$tF %1$tT¥"", location.getTime()); // ファイルをわかりやすくするため out.print(SEPARATER_CHARACTER); out.print(location.getTime()); out.print(SEPARATER_CHARACTER); out.print(location.getLatitude()); out.print(SEPARATER_CHARACTER); out.print(location.getLongitude()); out.print(SEPARATER_CHARACTER); out.print(location.getAccuracy()); out.print(SEPARATER_CHARACTER); out.print(location.getAltitude()); out.print(SEPARATER_CHARACTER); out.print(location.getSpeed()); out.print(SEPARATER_CHARACTER); out.print(location.getBearing()); out.println(); out.close(); } /** * ログファイルの内容を標準出力にプリントする。 * @param context * @throws UnsupportedEncodingException * @throws FileNotFoundException * @throws IOException */ public static void print(Context context) throws UnsupportedEncodingException, FileNotFoundException, IOException { BufferedReader in = new BufferedReader(new InputStreamReader( new FileInputStream(getFile(context)), context .getString(R.string.logFileEncoding))); // (7) String line; while ((line = in.readLine()) != null) { System.out.println(line); } in.close(); } /** * ログファイルを削除する。 * @param context * @throws IOException */ public static void delete(Context context) throws IOException { getFile(context).delete(); // (8) } } --- (1)(2) 外部メディアが使用可能かどうかを判断する 外部メディア(SD カード)の状態を取得する。 String status = Environment.getExternalStorageState(); // (1) 取得した状態が、実機にマウントされているかどうかを判断する。されていなければ、 IOException をスローする。 if (!status.equals(Environment.MEDIA_MOUNTED)) { // (2) throw new IOException(context.getString(R.string.mediaDisable)); } ・Environment クラス SD カードのような、外部メディア関係を扱うユーティリティのクラスとして、 Environment クラスがある。 java.lang.Object + android.os.Environment ・メソッド --- static File getDataDirectory() static File getDownloadCacheDirectory() static File getExternalStorageDirectory() static File getExternalStoragePublicDirectory(String type) static String getExternalStorageState() static File getRootDirectory() static boolean isExternalStorageEmulated() static boolean isExternalStorageRemovable() --- ・getExternalStorageState() メソッドの返り値になる定数(型はすべて String) --- MEDIA_BAD_REMOVAL 外部メディアをアンマウントする前に取り外した MEDIA_CHECKING 外部メディアのチェックを始めた MEDIA_MOUNTED 外部メディアが装着されている(読み取り・書き込み可) MEDIA_MOUNTED_READ_ONLY 外部メディアが装着されている(読み取り専用) MEDIA_NOFS 外部メディアは装着されているが、ブランクか サポートしていないファイルシステムを使っている MEDIA_REMOVED 外部メディアが装着されていない MEDIA_SHARED 外部メディアは USB メモリーとして共有されている(使えない状態) MEDIA_UNMOUNTABLE 外部メディアは装着されているが、マウントできない MEDIA_UNMOUNTED 外部メディアは装着されているが、マウントされていない --- (3)(4) ファイル・オブジェクトの取得 File file = new File(String.format(context .getString(R.string.logFilePathFormat), Environment.getExternalStorageDirectory(), // (3) context.getPackageName(), context.getString(R.string.logFilePrefix), ((GpsLogger02Activity) context).getTimestamp(), context.getString(R.string.logFileExtension))); // (4) ルートからのパスを作って、ファイル・オブジェクトを取得する。 ファイル名は、リソースで定義しているフォーマットにしたがって、文字列化する。 実際の値は、次のメソッドで、 context.getString(R.string.logFilePathFormat) 取得でき、つぎの文字列を持っている。 %1$s/data/%2$s/%3$s%4$tY%4$tm%4$td_%4$tH%4$tM%4$tS.%5$s それぞれの値はつぎのメソッドで取得する。(右は、このサンプルの例)) --- %1$s Environment.getExternalStorageDirectory() /mnt/sdcard/ %2$s context.getPackageName() jp.marunomaruno.android.gpslogger %3$s context.getString(R.string.logFilePrefix) location %4$tX ((GpsLogger02Activity) context).getTimestamp() タイムスタンプ %5$s context.getString(R.string.logFileExtension) txt --- (5) ディレクトリーがないときは作成する if (!directory.exists()) { // (5) directory.mkdirs(); } (6) locationのデータをログファイルにCSV形式で書き出す。 File オブジェクトが (3)(4) によって作れれば、これをもとにして、JavaSE と同じよう に、PrintWriter オブジェクトを作る。 PrintWriter out = new PrintWriter(new OutputStreamWriter( new FileOutputStream(getFile(context), true), context .getString(R.string.logFileEncoding))); // (6) (7) ログファイルの内容を標準出力にプリントする。 ログファイルの読み込みも、File オブジェクトがあれば、(6) と同様に、 BufferedReader オブジェクトを作る。 BufferedReader in = new BufferedReader(new InputStreamReader( new FileInputStream(getFile(context)), context .getString(R.string.logFileEncoding))); // (7) (8) ログファイルを削除する。 File オブジェクトなので、その delete() メソッドを使えばよい。 getFile(context).delete(); // (8) ◆ レイアウト 以下の GpsLogger01 プロジェクトと同じ。 位置情報 (3) / ファイル・システム (1) メモリー内のファイルに位置情報を書き込む http://blog.goo.ne.jp/marunomarunogoo/d/20120110 ◆ 文字列の定数 □ res/values/string.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">GpsLogger02</string> <string name="logTag">TEST</string> <string name="no_provider">プロバイダーは見つかりませんでした。</string> <string name="sensor_value_format_for_real">%1$s = %2.2f</string> <string name="sensor_value_format_for_timestamp">%1$s = %2$tF %2$tT</string> <string name="latitude">緯度</string> <string name="longitude">経度</string> <string name="accuracy">精度</string> <string name="altitude">標高</string> <string name="time">時間</string> <string name="speed">速度</string> <string name="bearing">ベアリング</string> <string name="logFilePrefix">location</string> <string name="logFileExtension">txt</string> <string name="logFilePathFormat"> %1$s/data/%2$s/%3$s%4$tY%4$tm%4$td_%4$tH%4$tM%4$tS.%5$s</string> <!-- /mnt/sdcard / data / app_name / location yyyymmdd_hhmmss.txt --> <!-- (1) --> <string name="logFileEncoding">UTF-8</string> <string name="startButton">開始</string> <string name="stopButton">終了</string> <string name="logPrintButton">ログ表示</string> <string name="logClearButton">ログクリア</string> <string name="logNotFound">ログはありません。</string> <string name="startMessageFormat">開始します。ファイル名 = %s</string> <string name="providerDisable">無線ネットワークを使用できるようにしてください。</string> <string name="providerEnabled">無線ネットワークを使用します。</string> <string name="mediaDisable">SDカードが使用できません。</string> </resources> --- (1) ログファイルのパスも含めたファイル名 <string name="logFilePathFormat"> %1$s/data/%2$s/%3$s%4$tY%4$tm%4$td_%4$tH%4$tM%4$tS.%5$s</string> <!-- /mnt/sdcard / data / app_name / location yyyymmdd_hhmmss.txt --> <!-- (1) --> たとえば、2012年1月2日6時57分14秒に開始すれば、 /mnt/sdcard/data/jp.marunomaruno.android.gpslogger/location20120102_065714.txt になる。 ◆マニフェスト □AndroidManifest.xml --- <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.marunomaruno.android.gpslogger" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- (1) --> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".GpsLogger02Activity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> --- (1) SD カードへの書き込み許可 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- (1) -->
[Android] 位置情報 (3) / ファイル・システム (1) メモリー内のファイルに位置情報を書き込む ================================================================================ Android で、永続化をはかる方法としては、以下の方法がある。 ・SharedPreferences ・ファイル・システム ・SQLite データベース ・ContentProvider 今回は、ファイル・システムを利用する。 ファイルシステムも、メメリー内とメモリーの外(SD カードなど)にファイルを構築す る方法で違いがある。 このサンプルは、メモリー内のファイルへの入出力を行う。 ■ メモリー内のファイルに位置情報を書き込む このアプリケーションは、つぎつぎと変化する位置情報を、メモリー内のファイル(ロー カル・ファイル)に書き込む。また、利用者の指定によって、ファイルの内容を表示したり、 削除したりする。 また、位置情報は、コンソールに表示するように、画面にスクロールして表示される。 位置情報の取得は、「開始/終了」ボタンのクリックによる。このボタンはトグルボタン になっていて、そのときの状態を保存する必要がある。 単なるファイルの扱いだけになっていないので、コードはある程度複雑になっているが、 ファイルの扱いの部分は、LogDao クラスのコードを参照すればよい。 トグルボタンを扱う部分と、コンソール表示コは、つぎの既出のドキュメントを参照され たい。 位置情報 (2) トグルボタンによる位置情報の取得の開始・終了 http://blog.goo.ne.jp/marunomarunogoo/d/20120109 標準出力 - あて先に画面を追加する http://blog.goo.ne.jp/marunomarunogoo/d/20111222 コード --- GpsLogger01Activity.java アクティビティ GpsLocationListener.java ロケーションのリスナー TextViewPrintStream.java System.out オブジェクトの置き換え(SyystemOut01 と同じ) LogDao.java ログの入出力を行うユーティリティのクラス --- ◆ アクティビティのクラス 採ったログを表示するための「ログ表示」ボタン、ログファイルをクリアする「ログクリ ア」ボタンが追加になっているので、これに対するハンドラー・メソッド onClickLogPrintButton() onClickLogClearButton() を追加している。メソッドは、単に LogDao のメソッドを呼び出しているだけである。 □ GpsLogger01Activity.java --- package jp.marunomaruno.android.sample; import java.io.FileNotFoundException; import java.io.IOException; import android.app.Activity; import android.location.Criteria; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; /** * @author marunomaruno * @version 1.0, 2011-12-14 * @since 1.0 */ public class GpsLogger01Activity extends Activity { private boolean isRunning = false; // 現在起動中かどうか private static final String IS_RUNNING = "isRunning"; private LocationManager locationManager; private LocationListener sensorEventListener; // onPause()が呼ばれたときに、すべてのリスナーを解除するため @Override public void onCreate(Bundle savedInstanceState) { Log.d(getString(R.string.logTag), String.format("onCreate() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onCreate(savedInstanceState); setContentView(R.layout.main); // 標準出力を置き換え(標準出力(LogCat)+テキストビュー) TextView sysout = (TextView) findViewById(R.id.sysout); System.setOut(new TextViewPrintStream(System.out, sysout)); System.setErr(new TextViewPrintStream(System.err, sysout)); locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); } @Override protected void onResume() { Log.d(getString(R.string.logTag), String.format("onResume() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onResume(); Button startStopButton = (Button) findViewById(R.id.startStopButton); Log.d(getString(R.string.logTag), "起動中 = " + isRunning); if (isRunning) { // 起動中のとき、終了ボタンを表示する startStopButton.setText(R.string.stopButton); } else { startStopButton.setText(R.string.startButton); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { Log.d(getString(R.string.logTag), String.format("onRestoreInstanceState() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onRestoreInstanceState(savedInstanceState); isRunning = savedInstanceState.getBoolean(IS_RUNNING); } @Override protected void onSaveInstanceState(Bundle outState) { Log.d(getString(R.string.logTag), String.format("onSaveInstanceState() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onSaveInstanceState(outState); outState.putBoolean(IS_RUNNING, isRunning); } /** * 開始/終了ボタン押下時 * @param view */ public void onClickStartStopButton(View view) { Log.d(getString(R.string.logTag), String.format("onClickStartButton() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); Button startStopButton = (Button) view; if (isRunning) { // 起動中のとき、とめる // すべてのリスナーを解除する removeListeners(); // 開始ボタンにする startStopButton.setText(R.string.startButton); System.out.println(getString(R.string.stopButton)); System.out.println(String.format( getString(R.string.sensor_value_format_for_timestamp), getString(R.string.stopButton), System .currentTimeMillis())); } else { // 起動していないとき、 // GPSロケーションを取得して登録する setProviderAndListener(); // 停止ボタンにする startStopButton.setText(R.string.stopButton); System.out.printf(getString(R.string.startMessageFormat), LogDao .getFile(this)); // (1) System.out.println(); } isRunning = !isRunning; // 起動中状態を逆にする Log.d(getString(R.string.logTag), String.format( "起動中 = %b, 開始時刻 = %tT, ファイル名 = %s", isRunning, System .currentTimeMillis(), LogDao.getFile(this))); } private void setProviderAndListener() { // プロバイダーを取得する条件を作成する Criteria fineAndLowPower = new Criteria(); fineAndLowPower.setAccuracy(Criteria.ACCURACY_FINE); fineAndLowPower.setPowerRequirement(Criteria.POWER_LOW); // プロバイダーを取得する String provider = locationManager.getBestProvider(fineAndLowPower, true); Log.d(getString(R.string.logTag), "provider = " + provider); TextView providerNameTextView = (TextView) findViewById(R.id.provider_name); providerNameTextView.setText(provider); // プロバイダーがみつからなければ、NETWORK_PROVIDER を設定しておく if (provider == null) { provider = LocationManager.NETWORK_PROVIDER; providerNameTextView.setText(R.string.no_provider); } // 位置情報のリスナーを取得して登録する sensorEventListener = GpsLocationListener.getInstance(this); locationManager.requestLocationUpdates( provider, 0, 0, sensorEventListener); } /** * ログをクリアする。 * @param view */ public void onClickLogClearButton(View view) { LogDao.delete(this); // (2) } /** * ログを表示する * @param view */ public void onClickLogPrintButton(View view) { try { LogDao.print(this); // (3) } catch (FileNotFoundException e) { System.out.println(getString(R.string.logNotFound)); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } private void removeListeners() { if (sensorEventListener != null) { locationManager.removeUpdates(sensorEventListener); } } } --- (1) ログのファイル名を取得する System.out.printf(getString(R.string.startMessageFormat), LogDao .getFile(this)); // (1) LogDao は、ユーティリティのクラスとして、すべてのメソッドは static になっている。 基本的には、アクティビティのオブジェクトを渡して、処理する。 (2) ログをクリアする LogDao.delete(this); // (2) (3) ログを表示する LogDao.print(this); // (3) ◆ センサーのリスナーのクラス。 Gps01 プロジェクトからみると、変更している箇所は、位置情報を標準出力に表示すると ころを、ログにも書き込む 1 箇所だけ。 □ GpsLocationListener.java --- package jp.marunomaruno.android.sample; import java.io.FileNotFoundException; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationProvider; import android.os.Bundle; import android.util.Log; /** * GPSによるロケーションのリスナー * @author maruno * @version 1.0, 2011-12-14 * @since 1.0 */ public class GpsLocationListener implements LocationListener { protected Context context; private static LocationListener instance; private GpsLocationListener(Context context) { this.context = context; } /** * このリスナーの唯一のインスタンスを取得する。 * @param context * @return このインスタンス */ public static LocationListener getInstance(Context context) { if (instance == null) { instance = new GpsLocationListener(context); } return instance; } @Override public void onLocationChanged(Location location) { Log.d(context.getString(R.string.logTag), String.format("onLocationChanged() " + context.getString( R.string.sensor_value_format_for_timestamp), context.getString(R.string.time), System.currentTimeMillis())); System.out.println("---"); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_timestamp), context.getString(R.string.time), location.getTime())); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_real), context.getString(R.string.latitude), location.getLatitude())); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_real), context.getString(R.string.longitude), location.getLongitude())); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_real), context.getString(R.string.accuracy), location.getAccuracy())); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_real), context.getString(R.string.altitude), location.getAltitude())); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_real), context.getString(R.string.speed), location.getSpeed())); System.out.println(String.format(context.getString( R.string.sensor_value_format_for_real), context.getString(R.string.bearing), location.getBearing())); try { LogDao.write(context, location); // (1) } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } @Override public void onProviderDisabled(String provider) { System.out.println(context.getString(R.string.providerDisable)); } @Override public void onProviderEnabled(String provider) { System.out.println(context.getString(R.string.providerEnabled)); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { Log.d(context.getString(R.string.logTag), String.format("onStatusChanged() " + context.getString( R.string.sensor_value_format_for_timestamp), context.getString(R.string.time), System.currentTimeMillis())); switch (status) { case LocationProvider.AVAILABLE: Log.d(context.getString(R.string.logTag), "onStatusChanged() - AVAILABLE"); break; case LocationProvider.OUT_OF_SERVICE: Log.d(context.getString(R.string.logTag), "onStatusChanged() - OUT_OF_SERVICE"); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: Log.d(context.getString(R.string.logTag), "onStatusChanged() - TEMPORARILY_UNAVAILABLE"); break; default: Log.d(context.getString(R.string.logTag), "onStatusChanged() - Other"); break; } } } --- (1) 位置情報をログに書き込む LogDao.write(context, location); // (1) ◆ ログを管理するクラス 位置情報ログにアクセスするクラス。すべてのメソッドは static となっているユーティ リティのクラス。 つぎのメソッドがある。 --- static void delete(Context context) ログファイルを削除する。 static File getFile(Context context) ログファイルを取得する。 static void print(Context context) ログファイルの内容を標準出力にプリントする。 static void write(Context context, Location location) location のデータをログファイルに CSV 形式で書き出す。 --- □ LogDao.java --- package jp.marunomaruno.android.sample; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import android.content.Context; import android.location.Location; /** * 位置情報ログにアクセスするクラス。 * すべてのメソッドはstatic。 * @author marunomaruno * @version 1.0, 2011-12-14 * @since 1.0 */ public class LogDao { private LogDao() { } /** * ログファイルを取得する。 * @param context コンテキスト * @return ログファイル */ public static File getFile(Context context) { // (1) File file = new File(context.getString(R.string.logFilePrefix) + "." + context.getString(R.string.logFileExtension)); // (2) return file; } /** * locationのデータをログファイルにCSV形式で書き出す。 * @param context * @param location * @throws UnsupportedEncodingException * @throws FileNotFoundException */ public static void write(Context context, Location location) // (3) throws UnsupportedEncodingException, FileNotFoundException { PrintWriter out = new PrintWriter(new OutputStreamWriter(context .openFileOutput(getFile(context).getName(), Context.MODE_APPEND | Context.MODE_WORLD_READABLE), context.getResources() .getString(R.string.logFileEncoding))); // (4) out.printf("¥"%1$tF %1$tT¥"", location.getTime()); // ファイルをわかりやすくするため // (5) out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getTime()); out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getLatitude()); out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getLongitude()); out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getAccuracy()); out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getAltitude()); out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getSpeed()); out.print(context.getString(R.string.csvSeparateCharacter)); out.print(location.getBearing()); out.println(); out.close(); } /** * ログファイルの内容を標準出力にプリントする。 * @param context * @throws UnsupportedEncodingException * @throws FileNotFoundException * @throws IOException */ public static void print(Context context) // (6) throws UnsupportedEncodingException, FileNotFoundException, IOException { BufferedReader in = new BufferedReader(new InputStreamReader(context .openFileInput(getFile(context).getName()), context .getString(R.string.logFileEncoding))); // (7) String line; while ((line = in.readLine()) != null) { System.out.println(line); } in.close(); } /** * ログファイルを削除する。 * @param context */ public static void delete(Context context) { // (8) context.deleteFile(getFile(context).getName()); // (9) } } --- (1)(2) ファイルを取得する public static File getFile(Context context) { // (1) 引数のコンテキストから、リソースで指定してある名前を基に、ファイル・オブジェクト を取得する。JavaSE と同じ方法で取得できる。 File file = new File(context.getString(R.string.logFilePrefix) + "." + context.getString(R.string.logFileExtension)); // (2) (3)(4)(5) location のデータをログファイルに CSV 形式で書き出す public static void write(Context context, Location location) // (3) throws UnsupportedEncodingException, FileNotFoundException { CSV ファイル(テキスト・ファイル)なので、扱いやすい PrintWriter オブジェクトとす る。 PrintWriter out = new PrintWriter(new OutputStreamWriter(context .openFileOutput(getFile(context).getName(), Context.MODE_APPEND | Context.MODE_WORLD_READABLE), context.getResources() .getString(R.string.logFileEncoding))); // (4) JavaSE と同じ部分として、つぎのように、指定された文字セットを使う OutputStreamWriter を作成する部分がある。 PrintWriter out = new PrintWriter(new OutputStreamWriter(OutputStream out, String charsetName)); 上記の out オブジェクトは、Context クラスの openFileOutput() メソッドによる。 context.openFileOutput(getFile(context).getName(), Context.MODE_APPEND | Context.MODE_WORLD_READABLE), Context クラスの FileOutputStream オブジェクトを開くメソッド --- abstract FileOutputStream openFileOutput(String name, int mode) --- ファイル名 name は、この LogDao クラスの getFile() メソッドを使えばよい。 モード mode は、ファイル操作のモードで、次のものがある。 --- MODE_PRIVATE 他のアプリからアクセス不可(デフォルト) 0x00000000 MODE_WORLD_READABLE 他のアプリから読み込み可 0x00000001 MODE_WORLD_WRITEABLE 他のアプリから書き込み可 0x00000002 MODE_APPEND 既存ファイルに追加可 0x00008000 --- これらのモードは、ビットに対応しているので、論理和を使って組み合わせが可能。 文字セット charsetName は、リソースファイルに指定してあるものを使う。 context.getResources().getString(R.string.logFileEncoding) PrintWriter オブジェクトができれば、あとは、通常の out オブジェクトを書くのと一 緒。 out.printf("¥"%1$tF %1$tT¥"", location.getTime()); // ファイルをわかりやすく するため // (5) (6)(7) ログファイルの内容を標準出力にプリントする。 public static void print(Context context) // (6) BufferedReader オブジェクトを作って扱う。作り方は、PrintWriter オブジェクトを作 るのと同様。ローカルのファイルを入力モードで開くのは、openFileInput() メソッドを 使う。 BufferedReader in = new BufferedReader(new InputStreamReader(context .openFileInput(getFile(context).getName()), context .getString(R.string.logFileEncoding))); // (7) Context クラスの FileInputStream オブジェクトを開くメソッド --- abstract FileInputStream openFileInput(String name) --- (8)(9) ログファイルを削除する public static void delete(Context context) { // (8) ローカルのファイルを削除するのは、deleteFile() メソッドを使う。 context.deleteFile(getFile(context).getName()); // (9) --- abstract boolean deleteFile(String name) --- ・Context クラスのファイル関係と思われるメソッド --- abstract boolean deleteFile(String name) abstract String[] fileList() abstract File getCacheDir() abstract File getDir(String name, int mode) abstract File getExternalCacheDir() abstract File getExternalFilesDir(String type) abstract File getFileStreamPath(String name) abstract File getFilesDir() abstract String getPackageCodePath() abstract String getPackageResourcePath() abstract FileInputStream openFileInput(String name) abstract FileOutputStream openFileOutput(String name, int mode) --- 詳細は API 参照。 ◆ レイアウト 以下の Gps02 プロジェクトと同じ。 位置情報 (2) トグルボタンによる位置情報の取得の開始・終了 http://blog.goo.ne.jp/marunomarunogoo/d/20120109 ◆ 文字列の定数 □ res/values/string.xml センサーの名前と合わせて、そのセンサーで取得する値の単位も定義した。 □ res/values/string.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">GpsLogger01</string> <string name="logTag">TEST</string> <string name="no_provider">プロバイダーは見つかりませんでした。</string> <string name="sensor_value_format_for_real">%1$s = %2.2f</string> <string name="sensor_value_format_for_timestamp">%1$s = %2$tF %2$tT</string> <string name="latitude">緯度</string> <string name="longitude">経度</string> <string name="accuracy">精度</string> <string name="altitude">標高</string> <string name="time">時間</string> <string name="speed">速度</string> <string name="bearing">ベアリング</string> <string name="logFilePrefix">location</string> <!-- (1) ファイル名 --> <string name="logFileExtension">txt</string> <!-- (2) 拡張子 --> <string name="logFileEncoding">UTF-8</string> <!-- (3) 文字コード --> <string name="csvSeparateCharacter">,</string> <!-- (4) CSVファイルの分離符 --> <string name="startButton">開始</string> <string name="stopButton">終了</string> <string name="logPrintButton">ログ表示</string> <string name="sendMailButton">メール送信</string> <string name="logClearButton">ログクリア</string> <string name="logNotFound">ログはありません。</string> <string name="startMessageFormat">開始します。ファイル名 = %s</string> <string name="providerDisable">無線ネットワークを使用できるようにしてください。 </string> <string name="providerEnabled">無線ネットワークを使用します。</string> </resources> --- ◆マニフェスト 以下の Gps01 プロジェクトと同じ。 位置情報 (1) 無線ネットワークを使った位置情報 http://blog.goo.ne.jp/marunomarunogoo/d/20111218
位置情報(2) トグルボタンによる位置情報の取得の開始・終了 ================================================================================ ■ 位置情報の取得の開始・終了をトグルボタンで制御する 位置情報の取得は、「開始/終了」ボタンのクリックによる。このボタンはトグルボタン になっていて、そのときの状態を保存する必要がある。 また、位置情報は、コンソールに表示するように、画面にスクロールして表示される。 トグルボタンは、実行状態をインスタンス変数で保持する。ただし、画面が切り替わった ときなどは、Activity のインスタンスは新しく生成されるため、Activity クラスの onSaveInstanceState() onRestoreInstanceState() メソッドをオーバーライドして、保存して、復帰する必要がある。 コンソール表示のイメージは、つぎの既出のドキュメントを参照されたい。 標準出力 - あて先に画面を追加する http://blog.goo.ne.jp/marunomarunogoo/d/20111222 コード --- Gps02Activity.java アクティビティ GpsLocationListener.java ロケーションのリスナー(Gps01 と同じ) TextViewPrintStream.java System.out オブジェクトの置き換え(ystemOut01 と同じ) --- Gps01 と同じ、というのは、以下のドキュメントの Gps01 プロジェクトのソースと同じ ソースである。 位置情報 (1) 無線ネットワークを使った位置情報 http://blog.goo.ne.jp/marunomarunogoo/d/20111218 ◆ アクティビティのクラス □ Gps02Activity.java --- package jp.marunomaruno.android.sample; import android.app.Activity; import android.location.Criteria; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; /** * 位置情報を表示するアクティビティ。 * トグルボタンで、開始・終了する。 * @author marunomaruno * @version 1.0, 2011-12-14 * @since 1.0 */ public class Gps02Activity extends Activity { private boolean isRunning = false; // 現在起動中かどうか // (1) private static final String IS_RUNNING = "isRunning"; // (2) private LocationManager locationManager; private LocationListener sensorEventListener; // onPause()が呼ばれたときに、すべてのリスナーを解除するため @Override public void onCreate(Bundle savedInstanceState) { Log.d(getString(R.string.logTag), String.format("onCreate() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onCreate(savedInstanceState); setContentView(R.layout.main); // 標準出力を置き換え(標準出力(LogCat)+テキストビュー) TextView sysout = (TextView) findViewById(R.id.sysout); // (3) System.setOut(new TextViewPrintStream(System.out, sysout)); // (4) System.setErr(new TextViewPrintStream(System.err, sysout)); // (5) locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); } @Override protected void onResume() { Log.d(getString(R.string.logTag), String.format("onResume() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onResume(); Button startStopButton = (Button) findViewById(R.id.startStopButton); Log.d(getString(R.string.logTag), "起動中 = " + isRunning); if (isRunning) { // 起動中のとき、終了ボタンを表示する // (6) startStopButton.setText(R.string.stopButton); // (7) } else { startStopButton.setText(R.string.startButton); // (8) } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // (9) Log.d(getString(R.string.logTag), String.format("onRestoreInstanceState() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onRestoreInstanceState(savedInstanceState); isRunning = savedInstanceState.getBoolean(IS_RUNNING); // (10) } @Override protected void onSaveInstanceState(Bundle outState) { // (11) Log.d(getString(R.string.logTag), String.format("onSaveInstanceState() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); super.onSaveInstanceState(outState); outState.putBoolean(IS_RUNNING, isRunning); // (12) } /** * 開始/終了ボタン押下時 * @param view */ public void onClickStartStopButton(View view) { // (13) Log.d(getString(R.string.logTag), String.format("onClickStartButton() " + getString(R.string.sensor_value_format_for_timestamp), getString(R.string.time), System.currentTimeMillis())); Button startStopButton = (Button) view; if (isRunning) { // 起動中のとき、とめる // (14) // すべてのリスナーを解除する removeListeners(); // 開始ボタンにする startStopButton.setText(R.string.startButton); System.out.printf(getString(R.string.stopMessageFormat), System.currentTimeMillis()); System.out.println(); } else { // 起動していないとき、 // GPSロケーションを取得して登録する setProviderAndListener(); // 停止ボタンにする startStopButton.setText(R.string.stopButton); System.out.printf(getString(R.string.startMessageFormat), System.currentTimeMillis()); System.out.println(); } isRunning = !isRunning; // 起動中状態を逆にする // (15) Log.d(getString(R.string.logTag), String.format( "起動中 = %b, 開始時刻 = %tT", isRunning, System .currentTimeMillis())); } private void setProviderAndListener() { // プロバイダーを取得する条件を作成する Criteria fineAndLowPower = new Criteria(); fineAndLowPower.setAccuracy(Criteria.ACCURACY_FINE); fineAndLowPower.setPowerRequirement(Criteria.POWER_LOW); // プロバイダーを取得する String provider = locationManager.getBestProvider(fineAndLowPower, true); Log.d(getString(R.string.logTag), "provider = " + provider); TextView providerNameTextView = (TextView) findViewById(R.id.provider_name); providerNameTextView.setText(provider); // プロバイダーがみつからなければ、NETWORK_PROVIDER を設定しておく if (provider == null) { provider = LocationManager.NETWORK_PROVIDER; providerNameTextView.setText(R.string.no_provider); } // 位置情報のリスナーを取得して登録する sensorEventListener = GpsLocationListener.getInstance(this); locationManager.requestLocationUpdates( provider, 0, 0, sensorEventListener); } private void removeListeners() { if (sensorEventListener != null) { locationManager.removeUpdates(sensorEventListener); } } } --- (1)(2) 現在起動中かどうかをあらわすインスタンス変数 private boolean isRunning = false; // 現在起動中かどうか // (1) トグルボタンを設定するために、現在の状態(起動中/停止中)をあらわすフラグをインス タンス変数として保持する。 これは、(9)の onSaveInstanceState() メソッドで保存して、 (8)の onRestoreInstanceState() メソッドで復帰することで、画面が隠れてまた現れたときに、 別のアクティビティのインスタンスが生成されても、データが保持されるようにしておく ためである。 この、保存するときのキーは、文字列なので、つぎのような定数を用意しておく。 private static final String IS_RUNNING = "isRunning"; // (2) (3)-(5) 標準出力を置き換え(標準出力(LogCat)+テキストビュー) 標準出力と標準エラー出力を置き換える。 TextView sysout = (TextView) findViewById(R.id.sysout); // (3) System.setOut(new TextViewPrintStream(System.out, sysout)); // (4) System.setErr(new TextViewPrintStream(System.err, sysout)); // (5) コンソール表示のイメージは、つぎの既出のドキュメントを参照されたい。 標準出力 - あて先に画面を追加する http://blog.goo.ne.jp/marunomarunogoo/d/20111222 (6)-(8) 起動中と停止中で、トグルボタンの表示を切り替える if (isRunning) { // 起動中のとき、終了ボタンを表示する // (6) startStopButton.setText(R.string.stopButton); // (7) } else { startStopButton.setText(R.string.startButton); // (8) } (9) onRestoreInstanceState() メソッドのオーバーライド onSaveInstanceState() メソッドで保存されたインスタンスの状態を、アクティビティが 再初期化される直前に呼び出される protected void onRestoreInstanceState(Bundle savedInstanceState) { // (9) (10) 実行状態を復帰する isRunning は boolean 型なので、Bundle クラスの getBoolean() メソッドを使う。 isRunning = savedInstanceState.getBoolean(IS_RUNNING); // (10) 保存されている型によって、さまざまな取得メソッドがある。 ・基本的な型を取得するメソッド --- xxx getXxx(String key) xxx getXxx(String key, xxx defaultValue) xxx[] getXxxArray(String key) --- xxx: boolean, byte, char, double, float, int, long, short, CharSequence, String, Parcelable なお、CharSequence, int, String は、上記に加えてつぎのArrayListを保存できるメソ ッドがある。 --- ArrayList<xxx> getXxxArrayList(String key) --- Parcelable 型については、ジェネリクスとして、型が考えられている。詳しくは、API を参照。 上記以外に、つぎのメソッドがある。 --- Object get(String key) Bundle getBundle(String key) Serializable getSerializable(String key) <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) --- (11) onSaveInstanceState() メソッドのオーバーライド インスタンスの状態をアクティビティに保存するときに呼び出される。 protected void onSaveInstanceState(Bundle outState) { // (11) (12) 実行状態を保存する outState.putBoolean(IS_RUNNING, isRunning); // (12) 取り出すためのキー(文字列)と、保存したい値を指定して、保存する。 保存したものは、対応する getXxx() メソッドによって取得する。 ・基本的な型を保存するメソッド --- void putXxx(String key, xxx value) void putXxxArray(String key, xxx[] value) --- xxx: boolean, byte, char, int, double, float, long, short, CharSequence, String, Parcelable なお、CharSequence, int, String は、上記に加えてつぎのArrayListを保存できるメソ ッドがある。 --- void putXxxArrayList(String key, ArrayList<xxx> value) --- Parcelable 型については、ジェネリクスとして、型が考えられている。詳しくは、API を参照。 上記以外に、つぎのメソッドがある。 --- void putBundle(String key, Bundle value) void putSerializable(String key, Serializable value) void putSparseParcelableArray(String key, SparseArray<? extends Parcelable> value) --- ただ、不思議なのは、Object 型を取得するための Object get(String key) メソッドがあるのに、これに対する void put(String key, Object value) がない。なぜ? 一般的なオブジェクトを保存したい場合は、シリアライズして保存することになるのか? または、いちいち Parcelable インターフェースを実装したクラスを作るのか? Serializable の実装の方が簡単かな。 (13) 開始/終了ボタン押下時 public void onClickStartStopButton(View view) { // (13) (14) 実行状態によって、動きを変える if (isRunning) { // 起動中のとき、とめる // (14) (15) 起動中状態を逆にする isRunning = !isRunning; // 起動中状態を逆にする // (15) トグルボタンで、boolean 型なので、否定すればよい。 ◆ 既出のソースの再掲 つぎのソースは、以下のドキュメントと同じである。 GpsLocationListener.java TextViewPrintStream.java 位置情報 (1) 無線ネットワークを使った位置情報 http://blog.goo.ne.jp/marunomarunogoo/d/20111218 ◆ レイアウト □ res/layout/main.xml --- <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <Button android:id="@+id/startStopButton" android:layout_weight="2" android:layout_width="0dp" android:layout_height="wrap_content" android:onClick="onClickStartStopButton" android:text="@string/startButton" /> </LinearLayout> <!-- プロバイダー名 --> <TextView android:id="@+id/provider_name" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <!-- センサー値 (1) --> <ScrollView android:id="@+id/sysoutScrollView" android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/sysout" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </ScrollView> </LinearLayout> --- (1) センサー値を表示する部分は、コンソール表示のイメージ部分なので、つぎの既出の ドキュメントを参照されたい。 標準出力 - あて先に画面を追加する http://blog.goo.ne.jp/marunomarunogoo/d/20111222 ◆ 文字列 □ res/values/strings.xml --- <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Gps02</string> <string name="logTag">TEST</string> <string name="no_provider">プロバイダーは見つかりませんでした。</string> <string name="sensor_value_format_for_real">%1$s = %2.2f</string> <string name="sensor_value_format_for_timestamp">%1$s = %2$tF %2$tT</string> <string name="latitude">緯度</string> <string name="longitude">経度</string> <string name="accuracy">精度</string> <string name="altitude">標高</string> <string name="time">時間</string> <string name="speed">速度</string> <string name="bearing">ベアリング</string> <string name="startButton">開始</string> <string name="stopButton">終了</string> <string name="startMessageFormat">開始します。時間 = %1$tF %1$tT</string> <string name="stopMessageFormat">終了します。時間 = %1$tF %1$tT</string> <string name="providerDisable">無線ネットワークを使用できるようにしてください。</string> <string name="providerEnabled">無線ネットワークを使用します。</string> </resources> --- ◆マニフェスト 以下の Gps01 プロジェクトと同じ。 位置情報 (1) 無線ネットワークを使った位置情報 http://blog.goo.ne.jp/marunomarunogoo/d/20111218