画像表示
Androidの仕様として(というかXML側の仕様のようだが)、画像ファイルに大文字は使えないようだ。Eclipseからファイルを追加した時に、[2014-07-23 14:34:35 - ImageEx] res\drawable-nodpi\Android.png: 無効なファイル名: must contain only [a-z0-9_.]
みたいな感じでエラーが出る。
上記みたいな感じで、res/drawable-nodpi/android.png を配置した。
package org.cenkhor.imageex;
import android.app.Activity;
import android.os.Bundle;
public class ImageEx extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new ImageExView(this));
}
}
package org.cenkhor.imageex;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
public class ImageExView extends View {
private Bitmap image;
public ImageExView(Context context) {
super(context);
setBackgroundColor(Color.rgb(255, 255, 255));
// リソースの読み込み
Resources r = context.getResources();
image = BitmapFactory.decodeResource(r, R.drawable.android);
}
@Override
protected void onDraw(Canvas canvas) {
int w = image.getWidth();
int h = image.getHeight();
float percent = 0.75f;
int centerX = getWidth() /2;
int centerY = getHeight() /2;
int startX = (int)(centerX - w*percent/2);
int startY = (int)(centerY - h*percent/2);
//元画像としたい矩形ってことだと思う
Rect src = new Rect(0,0,w,h);
//リサイズ後に指定したい矩形
Rect dst = new Rect(startX, startY,(int)(w * percent),h);
canvas.drawBitmap(image, src, dst,null);
Log.e("",image.getWidth()+"+"+ image.getHeight());
}
}
ログで画像の幅と高さを表示してみたが、元画像のサイズになっていたので、調べてみたところ、Androidで大きめの画像をリサイズ処理をするのは一手間かかるかんじがした。でも、Nexus 5とかかなりウィンドウサイズが大きかったりするんだけど、その辺どうやって対応してるんだろう。興味がわいて、ちょうど9-patchが書籍のコラムに載ってたので、Android SDKに同梱されてるアプリケーションで試してみた。これはこれ用のボタン画像作らないとダメそう。あと使いづらさがすごい。
サーフェイスビューの利用
リサイズしない画像を貼り付けるとでかい。
package org.cenkhor.surfaceex;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.view.Window;
public class SurfaceEx extends Activity {
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
//ウィンドウの表示形式を切り替えるらしいのだけど、切り替わらない
//OPAQUE:不透明,TRANSLUCENT:半透明,TRANSPARENT:透過
getWindow().setFormat(PixelFormat.OPAQUE);
setContentView(new SurfaceViewView(this));
}
}
package org.cenkhor.surfaceex;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class SurfaceViewView extends SurfaceView implements
SurfaceHolder.Callback, Runnable {
// サーフェスホルダー
private SurfaceHolder holder;
// スレッド
private Thread thread;
// イメージ
private Bitmap image;
// 座標、速度
private int px =0;
private int py = 0;
private int vx = 30;
private int vy = 30;
public SurfaceViewView(Context context) {
super(context);
// 画像の読み込み(danbo.pngをres/drawable-nodpiフォルダに追加)
Resources r = getResources();
image = BitmapFactory.decodeResource(r, R.drawable.danbo);
// サーフェスホルダーの生成
holder = getHolder();
holder.addCallback(this);
holder.setFixedSize(480,762);
}
@Override
public void run() {
Canvas canvas;
while (thread != null) {
// ダブルバッファリングのロック処理
canvas = holder.lockCanvas();
canvas.drawColor(Color.WHITE);
//画像の描画、画像サイズに合わせて調整
canvas.drawBitmap(image, px - 25, py - 24, null);
//ダブルバッファリングのアンロック処理
holder.unlockCanvasAndPost(canvas);
if (px < 0 || 480 < px)
vx = -vx;
if (py < 0 || 762 < py)
vy = -vy;
px += vx;
py += vy;
try {
Thread.sleep(5);
} catch (Exception e) {
System.out.println(e);
}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//サーフェスが生成されたタイミングで呼び出されるメソッド
//スレッドの開始
thread = new Thread(this);
thread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
//サーフェスが破棄されたタイミングで呼び出されるメソッド
//スレッドの破棄
thread = null;
}
}
サーフェイスビューはUIから独立して描画を行うビューのことで、ゲームやカメラプレビューなど高速な連続描画で使用するらしい。利用する場合は、ViewではなくSurfaceViewクラスを継承し、更に、サーフェイスビューの状態変化時のイベント処理を行う、SurfaceHolder.Callbackインターフェイスを実装する。サーフェイスビューの設定は、SurfaceHolderを取得して行う。Thread#startメソッドで、Runnableインターフェイスを実装したrunメソッドが呼び出され定期的な再描画を行う。アニメーション処理については他でもやっているので流す。
ダブルバッファリングが重要な感じだ。背景を白くして、座標移動した画像を表示する処理をまとめて行っている。そういえばスレッドについては読み終えてなかったので、時間を見つけてやる。
テキストビューとイメージビュー
package org.cenkhor.textandimageviewex;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class TextAndImageViewEx extends Activity {
private final static int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// レイアウトの生成
LinearLayout layout = new LinearLayout(this);
layout.setBackgroundColor(Color.rgb(255, 255, 255));
layout.setOrientation(LinearLayout.VERTICAL);
setContentView(layout);
// テキストビューの生成
TextView textView = new TextView(this);
textView.setText("Hello World!\nこれはTextViewのサンプルです。");
textView.setTextColor(Color.rgb(122, 122, 122));
// コンポーネントのサイズの指定
textView.setLayoutParams(new LinearLayout.LayoutParams(WC, WC));
// レイアウトのコンポーネントへの追加
layout.addView(textView);
// イメージビューの生成
ImageView imageView = new ImageView(this);
Bitmap image = BitmapFactory.decodeResource(getResources(),
R.drawable.takawo);
imageView.setImageBitmap(image);
// コンポーネントのサイズの指定
imageView.setLayoutParams(new LinearLayout.LayoutParams(WC, WC));
// レイアウトのコンポーネントへの追加
layout.addView(imageView);
}
}
これまでビューとして、View、もしくはSurfaceViewクラスを利用してきたが、今回はボタンやテキストボックスなどのコンポーネントを配置するためのビューである、レイアウトを使用している。
レイアウト
クラス | 機能 |
---|---|
LinearLayout | コンポーネントを直線上に整列 |
RelativeLayout | コンポーネントを相対位置(○○の上など)で指定 |
TableLayout | コンポーネントをテーブル状に整列 |
FrameLayout | コンポーネントをフレーム状に重ねて整列 |
AbsoluteLayout | コンポーネントをXY座標で指定 |
とりあえず、全部のレイアウト関連クラスのコードを順に書経してみる。
LinearLayout
package org.cenkhor.linearlayoutex;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Window;
import android.widget.Button;
import android.widget.LinearLayout;
public class LinearLayoutEx extends Activity {
private final static int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
private final static int MP = LinearLayout.LayoutParams.MATCH_PARENT;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// レイアウトの生成
LinearLayout layout = new LinearLayout(this);
layout.setBackgroundColor(Color.rgb(255, 255, 255));
//レイアウトの方向
layout.setOrientation(LinearLayout.VERTICAL);
//レイアウトの配置位置(align的な)今回は右下に
layout.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
setContentView(layout);
// ボタンの幅を配列で保持
int[] width = new int[] { 200, 200, 200, 200, 200, MP };
// ループ処理でボタンを生成、レイアウトに追加
for (int i = 0; i < 6; i++) {
Button button = new Button(this);
button.setText("[ " + i + " ] ");
button.setLayoutParams(new LinearLayout.LayoutParams(width[i], WC));
layout.addView(button);
}
}
}
RelativeLayout
package org.cenkhor.relativelayoutex;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Window;
import android.widget.Button;
import android.widget.RelativeLayout;
public class RelativeLayoutEx extends Activity {
private final static int WC = RelativeLayout.LayoutParams.WRAP_CONTENT;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
RelativeLayout layout = new RelativeLayout(this);
layout.setBackgroundColor(Color.rgb(255,255, 255));
setContentView(layout);
Button button1 = new Button(this);
button1.setId(1);
button1.setText("(1)");
button1.setLayoutParams(new RelativeLayout.LayoutParams(200,WC));
layout.addView(button1);
Button button2 = new Button(this);
button2.setId(2);
button2.setText("(2)");
RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(200,WC);
params2.addRule(RelativeLayout.BELOW,1);
button2.setLayoutParams(params2);
layout.addView(button2);
Button button3 = new Button(this);
button3.setId(3);
button3.setText("(3)");
RelativeLayout.LayoutParams params3 = new RelativeLayout.LayoutParams(200,WC);
params3.addRule(RelativeLayout.BELOW,1);
params3.addRule(RelativeLayout.RIGHT_OF,2);
button3.setLayoutParams(params3);
layout.addView(button3);
Button button4 = new Button(this);
button4.setId(4);
button4.setText("(4)");
RelativeLayout.LayoutParams params4 = new RelativeLayout.LayoutParams(200,WC);
params4.addRule(RelativeLayout.BELOW,3);
params4.addRule(RelativeLayout.RIGHT_OF,2);
button4.setLayoutParams(params4);
layout.addView(button4);
Button button5 = new Button(this);
button5.setId(5);
button5.setText("(5)");
RelativeLayout.LayoutParams params5 = new RelativeLayout.LayoutParams(200,WC);
params5.addRule(RelativeLayout.BELOW,3);
params5.addRule(RelativeLayout.RIGHT_OF,4);
button5.setLayoutParams(params5);
layout.addView(button5);
Button button6 = new Button(this);
button6.setId(6);
button6.setText("(6)");
RelativeLayout.LayoutParams params6 = new RelativeLayout.LayoutParams(200,WC);
params6.addRule(RelativeLayout.BELOW,3);
params6.addRule(RelativeLayout.RIGHT_OF,5);
button6.setLayoutParams(params6);
layout.addView(button6);
}
}
TableLayout
package org.cenkhor.tablelayoutex;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Window;
import android.widget.Button;
import android.widget.TableLayout;
import android.widget.TableRow;
public class TableLayoutEx extends Activity {
private final static int WC = TableLayout.LayoutParams.WRAP_CONTENT;
private final static int MP = TableLayout.LayoutParams.MATCH_PARENT;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
TableLayout layout = new TableLayout(this);
layout.setBackgroundColor(Color.rgb(255, 255, 255));
layout.setGravity(Gravity.CENTER);
setContentView(layout);
for (int i = 0; i < 6; i++) {
TableRow row = new TableRow(this);
row.setLayoutParams(new TableLayout.LayoutParams(WC, WC));
row.setGravity(Gravity.CENTER);
layout.addView(row);
for (int j = 0; j < 6; j++) {
Button button = new Button(this);
button.setText("(" + i + " ," + j + ")");
row.addView(button);
}
}
}
}
FrameLayout
package org.cenkhor.framelayoutex;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Gravity;
import android.view.Window;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class FrameLayoutEx extends Activity {
private final static int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
private final static int MP = LinearLayout.LayoutParams.MATCH_PARENT;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// レイアウトの生成
FrameLayout layout = new FrameLayout(this);
layout.setBackgroundColor(Color.rgb(255, 255, 255));
setContentView(layout);
// ヘッダの生成
LinearLayout header = new LinearLayout(this);
header.setBackgroundColor(Color.TRANSPARENT);
header.setGravity(Gravity.TOP);
Button btnHeader = new Button(this);
btnHeader.setText("Header");
btnHeader.setLayoutParams(new LinearLayout.LayoutParams(MP, WC));
header.addView(btnHeader);
layout.addView(header);
// フッタの生成
LinearLayout footer = new LinearLayout(this);
footer.setBackgroundColor(Color.TRANSPARENT);
footer.setGravity(Gravity.BOTTOM);
Button btnFooter = new Button(this);
btnFooter.setText("Footer");
btnFooter.setLayoutParams(new LinearLayout.LayoutParams(MP, WC));
footer.addView(btnFooter);
layout.addView(footer);
}
}
AbsoluteLayout
package org.cenkhor.absolutelayoutex;
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Window;
import android.widget.AbsoluteLayout;
import android.widget.Button;
public class AbsoluteLayoutEx extends Activity {
private final static int WC = AbsoluteLayout.LayoutParams.WRAP_CONTENT;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
AbsoluteLayout layout = new AbsoluteLayout(this);
layout.setBackgroundColor(Color.rgb(255, 255, 255));
setContentView(layout);
Button button1 = new Button(this);
button1.setText("(20,40) WCxWC");
//AbsoluteLayout.layoutParamsはwidth,height,x,yを引数に取れる
button1.setLayoutParams(new AbsoluteLayout.LayoutParams(WC,WC,20,40));
layout.addView(button1);
Button button2 = new Button(this);
button2.setText("(20,200) 300x200");
button2.setLayoutParams(new AbsoluteLayout.LayoutParams(400,400,400,800));
layout.addView(button2);
}
}
AbsoluteLayoutは、「型 AbsoluteLayout は使用すべきではありません」という警告表示がでるので、現行のバージョン以降は推奨されていないようだ。
その他
Android Screencast
せっかくアニメーションが実行できたので、Androidの実機での実行状態を録画したいと思ったので、何かいいものはないかなと思ったところ、下記記事からAndroid Screencastを見つけて、手順に沿って実行などしてみた。が、表示もレイテンシーが10秒ちかくあって、録画についても今のところ実用性はなさそうな感じ。jnlpの実行あたりはググりながら、エラー画面で出てきたURLをJavaの構成からアクセスを許可するURLに追加したところできた。その辺りは勉強になったが、当たり前になるとUXとかUIに関する感度が低下しそうなのでこういう手間に慣れたくないと思った。
androidscreencast - Desktop app to control an android device remotely - Google Project Hosting
appcompat_v7
Eclipseのプロジェクト・エクスプローラーに見覚えのない「appcompat_v7」というプロジェクトが入っていたので、何かと思い、調べてみた。SDKによって、自動生成されるライブラリ(しかもバージョンごとに増えていく)らしい。軽く削除してみたら結構面倒なことになった(res/valuesの中のxmlファイルでエラーが出る)ので、インポートして、プロジェクトのクリーンを行ったら復帰したので、メモしておく。