[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> ---