近況
しばらく間が開いてしまったけど、書けることがないだけで、連日なんだかんだでEclipseいじったり、Android端末(Nexus 5)をヤフオクで落札したり、コンパイルできないAndroidのアプリケーションプロジェクトを量産して気が狂うなどしていた。まぁ、導入部分で2年前のAndroid入門書とかを参考にやるもんじゃないんだろうなあと思いつつ、開発環境を書籍通りにせずウェブで検索しながら独自で構築した自分も悪い。
どこで躓いていたかというと、Androidアプリケーションのお作法的な箇所だと思う。SDKのバージョン、AndroidManifest.xml(Intent Filter)、その他の.xmlファイル、Activity(とView)、R、ファイル構成あたりな気がする。一番分かってなかったのはxmlファイルの重要性。Androidのエミュレータが起動してJavaが少しわかるようになってただけでもう出来る気がしてた。
あとは何やるにしろアルゴリズムが組めるかどうかが大事な気がするので、そこをやっていきたい。
Hello World
環境構築ができている前提で。Eclipseの新規プロジェクトで、Androidアプリケーション・プロジェクトを選択。
アプリケーション名はAndroidHelloWorldとか、適当に、パッケージ名は自分のドメインで作った。SDKは最小を2.3.3、ターゲットは4.x(L Preview)、コンパイラは4.4 KitKat Wear、テーマはHolo Light With Dark Action Bar。
プロジェクトの構成ではアクティビティの作成をチェック(しないと少なくとも僕にはまだめんどくさい)。ランチャーアイコンとかはデフォルトまま。次項目のアクティビティの作成では、Empty Activityを選択する。アクティビティ名は後で変えられそう(リファクタリング→名称変更)なのでそのままにしておく。
とりあえず、パッケージのディレクトリを選択した状態で実行ボタンを押すとAndroidアプリケーションを実行できるので、実行すると、エミュレータが起動してHello Worldが表示される。ここまでが結構紆余曲折あったので感慨深い。
package com.example.androidappsample;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class HelloWorld extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
onCreateメソッドがActivityを継承したクラスActivityMainクラスのなかで最初に呼び出される。この辺はお作法だけど、どういうことなのかもう少し進めたら調べることにする。
R.layout.activity_mainは、resフォルダのlayoutフォルダに入ってるactivity_main.xmlで定義されている内容を表示するような感じらしい。XMLで定義するようだ。
ここまではプロジェクト作成したほぼ空のプロジェクトファイルをビルドしただけなので、書籍の内容に合わせてandroid.view.Viewクラスを継承したHelloWorldViewクラスを作成して、そこで描画した内容を表示するように書き換えてみる。
package com.example.androidappsample;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class HelloWorld extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
// タイトルを表示しない
requestWindowFeature(Window.FEATURE_NO_TITLE);
// HelloWorldViewクラスのインスタンスをContentViewにセット
setContentView(new HelloWorldView(this));
}
}
package com.example.androidappsample;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class HelloWorldView extends View {
public HelloWorldView(Context context) {
super(context);
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawText("こんにちは。", 0, 12, new Paint());
}
}
ViewクラスではonDrawメソッドが呼び出される。Processingのdrawみたいな感じだ。ふむふむ。テキストとか、図形の描画をクラスでやるのは、そんなに苦にならなさそうな気がする。
こう見ると、どこでハマっていたのかすらよくわからなくなるのだけど、新規で.javaファイルを作る感覚で、プロジェクトにクラスを追加しても、AndroidManifext.xmlで宣言されているAction.MAINとかcategory.LAUNCHERみたいな部分が修正されない限り、新規で作ったクラスが実行されるわけでないので、エミュレータではアプリケーションが実行されないとか、作ってもエラーになるようなことだった。
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
端末の実行は、端末届いてないので割愛。基本APIを触っていくことにする。
文字列の描画
package org.cenkhor.stringex;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class StringEx extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(new StringExView(this));
}
}
package org.cenkhor.stringex;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class StringExView extends View {
public StringExView(Context context) {
super(context);
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setTextSize(24);
paint.setColor(Color.argb(122, 255, 0, 0));
canvas.drawText("画面の幅 : " + getWidth() + " 高さ : " + getHeight(), 0, 60, paint);
paint.setColor(Color.rgb(122, 122, 122));
canvas.drawText("Aの文字幅 : " + (int) paint.measureText("A"), 0, 120, paint);
canvas.drawText("AAAAの文字幅 : " + (int) paint.measureText("AAAA"), 0, 150, paint);
canvas.drawText("アセント : " + (int) paint.ascent(), 0, 180, paint);
canvas.drawText("ディセント : " + (int) paint.descent(), 0, 210, paint);
paint.setTextSize(32);
canvas.drawText("文字サイズ : " + (int) paint.getTextSize(), 0, 250, paint);
paint.setTextSize(64);
canvas.drawText("文字サイズ : " + (int) paint.getTextSize(), 0, 330, paint);
}
}
ログの出力
java.util.Log.e(tag,msg)メソッドでLogChatにログメッセージを出力できる。tagはエラーのステータスに応じて設定できる。
図形の描画
package org.cenkhor.graphicex;
import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
public class GraphicEx extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
//ここでnewするクラスを切り替えて図形表示のサンプルを切り替える
setContentView(new GraphicExViewTriangle(this));
}
}
package org.cenkhor.graphicex;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class GraphicExViewLine extends View {
public GraphicExViewLine(Context context) {
super(context);
setBackgroundColor(Color.rgb(255, 255, 255));
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.rgb(122, 122, 122));
for (int i = 0; i < 3000; i++) {
paint.setColor(Color.argb(20,(int) (Math.random() * getWidth()), (int) (Math.random() * getWidth()), (int) (Math.random() * getWidth())));
int startX = (int) (Math.random() * getWidth());
int startY = (int) (Math.random() * getHeight());
int endX = (int) (Math.random() * getWidth());
int endY = (int) (Math.random() * getHeight());
canvas.drawLine(startX, startY, endX, endY, paint);
}
}
}
package org.cenkhor.graphicex;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.View;
public class GraphicExViewPath extends View {
public GraphicExViewPath(Context context) {
super(context);
setBackgroundColor(Color.rgb(255, 255, 255));
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.argb(255,(int) (Math.random() * getWidth()), (int) (Math.random() * getWidth()), (int) (Math.random() * getWidth())));
float startX = (int) (Math.random() * getWidth());
float startY = (int) (Math.random() * getHeight());
Path path = new Path();
path.moveTo(startX, startY);
for (int i = 0; i < 300; i++) {
float contX = (float) (Math.random() * getWidth());
float contY = (float) (Math.random() * getHeight());
path.lineTo(contX, contY);
}
canvas.drawPath(path, paint);
}
}
package org.cenkhor.graphicex;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
public class GraphicExViewRect extends View {
public GraphicExViewRect(Context context) {
super(context);
setBackgroundColor(Color.rgb(255, 255, 255));
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setColor(Color.rgb(0, 0, 0));
for (int i = 0; i <= getWidth(); i += 20) {
for (int j = 0; j <= getHeight(); j += 20) {
if (Math.random() > 0.5) {
paint.setStyle(Paint.Style.STROKE);
} else {
paint.setStyle(Paint.Style.FILL);
}
if (Math.random() > 0.5) {
canvas.drawRect(new Rect(i + 3, j + 3, i + 14, j + 14),
paint);
} else {
canvas.drawRoundRect(
new RectF(i + 3, j + 3, i + 14, j + 14), 2, 2,
paint);
}
}
}
}
}
package org.cenkhor.graphicex;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;
public class GraphicExViewCircle extends View {
public GraphicExViewCircle(Context context) {
super(context);
setBackgroundColor(Color.rgb(255, 255, 255));
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setColor(Color.rgb(0, 0, 0));
for (int i = 0; i <= getWidth(); i += 20) {
for (int j = 0; j <= getHeight(); j += 20) {
if (Math.random() > 0.5) {
paint.setStyle(Paint.Style.STROKE);
} else {
paint.setStyle(Paint.Style.FILL);
}
canvas.drawCircle(i+10, j+10, 8, paint);
}
}
}
}
package org.cenkhor.graphicex;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.View;
public class GraphicExViewTriangle extends View {
public GraphicExViewTriangle(Context context) {
super(context);
setBackgroundColor(Color.rgb(255, 255, 255));
}
protected void onDraw(android.graphics.Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeWidth(1);
paint.setColor(Color.rgb(0, 0, 0));
Path path = new Path();
for (int i = 0; i <= getWidth(); i += 20) {
for (int j = 0; j <= getHeight(); j += 20) {
if (Math.random() > 0.5) {
paint.setStyle(Paint.Style.STROKE);
} else {
paint.setStyle(Paint.Style.FILL);
}
int r = (int) (Math.random() * 4);
switch (r) {
case 0:
path.moveTo(i + 2, j + 2);
path.lineTo(i + 14, j + 14);
path.lineTo(i + 14, j + 2);
path.lineTo(i + 2, j + 2);
break;
case 1:
path.moveTo(i + 2, j + 2);
path.lineTo(i + 14, j + 14);
path.lineTo(i + 2, j + 14);
path.lineTo(i + 2, j + 2);
break;
case 2:
path.moveTo(i + 14, j + 14);
path.lineTo(i + 14, j + 2);
path.lineTo(i + 2, j + 14);
path.lineTo(i + 14, j + 14);
break;
case 3:
path.moveTo(i + 2, j + 2);
path.lineTo(i + 2, j + 14);
path.lineTo(i + 14, j + 2);
path.lineTo(i + 2, j + 2);
break;
default:
break;
}
canvas.drawPath(path, paint);
}
}
}
}