marunomaruno-memo

marunomaruno-memo

[Android][AppInventor] PaintPot の Java 版

2011年12月30日 | Android
[Android][AppInventor] PaintPot の Java 版
================================================================================

AppInventor のチュートリアルのサンプル「PaintPot」を Java を使って実装してみる。

PaintPot は以下のようなアプリ。
---
PaintPot で画面をタッチしてさまざまなカラーのドットや線を描いてらくがきができま
す。
これが PaintPot アプリでできることです。
    描きたい色の仮想ペンキ入れに、指を浸します。
    画面にドラッグした指にそって線が描かれます。
    画面を突いてドットを描きます。
    下のボタンを使って画面をきれいに拭き取ります。
    描画する背景としてイメージが含まれています
---
(ソフトウェア技術ドキュメントを勝手に翻訳 - AppInventor チュートリアル、
http://www.techdoctranslator.com/appinventor/learn/tutorials より)

ただし、オリジナルは、画面の縦横が変わっても線が保持されているが、これは保持して
いない。


今回作ったクラスはつぎの 4 つ。

PaintPotActivity    アクティビティ
CanvasView          カスタム・ビュー(このクラスで描画)
PointWithPaint      Paint オブジェクト付きの点
PathWithPaint       Paint オブジェクト付きの折れ線


今回作ってみて、GUI 系のことは、AppInventor のようなものがあると便利。Java で作
るとなると、わりと面倒、という感じだ。


■ アクティビティ

線の色や太さを変えるボタンとその処理。
今回は、レイアウトの属性 android:onClick 属性にハンドラーを設定したので、このク
ラスの中でハンドラーを定義している。

□ PaintPotActivity
---
package jp.marunomaruno.android.paintpot;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

/**
 * AppInventor のサンプル PaintPot の Java 版。
 * アクティビティ
 * @author marunomaruno
 * @version 1.0, 2011-12-25
 */
public class PaintPotActivity extends Activity {

    private CanvasView canvasView;    // (1)

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

    @Override
    protected void onResume() {
        Log.d("TEST", "PaintPotActivity.onResume()");
        super.onResume();

        canvasView = (CanvasView) findViewById(R.id.canvasView);
    }

    /**
     * 「赤」ボタン・クリック
     * @param v
     */
    public void onRedButtonClick(View v) {
        Log.d("TEST", String.format(
                "PaintPotActivity.onRedButtonClick() - [%s]", v.toString()));
        canvasView.setPaintColor(Color.RED);    // (2)
    }

    /**
     * 「緑」ボタン・クリック
     * @param v
     */
    public void onGreenButtonClick(View v) {
        Log.d("TEST", String.format(
                "PaintPotActivity.onGreenButtonClick() - [%s]", v.toString()));
        canvasView.setPaintColor(Color.GREEN);    // (3)
    }

    /**
     * 「青」ボタン・クリック
     * @param v
     */
    public void onBlueButtonClick(View v) {
        Log.d("TEST", String.format(
                "PaintPotActivity.onBlueButtonClick() - [%s]", v.toString()));
        canvasView.setPaintColor(Color.BLUE);    // (4)
    }

    /**
     * 「クリア」ボタン・クリック
     * @param v
     */
    public void onWipeButtonClick(View v) {
        Log.d("TEST", String.format(
                "PaintPotActivity.onWipeButtonClick() - [%s]", v.toString()));
        canvasView.wipe();    // (5)
    }

    /**
     * 「太線」ボタン・クリック
     * @param v
     */
    public void onHeavyButtonClick(View v) {
        Log.d("TEST", String.format(
                "PaintPotActivity.onHeavyButtonClick() - [%s]", v.toString()));
        canvasView.setStrokeWidth(CanvasView.HEAVY);    // (6)
    }

    /**
     * 「細線」ボタン・クリック
     * @param v
     */
    public void onThinButtonClick(View v) {
        Log.d("TEST", String.format(
                "PaintPotActivity.onThinButtonClick() - [%s]", v.toString()));
        canvasView.setStrokeWidth(CanvasView.THIN);    // (7)
    }
}
---

(1) カスタムビューのオブジェクト

ボタンをクリックしたときに、色を変えたり、線の太さを変えたりするために持っている。

    private CanvasView canvasView;    // (1)


(2)-(7) カスタムビューの属性設定 

それぞれ、ボタンをクリックしたときに行う処理。

    canvasView.setPaintColor(Color.RED);    // (2)
    canvasView.setPaintColor(Color.GREEN);    // (3)
    canvasView.setPaintColor(Color.BLUE);    // (4)
    canvasView.wipe();    // (5)
    canvasView.setStrokeWidth(CanvasView.HEAVY);    // (6)
    canvasView.setStrokeWidth(CanvasView.THIN);    // (7)


■ カスタム・ビュー

描画のキャンパスになっているカスタム・ビューのクラス。
丸や線のデータをリストで保持している。

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

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

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;

/**
 * AppInventor のサンプル PaintPot の Java 版。 
 * 丸を打つ、線を引くキャンバス。
 * @author marunomaruno
 * @version 1.0, 2011-12-25
 */
public class CanvasView extends ImageView {
    public static final float HEAVY = 15F;    // 太い    // (1)
    public static final float THIN = 5F;    // 細い    // (2)

    private Paint paint;

    private List<PointWithPaint> dots; // ドット用    // (3)
    private List<PathWithPaint> lines; // 軌跡用    // (4)

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

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

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

    /**
     * 初期化する。
     * @param context
     */
    private void init(Context context) {
        paint = new Paint();
        paint.setStrokeWidth(THIN);
        paint.setAntiAlias(true);

        wipe();
    }

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

        // 丸を描画する
        for (PointWithPaint point : dots) {
            Log.v("TEST", "dot = " + point.toString());
            canvas.drawCircle(point.x, point.y, point.paint.getStrokeWidth(),
                    point.paint);
        }

        // 線を描画する
        for (PathWithPaint line : lines) {
            Log.v("TEST", "line = " + line.toString());
            canvas.drawPath(line.path, line.paint);
        }

    }

    // onTouchEvent() だけで使用
    private int prevAction = -1; // 直前のアクション    // (5)
    private Path path = null; // パス    // (6)

    /*
     * (non-Javadoc)
     * @see android.view.View.OnTouchListener#onTouch(android.view.View,
     * android.view.MotionEvent)
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("TEST", String.format(
                "CanvasTouchlistener.onTouch() - action = [%d]", event
                        .getAction()));

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:    // (7)
            path = new Path();
            path.moveTo(event.getX(), event.getY());
            break;

        case MotionEvent.ACTION_UP:    // (8)
            if (prevAction == MotionEvent.ACTION_DOWN) {    // (9)
                dots.add(new PointWithPaint(event.getX(), event.getY(), paint));
            }

            lines.add(new PathWithPaint(path, paint));
            invalidate();

            break;

        case MotionEvent.ACTION_MOVE:    // (10)
            path.lineTo(event.getX(), event.getY());

            break;

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

        }

        prevAction = event.getAction();    // (11)

        return true;    // (12)
    }

    /**
     * 指定された色に設定する。
     * @param color 色
     */
    public void setPaintColor(int color) {
        paint.setColor(color);
    }

    /**
     * 指定された線の幅にする。
     * @param width 幅
     */
    public void setStrokeWidth(float width) {
        paint.setStrokeWidth(width);
    }

    /**
     * 描画したものをきれいにする。
     */
    public void wipe() {
        dots = new ArrayList<PointWithPaint>();
        lines = new ArrayList<PathWithPaint>();
        invalidate();
    }
}
---

(1)(2) 線の太さをあらわす定数

    public static final float HEAVY = 15F;    // 太い    // (1)
    public static final float THIN = 5F;    // 細い    // (2)


(3)(4) 軌跡の保持


    private List<PointWithPaint> dots; // ドット用    // (3)
    private List<PathWithPaint> lines; // 折れ線用    // (4)


(5)(6) onTouchEvent() だけで使用するインスタンス変数

ACTION_UP したときにサークルを描くと、ドラッグしているときに描いた折れ線の最後に
サークルが描かれるので、前回のアクションを保持しておくための変数。

    private int prevAction = -1; // 直前のアクション    // (5)


1回の移動で描かれる折れ線を保持する。

    private Path path = null; // パス    // (6)


(7) タッチしたとき

タッチしたときは、新しい折れ線のオブジェクトを生成して、そこをスタート地点にする。

    case MotionEvent.ACTION_DOWN:    // (7)
        path = new Path();
        path.moveTo(event.getX(), event.getY());
        break;


(8)(9) 指が離れたとき

指が離れたときは、その座標を折れ線の最後にする。
また、タッチして、その座標で指を離せば、そこにサークルを描く。

    case MotionEvent.ACTION_UP:    // (8)
        if (prevAction == MotionEvent.ACTION_DOWN) {    // (9)
            dots.add(new PointWithPaint(event.getX(), event.getY(), paint));
        }

        lines.add(new PathWithPaint(path, paint));
        invalidate();


(10) 指を動かしているとき

指を動かしているときは、その座標を折れ線の点とする。

    case MotionEvent.ACTION_MOVE:    // (10)
        path.lineTo(event.getX(), event.getY());


(11) 前回のアクションを保持

    prevAction = event.getAction();    // (11)


(12) アクションは処理済とする

    return true;    // (12)

この返り値が false のままだと、未処理となるので、つぎのイベントを拾えない。
今回は、MotionEvent を拾うハンドラーは、このメソッドなので、true にしておく。


■ データ

描画するデータを保持するクラス。
今回は、色と線の太さも保持しないといけないので、
タッチされた座標と Paint オブジェクトのペアを持つクラス PointWithPaint 
と、
移動した座標群と Paint オブジェクトのペアを持つクラス PathWithPaint 
の 2 つのクラスを作る。


□ PointWithPaint.java
---
package jp.marunomaruno.android.paintpot;

import android.graphics.Paint;
import android.graphics.PointF;

/**
 * AppInventor のサンプル PaintPot の Java 版。 
 * Paintオブジェクトつきの点
 * @author marunomaruno
 * @version 1.0, 2011-12-25
 */
public class PointWithPaint {
    float x;
    float y;
    Paint paint;

    public PointWithPaint(float x, float y, Paint paint) {
        this.x = x;
        this.y = y;
        this.paint = new Paint(paint); // (1)
        this.paint.setStyle(Paint.Style.FILL); // 塗りつぶす // (2)
    }

    public PointWithPaint(PointF point, Paint paint) {
        this(point.x, point.y, paint);
    }

    @Override
    public String toString() {
        return String.format("[%.1f, %.1f, %.1f, %x]", x, y, paint
                .getStrokeWidth(), paint.getColor());
    }
}
---

(1)(2) あたらしいペイント・オブジェクトを生成

あたらしいペイント・オブジェクトとして保持する必要がある。そうでないと、最後に設
定したペイント・オブジェクトの値が描画時に採用されることになる。

    this.paint = new Paint(paint); // (1)

また、円を描くので、ここでは、塗りつぶしのスタイルにする。

    this.paint.setStyle(Paint.Style.FILL); // 塗りつぶす // (2)


□PathWithPaint.java
---
package jp.marunomaruno.android.paintpot;

import android.graphics.Paint;
import android.graphics.Path;

/**
 * AppInventor のサンプル PaintPot の Java 版。 
 * Paintオブジェクトつきの折れ線。
 * @author marunomaruno
 * @version 1.0, 2011-12-25
 */
public class PathWithPaint {
    Path path;
    Paint paint;

    public PathWithPaint(Path path, Paint paint) {
        super();
        this.path = path;
        this.paint = new Paint(paint);    // (1)
        this.paint.setStyle(Paint.Style.STROKE); // 輪郭のみ    // (2)
    }

    @Override
    public String toString() {
        return String.format("[%s, %.1f, %x]", getPathString(), paint
                .getStrokeWidth(), paint.getColor());
    }

    private String getPathString() {
        return path.toString();
    }

}
---

(1)(2) あたらしいペイント・オブジェクトを生成

あたらしいペイント・オブジェクトとして保持する必要がある。そうでないと、最後に設
定したペイント・オブジェクトの値が描画時に採用されることになる。

    this.paint = new Paint(paint); // (1)

また、折れ線を描くので、ここでは、輪郭のみのスタイルにする。

    this.paint.setStyle(Paint.Style.STROKE); // 輪郭のみ    // (2)


■ レイアウト

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

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

        <Button
            android:id="@+id/redButton"
            android:onClick="onRedButtonClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/redButton" />

        <Button
            android:id="@+id/greenButton"
            android:onClick="onGreenButtonClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/greenButton" />

        <Button
            android:id="@+id/blueButton"
            android:onClick="onBlueButtonClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/blueButton" />

    </LinearLayout>

    <jp.marunomaruno.android.paintpot.CanvasView
        android:id="@+id/canvasView"
        android:layout_width="fill_parent"
        android:layout_height="300px"
        android:scaleType="fitXY"
        android:src="@drawable/kitty" />    <!-- (1) -->

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

        <Button
            android:id="@+id/wipeButton"
            android:onClick="onWipeButtonClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/wipeButton" />

        <Button
            android:id="@+id/heavyButton"
            android:onClick="onHeavyButtonClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/heavyButton" />

        <Button
            android:id="@+id/thinButton"
            android:onClick="onThinButtonClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/thinButton" />

    </LinearLayout>

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Medium Text"
        android:textAppearance="?android:attr/textAppearanceMedium" />


</LinearLayout>
---

(1) カスタムビュー

要素名に、カスタム・ビューの完全修飾名をしていするだけでよい。

    <jp.marunomaruno.android.paintpot.CanvasView
        android:id="@+id/canvasView"
        android:layout_width="fill_parent"
        android:layout_height="300px"
        android:scaleType="fitXY"
        android:src="@drawable/kitty" />    <!-- (1) -->

ここで、指定された大きさ (layout_width と layout_height) に対して、画像を自動的
にその大きさに拡大・縮小して表示する属性を指定しておきます。
        android:scaleType="fitXY"

値は以下のものが指定できる。

------------- ----------------------- -------- ----------
値            説明                    リサイズ 縦横比維持
------------- ----------------------- -------- ----------
matrix        描画時に Matrix を使用     O         X
fitXY         表示領域の大きさにする     O         X
fitStart      左上端寄せ                 O         O
fitCenter     中央寄せ                   O         O
fitEnd        右下端寄せ                 O         O
center        中央寄せ(リサイズなし)     X         -
centerCrop    中央寄せ(余白含める)       O         O
centerInside  中央寄せ(余白含めない)     O         O
------------- ----------------------- -------- ----------


■ 文字列

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, PaintPotActivity!</string>
    <string name="app_name">PaintPot</string>
    <string name="redButton">赤</string>
    <string name="greenButton">緑</string>
    <string name="blueButton">青</string>
    <string name="wipeButton">クリア</string>
    <string name="heavyButton">太線</string>
    <string name="thinButton">細線</string>
</resources>
---



[Android][AppInventor] MIT 版 App Inventor のインストール

2011年12月23日 | Android
MIT 版 App Inventor のインストール
================================================================================

参考を基にして、MIT 版 App Inventor をインストールした。

なお、文中の 
【AppId】は、自分の登録したアプリケーションID
【email】は、アプリケーションID を登録した email
を示す。


□ 参考

「Running App Inventor services with the MIT JAR files」の翻訳
http://www.app-inventor.jp/shiryoushuu/transrate

MIT 版App Inventorをローカルで動かしてみた。動いたよ! - gabuchanの日記
http://d.hatena.ne.jp/gabuchan/20111217/1324133785


□ 環境の前提

OS: CentOS 5.5
JDK 1.6

また、作業は root で行った


■ 1. Google App Engine の設定

1. Google App Engine のダウンロード

つぎのURLからダウンロードする

Java 用 Google App Engine SDK
http://code.google.com/intl/ja/appengine/downloads.html#Google_App_Engine_SDK_for_Java
appengine-java-sdk-1.6.1.zip


2. このファイルを展開して、/usr/local/ に配置


3. この中のコマンドを使えるように、パスに設定

ログインプロファイルの更新と反映 

編集する
# vi .bash_profile 

つぎを追加する
PATH=$PATH:/usr/local/appengine-java-sdk-1.6.1/bin 

設定値をプロンプトに反映させる
# source .bash_profile


■ 2. MIT 版 App Inventor の設定

1. MIT 版 App Inventor サービス ZIP 版のダウンロード

つぎのURLからダウンロードする
Download .zip File to Run Your Own Instance of App Inventor
http://appinventoredu.mit.edu/download-jar-files
appinventor-service-Dec-20.zip

※2011-12-22 現在、上記のファイルが最新

キュメント
https://docs.google.com/document/d/124V0q-Jzs8n9LqAlFKnSWxGLei_KZAUQGJUZwlALVws/
edit?hl=en_US&pli=1


2. ファイルを解凍

つぎの 2 つの tgz ファイルが出る。
appinventor-service-Dec-20.zip_FILES
    + appinventor-service-Dec-20
        + appinventor-Nov-24.tgz        App Engine 用
        + for-BuildServer.tgz           ビルドサーバー 用

なお、__MACOSX というディレクトリーもできるが無視。


■ 3. ビルドサーバーの構築

1. App Engine 用ファイルの展開

appinventor-Nov-24.tgz : App Engine用

解凍後
appinventor-service-Dec-20.zip_FILES
    + appinventor-service-Dec-20
        + appinventor-Dec-20
            + appinventor
                + war
                    + WEB-INF
                    |    + appengine-web.xml           App Engine の記述子
                    + whitelist                        ホワイトリスト


2. ビルドサーバーの指定

appinventor-Nov-24.tgz を展開して、そこの中にある
    appinventor/war/WEB-INF/appengine-web.xml
ファイルを編集する。

ビルドサーバーは、values 属性値で指定する。なお、ポート番号は変更できない。
    <property name="build.server.host" value="localhost:9990" />

※ローカルホストで行うのでこのまま

ホワイトリストを使うのであれば、value="true" にする。
    <property name="use.whitelist" value="false"/>

このとき、つぎのファイルの中のリストを設定する。
    war/whitelist

※ホワイトリストを使わないのでこのまま


3. for-BuildServer.tgz を展開

このファイルはビルドサーバーに持っていく。
※今回は、ローカルなのでこのままにしておく。

for-BuildServer.tgz : ビルドサーバ用
    + for-BuildServer
        + lib
        + launch-buildserver


4. ビルドサーバーを起動

つぎのコマンドを実行する。
---
# killall java
# ./launch-buildserver
---

次のコマンドでログを確認できる。
---
# cat -f buildserver-log.out
  class com.google.appinventor.buildserver.BuildServer
2011/12/22 10:11:01 com.sun.jersey.api.core.ScanningResourceConfig init
情報: No provider classes found.
2011/12/22 10:11:01 com.sun.jersey.server.impl.application.WebApplicationImpl _i
nitiate
情報: Initiating Jersey application, version 'Jersey: 1.3 06/17/2010 05:04 PM'
2011/12/22 10:11:02 com.sun.grizzly.Controller logVersion
情報: Starting Grizzly Framework 1.9.18-i - Thu Dec 22 10:11:02 JST 2011
App Inventor Build Server - Version: 42 Id: 40fd4340860b
Visit: http://127.0.0.1:9990/buildserver
Server running
---

なお、
http://127.0.0.1:9990/buildserver
を見ても、何も(エラーも)表示されない。


※終了させるコマンドが見当たらないようなので、kill コマンドで終了させる。


■ 4. App Engine の構築

1. App Engine に App Inventor サービスをアップロード

つぎのコマンドでアップロードする
---
# appcfg.sh -A 【AppId】 update war
Reading application configuration data...
2011/12/22 10:37:04 com.google.apphosting.utils.config.AppEngineWebXmlReader rea
dAppEngineWebXml
情報: Successfully processed war/WEB-INF/appengine-web.xml
2011/12/22 10:37:04 com.google.apphosting.utils.config.AbstractConfigXmlReader r
eadConfigXml
情報: Successfully processed war/WEB-INF/web.xml
2011/12/22 10:37:04 com.google.apphosting.utils.config.IndexesXmlReader readConf
igXml
情報: Successfully processed war/WEB-INF/appengine-generated/datastore-indexes-a
uto.xml
Beginning server interaction for 【AppId】...
Email: 【email】
Password for 【email】: 
0% Created staging directory at: '/tmp/appcfg5728424899164162038.tmp'
5% Scanning for jsp files.
20% Scanning files on local disk.
25% Scanned 250 files.
 : (中略)
99% Will check again in 4 seconds.
99% Closing update: new version is ready to start serving.
99% Uploading index definitions.

Update completed successfully.
Success.
Cleaning up temporary files...
---


■ 5. MIT 版 App Inventor を使う

1. ブラウザーでアクセス

Webブラウザーで、つぎのアドレスで、App Inventor が使える

http://【AppId】.appspot.com/


2. ログイン

最初にログイン・ダイアログが表示されるが、今回はホワイトリストを設定していないの
で、そのまま「Log In」をクリックする。


3. 利用規約に同意

つぎのメッセージが表示されるので、「Terms of Service」をクリックして同意
---
To use App Inventor for Android, you must accept the following terms of service.
Terms of Service
You agree to have lots of fun learning to use App Inventor and making apps for y
our Android device! 
---


4. App Inventor を使う

つぎのメッセージが赤字で表示
---
This is an experimental version of App Inventor. IT IS FOR TESTING ONLY, NOT FOR
 GENERAL USE! 
---

あとは、普通に使えると思う。
ソースのアップロードも普通に行えた。
実機でも動作が確認できた。


■ 6. ローカル環境で使う

1. [3. ビルドサーバーの構築] まで行ったら、つぎのコマンドでApp Engine を構築する。

---
# dev_appserver.sh war/
2011/12/22 1:40:47 com.google.apphosting.utils.jetty.JettyLogger info
情報: Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLo
gger
 : (中略)
2011/12/22 1:40:49 com.google.appengine.tools.development.DevAppServerImpl start
情報: The server is running at http://localhost:8080/
2011/12/22 1:40:49 com.google.appengine.tools.development.DevAppServerImpl start
情報: The admin console is running at http://localhost:8080/_ah/admin
---

2. 使う

ローカルの場合、赤い色で次が出る
---
This is an experimental version of App Inventor. IT IS FOR TESTING ONLY, NOT FOR
 GENERAL USE! Note: This App Inventor instance is not being hosted on AppEngine.
 As a result, it will not correctly save your projects when you log out. You'll 
have to download them if you want them saved.
---

ローカルなので、プロジェクトの保存が必要、とのこと。

★ローカルで実行したときに、なんと、ブロックエディッターで、日本語表示ができた。
うれしい。
ただ、なぜできたかは今のところ不明。

※こちらも、kill コマンドで終了かな。



[Android] 標準出力 - あて先に画面を追加する

2011年12月22日 | Android
標準出力 - あて先に画面を追加する
================================================================================

■ 標準出力のあて先に画面を追加する

標準出力(標準エラー出力も含む)のあて先は、LogCat。
たとえば、
    System.out.print("test");
としたときは、つぎと同じことになる。
    Log.i("System.out", "test");

このあて先に、画面(TextView)も追加する。

これには、PrintStream クラスを継承したクラスを作って、System.setOut() メソッドで、
標準出力オブジェクトを置き換えてやればよい。

画面でスクロールさせるために、res/layout/main.xml に ScrollView を設定してあげる
必要がある。


◆ アクティビティのクラス。

□ SystemOut01Activity.java
---
package jp.marunomaruno.android.sample;

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

/**
 * @author marunomaruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class SystemOut01Activity extends Activity {

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

        // 標準出力を置き換え(標準出力(LogCat)+テキストビュー)
        TextView sysout = (TextView) findViewById(R.id.sysout);
        System.setOut(new TextViewPrintStream(System.out, sysout));    // (1)
        System.setErr(new TextViewPrintStream(System.err, sysout));    // (2)
        
        // 出力する
        for (int i = -50; i < 50; i++) {
            System.out.printf("%3d|", i);    // (3)
            for (int j = 0; j < Math.abs(i); j++) {
                System.out.print("*");
            }
            System.out.println();
        }

        // 例外をスローする
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();    // (4)
        }
    }
}
---


(1)(2) 標準出力、標準エラー出力の置き換え

    System.setOut(new TextViewPrintStream(System.out, sysout));    // (1)
    System.setErr(new TextViewPrintStream(System.err, sysout));    // (2)

こうすることで、標準出力、標準エラー出力のどちらも指定した TextView で出力させる
ことができる。


(3) 標準出力を使う

    System.out.printf("%3d|", i);    // (3)


(4) 標準エラー出力を使う

    e.printStackTrace();    // (4)


◆ 標準出力をテキストビューにも出力させるクラス。

PrintStream クラスを継承したクラスを作って、print(String) メソッドをオーバーライ
ドすればよい。


□ TextViewPrintStream.java
---
package jp.marunomaruno.android.sample;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;

import android.view.View;
import android.widget.ScrollView;
import android.widget.TextView;

/**
 * 標準入出力を Log と テキストビューに出力させるようにするクラス.
 * 指定されたテキストビューは、スクロールビューに囲まれている必要がある。
 * @author marunomaruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class TextViewPrintStream extends PrintStream { // (1)

    private TextView view; // (2)
    private Runnable scrollDown = new ScrollDown();    // (3)
    
    public TextViewPrintStream(File file, String csn, TextView view)
            throws FileNotFoundException, UnsupportedEncodingException { // (4)
        super(file, csn);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    public TextViewPrintStream(File file, TextView view)
            throws FileNotFoundException {    // (4)
        super(file);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    public TextViewPrintStream(OutputStream out, boolean autoFlush, String enc,
            TextView view) throws UnsupportedEncodingException {    // (4)
        super(out, autoFlush, enc);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    public TextViewPrintStream(OutputStream out, boolean autoFlush,
            TextView view) {    // (4)
        super(out, autoFlush);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    public TextViewPrintStream(OutputStream out, TextView view) {    // (4)
        super(out);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    public TextViewPrintStream(String fileName, String csn, TextView view)
            throws FileNotFoundException, UnsupportedEncodingException { // (4)
        super(fileName, csn);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    public TextViewPrintStream(String fileName, TextView view)
            throws FileNotFoundException {    // (4)
        super(fileName);
        this.view = view;
        ((View) view.getParent()).post(scrollDown);    // (5)
    }

    @Override
    public synchronized void print(String str) { // (6)
        super.print(str);
        view.append(str); // (7)
    }

    /**
     * スクロールダウンさせるスレッドのクラス
     * @author marunomaruno
     */
    private class ScrollDown implements Runnable { // (8)
        public void run() {
            ((ScrollView) view.getParent()).fullScroll(View.FOCUS_DOWN); // (9)
        }
    }
}
---


(1) PrintStream クラスのサブクラスを作る

    public class TextViewPrintStream extends PrintStream { // (1)


(2) 出力先のオブジェクト

    private TextView view; // (2)

テキストビューに出力させるので、それを保持しておく。


(3) スクロールダウンさせるスレッドのオブジェクト

    private Runnable scrollDown = new ScrollDown();    // (3)

ScrollDown クラスは、(8)で作成するプライベートな内部クラス。


(4) コンストラクター

現行の PrintStream クラスのコンストラクターの引数の後ろに、出力先になる TextView
 を指定しておく。

    public TextViewPrintStream(File file, String csn, TextView view)
            throws FileNotFoundException, UnsupportedEncodingException { // (4)

    public TextViewPrintStream(File file, TextView view)
            throws FileNotFoundException {    // (4)

    public TextViewPrintStream(OutputStream out, boolean autoFlush, String enc,
            TextView view) throws UnsupportedEncodingException {    // (4)

    public TextViewPrintStream(OutputStream out, boolean autoFlush,
            TextView view) {    // (4)

    public TextViewPrintStream(OutputStream out, TextView view) {    // (4)

    public TextViewPrintStream(String fileName, String csn, TextView view)
            throws FileNotFoundException, UnsupportedEncodingException { // (4)

    public TextViewPrintStream(String fileName, TextView view)
            throws FileNotFoundException {    // (4)


(5) このテキストビューを囲むスクロールビュー・オブジェクトに対してつねに最下段を
指すように指定

    ((View) view.getParent()).post(scrollDown);    // (5)


(6) print メソッドのオーバーライド

    public synchronized void print(String str) { // (6)

このメソッドをオーバーライドすることで、自分の意図した PrintStream オブジェクト
になる。


(7) TextView に文字列を追加する

    view.append(str); // (7)


(8) スクロールさせるためのクラス

    private class ScrollDown implements Runnable { // (8)


(9) スクロールさせる

このテキストビューを囲むスクロールビュー・オブジェクトに対して、最下段を表示する
ように指定する。

    ((ScrollView) view.getParent()).fullScroll(View.FOCUS_DOWN); // (9)


□View クラスのどの位置にするかの定数
---
int     FOCUS_BACKWARD
int     FOCUS_DOWN
int     FOCUS_FORWARD
int     FOCUS_LEFT
int     FOCUS_RIGHT
int     FOCUS_UP
---


□ScrollView.fullScroll メソッド
---
public boolean fullScroll (int direction)
---
direction に、上記の定数のうち、FOCUS_DOWN または FOCUS_UP を指定する。


◆ レイアウト

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

    <ScrollView
        android:id="@+id/sysoutScrollView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >    <!-- (1) -->

        <TextView
            android:id="@+id/sysout"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />    <!-- (2) -->
    </ScrollView>
</LinearLayout>
---

(1) スクロールビュー

(2) 標準出力のあて先のテキストビュー



◆ 文字列の定数

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



[Android] 位置情報 (1) 無線ネットワークを使った位置情報

2011年12月18日 | Android
位置情報 (1) 無線ネットワークを使った位置情報
================================================================================

■ 無線ネットワークを使った位置情報

無線ネットワークを使った位置情報を取得する。
これも、センサーを使ったものと同じようにプログラミングできる。

基本は、以下の手順による。
1. LocationManager のオブジェクトを取得する
2. LocationManager オブジェクトに位置情報取得のリスナーを登録する
3. 位置情報が変化したら、その値を取得する


◆ アクティビティのクラス。

□ Gps01Activity.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.widget.TextView;

/**
 * 位置情報を取得して表示する。
 * @author marunomaruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class Gps01Activity extends Activity {
    // TextView
    private TextView sensorValueTextView; // GPS値

    private LocationManager locationManager;                            // (1)
    private LocationListener sensorEventListener; // onPause()が呼ばれたときに、すべてのリスナーを解除するため

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

        sensorValueTextView = (TextView) findViewById(R.id.sensor_value);
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); // (2)
        Log.d(getString(R.string.logTag), "locationManager = "
                + locationManager);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // プロバイダーを取得する条件を作成する
        Criteria fineAndLowPower = new Criteria();                      // (3)
        fineAndLowPower.setAccuracy(Criteria.ACCURACY_FINE);            // (4)
        fineAndLowPower.setPowerRequirement(Criteria.POWER_LOW);        // (5)
        fineAndLowPower.setAltitudeRequired(true);                      // (6)
        fineAndLowPower.setBearingRequired(true);                       // (7)
        fineAndLowPower.setSpeedRequired(true);                         // (8)

        // プロバイダーを取得する
        String provider = locationManager
                .getBestProvider(fineAndLowPower, true);                // (9)
        Log.d(getString(R.string.logTag), "provider = "
                + provider);

        TextView providerNameTextView = (TextView) findViewById(R.id.provider_name);
        providerNameTextView.setText(provider);
        
        // プロバイダーがみつからなければ、NETWORK_PROVIDER を設定しておく
        if (provider == null) {                                         // (10)
            provider = LocationManager.NETWORK_PROVIDER;
            providerNameTextView.setText(R.string.no_provider);
        }
        
        // 位置情報のリスナーを取得して登録する
        sensorEventListener = new GpsLocationListener(sensorValueTextView); // (11)
        locationManager.requestLocationUpdates(
                provider, 
                0, 
                0, 
                sensorEventListener);                                   // (12)
    }

    @Override
    public void onPause() {
        super.onPause();

        // すべてのリスナーを解除する
        locationManager.removeUpdates(sensorEventListener);             // (13)
    }
}
---

(1)(2) LocationManager とその取得

    private LocationManager locationManager;        // (1)


□ LocationManager

LocationManager は、位置情報を管理するクラス。ここに、位置情報が変更すると起こる
イベントに対するリスナーを登録する。

java.lang.Object
   +     android.location.LocationManager


このクラスのオブジェクトは、
Context.getSystemService(Context.LOCATION_SERVICE)
をとおして取得する。実際は(2)を参照。


Context のサブクラスである Activity クラスのサブクラス中なので、つぎのように取得
できる。

    locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); // (2)


(3)-(8) プロバイダーを取得する基準を作成する

位置情報を取得するためのプロバイダーを選ぶための基準を作成する。この基準に基づい
て、システムは最適と思われるプロバイダーを選ぶ。

    Criteria fineAndLowPower = new Criteria();                      // (3)

基準オブジェクトを作成して、つぎのメソッドを使って基準を設定する。

    fineAndLowPower.setAccuracy(Criteria.ACCURACY_FINE);            // (4)
    fineAndLowPower.setPowerRequirement(Criteria.POWER_LOW);        // (5)
    fineAndLowPower.setAltitudeRequired(true);                      // (6)
    fineAndLowPower.setBearingRequired(true);                       // (7)
    fineAndLowPower.setSpeedRequired(true);                         // (8)


□ Criteria クラス

プロバイダーを選択する上での基準になる。ここに、選択する基準を設定する。

java.lang.Object
   +     android.location.Criteria


□ 定数
---
int     ACCURACY_COARSE    おおよその精度
int     ACCURACY_FINE      細かい精度
int     ACCURACY_HIGH      高い精度
int     ACCURACY_LOW       低い精度
int     ACCURACY_MEDIUM    中間の精度
int     NO_REQUIREMENT     要求しない
int     POWER_HIGH         電池を多く使う
int     POWER_LOW          電池をそれほど使わない
int     POWER_MEDIUM       電池を普通に使う
---

□ コンストラクター
---
    Criteria()
    Criteria(Criteria criteria)
---


□ おもな基準を設定するメソッド
---
void  setAccuracy(int accuracy)                     緯度・経度の精度を設定
void  setBearingAccuracy(int accuracy)              ベアリングの精度を設定
void  setHorizontalAccuracy(int accuracy)           緯度・経度の精度を設定
void  setPowerRequirement(int level)                最大電力レベルを設定
void  setSpeedAccuracy(int accuracy)                速度の精度を設定
void  setVerticalAccuracy(int accuracy)             高度の精度を設定
void  setCostAllowed(boolean costAllowed)   プロバイダがコストを負担するかどうか
void  setAltitudeRequired(boolean altitudeRequired) 標高を出すかどうか
void  setBearingRequired(boolean bearingRequired)   ベアリングを出すかどうか
void  setSpeedRequired(boolean speedRequired)       速度を出すかどうか
---

なお、それぞれの基準を設定するメソッドによって、指定できる基準の定数が決まってい
る。この定数以外だと、IllegalArgumentException 例外がスロー。

--------------------- ------  ------  ------  ------  ------  --------------
                      COARSE   FINE    HIGH     LOW   MEDIUM  NO_REQUIREMENT
--------------------- ------  ------  ------  ------  ------  --------------
setAccuracy             ○      ○
setBearingAccuracy                      ○      ○                  ○
setHorizontalAccuracy                   ○      ○      ○          ○
setSpeedAccuracy                        ○      ○                  ○
setVerticalAccuracy                     ○      ○      ○          ○
--------------------- ------  ------  ------  ------  ------  --------------

★ 今回のサンプルの(6)-(8)のように、標高、ベアリング、速度を要求しても、ネット
ワーク・プロバイダーの場合、できなければ 0 が返るようだ。
GPS プロバイダーの場合、動きが止まるようだ(?)。


(9)(10) プロバイダーを取得する

作成した基準に基づいて、プロバイダーを取得する。

    String provider = locationManager
            .getBestProvider(fineAndLowPower, true);                    // (9)
    if (provider == null) {                                             // (10)
        provider = LocationManager.NETWORK_PROVIDER;
        providerNameTextView.setText(R.string.no_provider);
    }

プロバイダーを取得するのは、基準を指定して、その基準にしたがって、よりよいプロバ
イダーをシステムが選択する。
getBestProvider() メソッドの 2 番目の引数 enabledOnly が true の場合は、使用でき
るものだけが取得されるの。使用できるプロバイダーがなければ、null が返る。


□ メソッド
---
String     getBestProvider(Criteria criteria, boolean enabledOnly)
    基準を満たすプロバイダー名を取得する
---

上記以外にも、プロバイダーを取得したり、確認するメソッドがある。
---
List<String>     getAllProviders()
    すべてのプロバイダー名を取得する

LocationProvider     getProvider(String name)
    指定されたプロバイダー名のプロバイダーのオブジェクトを取得する

List<String>     getProviders(boolean enabledOnly)
    基準を満たす、すべてのプロバイダー名を取得する

List<String>     getProviders(Criteria criteria, boolean enabledOnly)
    基準を満たす、すべてのプロバイダー名を取得する

boolean     isProviderEnabled(String provider)
    指定されたプロバイダー名のプロバイダーが使用できるかどうかを返す
---


(11)(12) 位置情報のリスナーを取得して登録

センサーのときと同じように、リスナーのオブジェクトを取得する。

    sensorEventListener = new GpsLocationListener(sensorValueTextView);  // (11)

位置情報のリスナーは、LocationListener インターフェースを実装しているオブジェク
トで、つぎのメソッドを実装しなければならない。
    abstract void onLocationChanged(Location location)
    abstract void onProviderDisabled(String provider)
    abstract void onProviderEnabled(String provider)
    abstract void onStatusChanged(String provider, int status, Bundle extras)


位置情報リスナーの登録には、LocationManager.requestLocationUpdates() メソッドを
使う。

    locationManager.requestLocationUpdates(
            provider, 
            0, 
            0, 
            sensorEventListener);                                    // (12)

このメソッドは、いくつかオーバーロードされている。

□ requestLocationUpdates のオーバーロードされたメソッド
---
void  requestLocationUpdates(long minTime, float minDistance, 
                    Criteria criteria, PendingIntent intent)
    基準 に基づいた インテント を登録する

void  requestLocationUpdates(long minTime, float minDistance, 
                    Criteria criteria, LocationListener listener, Looper looper)
    基準 に基づいた リスナー を登録する

void  requestLocationUpdates(String provider, long minTime, float minDistance,
                                    LocationListener listener)
    プロバイダーに基づいた リスナー を登録する

void  requestLocationUpdates(String provider, long minTime, float minDistance,
                                    LocationListener listener, Looper looper)
    プロバイダーに基づいた リスナー を登録する

void  requestLocationUpdates(String provider, long minTime, float minDistance,
                                    PendingIntent intent)
    プロバイダーに基づいた インテント を登録する
---

それぞれの引数
---
long             minTime      位置情報を更新する最小の時間間隔(ミリ秒)
float            minDistance  位置情報を更新する最小の距離間隔(メートル)
Criteria         criteria     基準
PendingIntent    intent       位置を更新するためのインテント
LocationListener listener     位置が更新されたときに呼ばれるリスナー
Looper           looper       メッセージループのオブジェクト
String           provider     プロバイダー
---

なお、この引数 minTime や minDistance は、指定したとおりになるかどうかはわからな
い。プロバイダー次第か。


□ プロバイダー

LocationManager クラスのプロバイダー関係の定数
---
String  GPS_PROVIDER         GPS ロケーションを利用したプロバイダー
String  NETWORK_PROVIDER     ネットワークを利用したプロバイダー
String  PASSIVE_PROVIDER     位置情報の修正を行わない特殊なプロバイダー
---


(13) すべてのリスナーを解除する

    locationManager.removeUpdates(sensorEventListener);                // (13)


◆ 位置情報のリスナーのクラス。

位置情報が変わったことに対するリスナーのクラス。LocationListener インターフェー
スを実装する。

位置情報が変わったら、onLocationChanged() が呼ばれる。


□ GpsLocationListener.java
---
package jp.marunomaruno.android.sample;

import android.location.Location;
import android.location.LocationListener;
import android.location.LocationProvider;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

/**
 * GPSによるロケーションのリスナー
 * 
 * @author maruno
 * @version 1.0, 2011-12-14
 * @since 1.0
 */
public class GpsLocationListener implements LocationListener {          // (1)

    protected TextView textView;

    public GpsLocationListener(TextView textView) {
        this.textView = textView;
    }

    @Override
    public void onLocationChanged(Location location) {                  // (2)
        Log.d(textView.getResources().getString(R.string.logTag),
                "onLocationChanged()");
        textView.setText("");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_timestamp), 
                textView.getResources().getString(R.string.time), 
                location.getTime()));                                   // (3)
        textView.append("\n");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_real), 
                textView.getResources().getString(R.string.latitude), 
                location.getLatitude()));                               // (4)
        textView.append("\n");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_real), 
                textView.getResources().getString(R.string.longitude), 
                location.getLongitude()));                              // (5)
        textView.append("\n");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_real), 
                textView.getResources().getString(R.string.accuracy), 
                location.getAccuracy()));                               // (6)
        textView.append("\n");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_real), 
                textView.getResources().getString(R.string.altitude), 
                location.getAltitude()));                               // (7)
        textView.append("\n");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_real), 
                textView.getResources().getString(R.string.speed), 
                location.getSpeed()));                                  // (8)
        textView.append("\n");

        textView.append(String.format(textView.getResources().getString(
                R.string.sensor_value_format_for_real), 
                textView.getResources().getString(R.string.bearing), 
                location.getBearing()));                                // (9)
        textView.append("\n");

    }

    @Override
    public void onProviderDisabled(String provider) {                   // (10)
        Log.d(textView.getResources().getString(R.string.logTag),
                "onProviderDisabled()");
    }

    @Override
    public void onProviderEnabled(String provider) {                    // (11)
        Log.d(textView.getResources().getString(R.string.logTag),
                "onProviderEnabled()");
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {    // (12)
        Log.d("TEST", "onStatusChanged()");
        switch (status) {
        case LocationProvider.AVAILABLE:                                // (13)
            Log.d(textView.getResources().getString(R.string.logTag),
                    "onStatusChanged() - AVAILABLE");
            break;
        case LocationProvider.OUT_OF_SERVICE:                           // (14)
            Log.d(textView.getResources().getString(R.string.logTag),
                    "onStatusChanged() - OUT_OF_SERVICE");
            break;
        case LocationProvider.TEMPORARILY_UNAVAILABLE:                  // (15)
            Log.d(textView.getResources().getString(R.string.logTag),
                    "onStatusChanged() - TEMPORARILY_UNAVAILABLE");
            break;
        default:
            Log.d(textView.getResources().getString(R.string.logTag),
                    "onStatusChanged() - Other");
            break;
        }
    }
}
---

(1) LocationListener インターフェースを実装

位置情報を取得するリスナーは、LocationListener インターフェースを実装する。

    public class GpsLocationListener implements LocationListener {       // (1)


□ LocationListener インターフェース

実装すべきメソッド
---
abstract void     onLocationChanged(Location location)
    場所が変更されたときに呼び出される

abstract void     onProviderDisabled(String provider)
    プロバイダーが無効になったときに呼び出される

abstract void     onProviderEnabled(String provider)
    プロバイダーが有効になったときに呼び出される

abstract void     onStatusChanged(String provider, int status, Bundle extras)
    プロバイダーの状態が変更されたときに呼び出される
---


(2) 場所が変更されたときに呼び出されるハンドラー・メソッド

つぎのメソッドを実装する。

    public void onLocationChanged(Location location) {                    // (2)

引数は Location オブジェクト。

□ Location クラス

位置情報を持つクラス。

java.lang.Object
   +     android.location.Location


定数は、以下の 3 つがある。これは、緯度や経度の形式を決める。

定数
---
int FORMAT_DEGREES  [+-]DDD.DDDDD、 D: 度
int FORMAT_MINUTES  [+-]DDD:MM.MMMMM、D: 度、M: 分、1 度 = 60 分
int FORMAT_SECONDS  [+-]DDD:MM:SS.SSSSS、D: 度、M: 分、S: 秒、1 度 = 60 分、1 分 = 60 秒
---

おもなメソッド
---
static String convert (double coordinate, int outputType) 
    度(coordinate)を指定された緯度や経度の形式(outputType)の文字列にする
float    getAccuracy()   精度
double   getAltitude()   標高
float    getBearing()    ベアリング
Bundle   getExtras()     プロバイダー固有の情報
double   getLatitude()   緯度
double   getLongitude()  経度
String   getProvider()   プロバイダー
float    getSpeed()      速度
long     getTime()       時間
---

convert() メソッドの実行例

Location.convert(123.456789, Location.FORMAT_DEGREES) = "123.45679"
Location.convert(123.456789, Location.FORMAT_MINUTES) = "123:27.40734"
Location.convert(123.456789, Location.FORMAT_SECONDS) = "123.27:24.4404"


(3)-(9)

次のメソッドで、緯度や経度を取得している。

    location.getTime()));                                   // (3)
    location.getLatitude()));                               // (4)
    location.getLongitude()));                              // (5)
    location.getAccuracy()));                               // (6)
    location.getAltitude()));                               // (7)
    location.getSpeed()));                                  // (8)
    location.getBearing()));                                // (9)


(10) プロバイダーが無効になったときに呼び出されるハンドラー・メソッド

    public void onProviderDisabled(String provider) {                    // (10)


(11) プロバイダーが有効になったときに呼び出されるハンドラー・メソッド

    public void onProviderEnabled(String provider) {                    // (11)


(12) プロバイダーの状態が変更されたときに呼び出されるハンドラー・メソッド

    public void onStatusChanged(String provider, int status, Bundle extras) {    // (12)

今回のように、ひとつのプロバイダーを登録しているだけだと動かないようだ。


(13)-(15) 状態の変化

LocationProvider の定数によって、どいう状態かを判断する。

    case LocationProvider.AVAILABLE:                                // (13)
    case LocationProvider.OUT_OF_SERVICE:                           // (14)
    case LocationProvider.TEMPORARILY_UNAVAILABLE:                  // (15)

LocationProvider の定数
---
int     AVAILABLE     
int     OUT_OF_SERVICE     
int     TEMPORARILY_UNAVAILABLE 
---


◆ レイアウト

□ 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/provider_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <!-- センサー値 -->
    <TextView
        android:id="@+id/sensor_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</LinearLayout>
---


◆ 文字列の定数

□ res/values/string.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Gps01</string>
    <string name="logTag">TEST</string>
    <string name="no_provider">プロバイダーは見つかりませんでした。</string>
    <string name="gpsUnit">m/s^2</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>
</resources>
---


◆マニフェスト

ユーザーパーミッションをつける。

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

    <uses-sdk android:minSdkVersion="8" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>    <!-- (1) -->
    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>    <!-- (2) -->
    <uses-permission android:name="android.permission.INTERNET"/>    <!-- (3) -->

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".Gps01Activity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
    </application>

</manifest>
---

(1)-(3)

パーミッションは以下のものを使う

ACCESS_FINE_LOCATION    GPS を使う
ACCESS_MOCK_LOCATION    エミュレーターで GPS をエミュレートする
INTERNET                無線を使う

これを、AndroidManifest.xml に記述する。