素人のアンドロイドアプリ開発日記

描画のコンテンツ

androidのbitmapについてまとめ

2011.12.06

Bitmapを使う時は結構あったのですが、まとめてなかったので少しBitmapの利用の仕方をまとめてみます。

 

はじめにbitmapの取得の方法をいくつか、うしろの方で、bitmapの使い方をいくつか紹介してます。

続きを読む…

カテゴリー:描画

SurfaceViewを使ってみる。

2011.04.19

今まで描画に関してはViewを利用していましたが、今回はSurfaceViewを利用してみたいと思います。

SurfaceViewに関しては「ゲーム向き」と言われますが、Viewに比べて描画の動作を軽量化できる。と言った点でゲーム向きのようですが、描画自体は若干粗くなるようです。

surfaceを利用する場合はいくつかの関数で構成されていますが、ある程度のテンプレートを示させていただきます。

public class MainView extends SurfaceView implements SurfaceHolder.Callback, Runnable{
 private SurfaceHolder holder;
 private Thread thread;

 public MainView(Context context){
 super(context);
 holder = null;
 thread = null;
 getHolder().addCallback(this);
 }

 public void surfaceCreated(SurfaceHolder holder){
 this.holder = holder;
 thread = new Thread(this);
 }

 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
 if(thread != null ){
 thread.start();
 }
 }

 public void surfaceDestroyed(SurfaceHolder holder){
 thread = null;
 }

 public void run(){
 Canvas canvas = holder.lockCanvas();
 //canvas.draw(***);
 holder.unlockCanvasAndPost(canvas);

 }
}

上記が基本の形です。

Threadを作成してその中で描画を行う形式です。繰り返し利用をしない場合はthreadを作成しなくても問題ありません。

ここでThreadを一フレームと考えて、フレーム毎の動作が作成できます。

動作の処理によっては遅くなったり、早く見えたりしてしまいます。さまざまな機種で同様の動作を行えるようにする為に、下記の形で「時間」を用いる事をおすすめします。

private long myTime = 0;

を作成。

mTime = System.currentTimeMillis();

surfaceChangedで実行をします。※これを設定しないと最初の一回の値が変になるので注意です。

int Interval = (System.currentTimeMillis()-myTime);
myTime = System.currentTimeMillis();

をThread内で設定をする事で、前回のthread実行からどれくらい時間がかかったかが分かります。

このIntervalを利用して、

1秒間に50だけ動かしたい対象があった場合に50/1000*Intervalとする事で時間に対応した位置にオブジェクトの配置をする事ができ処理が重くなった場合にも、さほど気にする事なく動作(移動がフレーム落ち感はあっても目的地までは通常の時間でたどり着きます。)を行えます

また、SurfaceViewで物体を動かす際には多くの場合に

canvas.drawColor(Color.WHITE);

などで、毎回前回の描写をリセットしてから、描写する事が多いです。

また、この部分にalphaをつけると残像を残した状態で作成ができます。

以上です。

カテゴリー:描画

お絵かきアプリtips

2011.04.15

前回までで、保存ができる落書きができましたが、

次の問題が出てしまいます。
・端末の回転で、初期化されてしまう。
・何もかかないでいると、画面がスリープする。

端末の回転をしない為には

参照:SE奮闘記さん

android:screenOrientation=""

で変更を行えるようです

Android Wiki*さんに方向の説明があるのですが利用できる定数は

unspecified:デフォルト
portrait:縦固定
landscape:横固定

でセンサーによっての取得もあるようです。

下記のような形で、スクリプトでも方向を制御できるようです。

Configuration config = getResources().getConfiguration();
if(config.orientation == Configuration.ORIENTATION_LANDSCAPE) { 
 this.setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
}

個人的な意見だと、アプリを通して同じ方向で動作できたほうが使い易く、

必要性はなければ縦で作成をした方が無難だと思います。

 

次にスリープの問題です。スリープをしないような設定をしてしまうと、電源の消費の問題であまり好まれませんが、書いている時の考え中などでは、どうしてもそのままにしておきたいものです。

画面をスリープ状態にさせないためにはで、

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

で常にスクリーンをONに出来るようでした。

getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

で上のスクリーンONを打ち消す事ができるようなので、一定以上の時間が経った場合にはスクリーンONを削除した方がよいかと思います。

カテゴリー:描画

お絵かきアプリで書いた絵を保存する。

2011.04.15

次に前回までに利用したお絵かきアプリで書いた画像を保存してみたいと思います。

今回はviewを別のクラスに分けているので、クラス間の値の引渡しなども重要です。

 

まず、最初にメニューを作成して、そのメニューに関数を割り当てましょう。

作成をするメニューはページの初期化と、保存と、終了です。

まずstring.xmlに値を作成します。

string.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
 <string name="hello">Hello World, DrawBm!</string>
 <string name="app_name">drawbm</string>
 <string name="clean">DELETE</string>
 <string name="save">SAVE</string>
 <string name="finish">FINISH</string>
</resources>

次にmenu.xmlを作成してメニューを作ります。

<?xml version="1.0" encoding="utf-8"?>
<menu
 xmlns:android="http://schemas.android.com/apk/res/android">
 <item 
 android:id="@+id/item1"
 android:title="@string/clean"
 ></item>
 <item 
 android:id="@+id/item2"
 android:title="@string/save"
 ></item>
 <item 
 android:id="@+id/item3"
 android:title="@string/finish"
 ></item>
</menu>

メニューの生成はActivityで作成します。

package in.andante.drawbm;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class DrawBm extends Activity {
 penView penview;
 
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 penview= new penView(this);
 setContentView(penview);
 }
 /** メニューの生成イベント */
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
 super.onCreateOptionsMenu(menu);
 getMenuInflater().inflate(R.menu.menu,menu);  
 return true;
 }
 /** メニューがクリックされた時のイベント */
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
 switch ( item.getItemId() ) {
 case R.id.item1:
 penview.clearDrawList(); 
 break;
 case R.id.item2:
 penview.saveToFile();
 break;
 case R.id.item3:
 finish();
 break;
 }
 return true;
 }

}

で、ここで、penViewで存在していない関数ですがclearDrawList()、saveToFile()を想定して作成をしています。

保存を行う為にはbitmapに一度書き写す必要があるので、bitmapを取り扱っていた

お絵かきアプリを作る。描画編3で作成をしていきたいと思います。

簡単に作成をしていた仕組みの説明ですが、bitmapとcanvasを作成して、指の動きに合わせてcanvasに描画をします。そのcanvasをviewに書く事で、お絵かきを表示しています。

clearDrawList()に関しては以下の形になります。

public void clearDrawList(){
 bmpCanvas.drawColor(Color.BLACK);
 invalidate();
}

ここで塗りつぶす色はキャンバスの色にします。色を塗りつぶして、bmpbmpCanvasをViewに反映する為にinvalidateを実行します(つまり、再描画)で現在の絵を一度破棄出来ます。

次にデータの保存を行います。データの保存に関してはmanifestに関しても書き込みを可能に変更します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="in.andante.drawbm"
 android:versionCode="1"
 android:versionName="1.0">
 <uses-sdk android:minSdkVersion="7" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

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

 </application>
</manifest>

sdcardの書き込み設定を確認します。

下記で、書き込みが可能であればtrue、可能でなければ、falseに変更を行います。

private boolean sdcardWriteReady(){
 String state = Environment.getExternalStorageState();
 return (Environment.MEDIA_MOUNTED.equals(state));
}

また、Viewを呼び出しの際にcontextをActivityのキャストして、保持しておきます。

private Activity _context;
public penView(Context context) {
 super(context);
 _context = (Activity)context;
}

sdcardが利用できなければ、Toastのデータで保存が出来なかった旨を表示して動作を終了します。

public void saveToFile(){
 if(!sdcardWriteReady()){
 Toast.makeText(_context, "SDCARDが認識されません。", Toast.LENGTH_SHORT).show();
 return;
 }
}

sdcardのpathは下記で取得ができます。

Environment.getExternalStorageDirectory().getPath()

なので、このアプリ専用のフォルダの名前をdrawbmとすると

File file = new File(Environment.getExternalStorageDirectory().getPath()+"/drawbm/");

で、ディレクトリを取得できます。このブログを最初に作成した段階では、このディレクトリは存在していないので、存在してない時にはこのディレクトリを作成します。

if(!file.exists()){
 file.mkdir();
}

このディレクトリのパスを保存する名前を作成して絶対パスを取得します。

String imgName = file.getAbsolutePath() + "/" + System.currentTimeMillis() +".jpg";
File saveFile = new File(imgName);

while(saveFile.exists()) {
 imgName = file.getAbsolutePath() + "/" + System.currentTimeMillis() +".jpg";
 saveFile = new File(imgName);
}

もしも同じ名前のファイルが存在する場合は回避をします。好きな名前で入れたい場合はここでdialogを出してもいいかもしれないです。

※上記では時間を利用して名前をつけているので基本的に同じ名前になる事はありません。

try {
 FileOutputStream out = new FileOutputStream(imgName);
 bmp.compress(CompressFormat.JPEG, 100, out);
 out.flush();
 out.close();
 Toast.makeText(_context, "保存されました。", Toast.LENGTH_SHORT).show();
} catch(Exception e) {
 Toast.makeText(_context, "エラーが発生しました。", Toast.LENGTH_SHORT).show();
}

これで画像の保存ができました。

penView.java

package in.andante.drawbm;

import java.io.File;
import java.io.FileOutputStream;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Environment;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

public class penView extends View {
 
 private float oldx = 0f;
 private float oldy = 0f;
 private Bitmap bmp = null;
 private Canvas bmpCanvas;
 private Paint paint;
 private Activity _context;
 
public penView(Context context) {
 super(context);
 _context = (Activity)context;
 paint = new Paint();
 paint.setColor(Color.MAGENTA);
 paint.setAntiAlias(true);
 paint.setStyle(Paint.Style.STROKE);
 paint.setStrokeWidth(6);
 paint.setStrokeCap(Paint.Cap.ROUND);
 paint.setStrokeJoin(Paint.Join.ROUND);
}
 
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w,h,oldw,oldh);
 bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
 bmpCanvas = new Canvas(bmp);
 }
 
 
 public void onDraw(Canvas canvas) {
 canvas.drawBitmap(bmp, 0, 0, null);
 }
 
 public boolean onTouchEvent(MotionEvent e){
 switch(e.getAction()){
 case MotionEvent.ACTION_DOWN: //最初のポイント
 oldx = e.getX();
 oldy = e.getY();
 break;
 case MotionEvent.ACTION_MOVE: //途中のポイント
 bmpCanvas.drawLine(oldx, oldy, e.getX(), e.getY(), paint);
 oldx = e.getX();
 oldy = e.getY();
 invalidate();
 break;
 default:
 break;
 }
 return true;
 }
 
 public void clearDrawList(){
 bmpCanvas.drawColor(Color.BLACK);
 invalidate();
 }
 
 public void saveToFile(){
 if(!sdcardWriteReady()){
 Toast.makeText(_context, "SDcardが認識されません。", Toast.LENGTH_SHORT).show();
 return;
 }
 
 File file = new File(Environment.getExternalStorageDirectory().getPath()+"/drawbm/");
 
 
 try{
 if(!file.exists()){
 file.mkdir();
 }
 }catch(SecurityException e){}
 
 String AttachName = file.getAbsolutePath() + "/";
 AttachName += System.currentTimeMillis()+".jpg";
 File saveFile = new File(AttachName);
 while(saveFile.exists()) {
 AttachName = file.getAbsolutePath() + "/" + System.currentTimeMillis() +".jpg";
 saveFile = new File(AttachName);
 }
 try {
 FileOutputStream out = new FileOutputStream(AttachName);
 bmp.compress(CompressFormat.JPEG, 100, out);
 out.flush();
 out.close();
 Toast.makeText(_context, "保存されました。", Toast.LENGTH_SHORT).show();
 } catch(Exception e) {
 Toast.makeText(_context, "例外発生", Toast.LENGTH_SHORT).show();
 }
 }

 private boolean sdcardWriteReady(){
 String state = Environment.getExternalStorageState();
 return (Environment.MEDIA_MOUNTED.equals(state));
 }
}

以上です。

カテゴリー:描画

お絵かきアプリを作る。描画編3

2011.04.14

お絵かきアプリですが、前回までで、おしまいと思っていたのですが、より簡単な方法があったので、もう一度作成をしてみたいと思います。

Android で再開する Java プログラミング(10) – 手書きメモを作る

で保存の部分を確認していたところ、bitmapに直接記述を行う部分が書いてあり、その方法です。

あと、今まで描画に際してonDrawでpaintを作成していたのですが、何度も呼び出すファンクションなのでコンストラクタで作った方が良い気がするので、今回はその方法も入れてみました。

public penView(Context context) {
 super(context);
 //初期設定で作成してしまう。
 paint = new Paint();
 paint.setColor(Color.BLUE);
 paint.setAntiAlias(true);
 paint.setStyle(Paint.Style.STROKE);
 paint.setStrokeWidth(6);
 paint.setStrokeCap(Paint.Cap.ROUND);
 paint.setStrokeJoin(Paint.Join.ROUND);
}

次にBitmapを作成してみます。

protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w,h,oldw,oldh);
 bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
 bmpCanvas = new Canvas(bmp);
}

Bitmapを生成する際に第三引数にBitmap.Config.ARGB_8888と入れますが、これは32ビットのARGBデータでBitmapを作成する事を、示しています。通常androidで使用しているColorの値がこれに該当しますので、特に指定の必要がなければこの値をいれてください。

参照:グラフィックス(6)-Bitmapの描画とMatrixの操作

onSizeChangedは表示されるタイミングで呼び出され、viewの表示領域の値を取得できるので、この関数で、viewを設定します。

 

また、タッチのイベントでは、このCanvasに線を書きます。

public boolean onTouchEvent(MotionEvent e){
 switch(e.getAction()){
 case MotionEvent.ACTION_DOWN: //最初のポイント
 oldx = e.getX();
 oldy = e.getY();
 break;
 case MotionEvent.ACTION_MOVE: //途中のポイント
 bmpCanvas.drawLine(oldx, oldy, e.getX(), e.getY(), paint);
 oldx = e.getX();
 oldy = e.getY();
 invalidate();
 break;
 default:
 break;
 }
 return true;
}

Canvasのdarwのファンクションは常に上書きになるので、bmp上に線が上書きされていきます。このbmdをonDrawでViewに書き写します。

public void onDraw(Canvas canvas) {
 canvas.drawBitmap(bmp, 0, 0, null);
}

以上で簡単にですが、ビットマップを作成する事ができました。

ソースは以下です。

package in.andante.drawbm;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;

public class penView extends View {
 private float oldx = 0f;
 private float oldy = 0f;
 private Bitmap bmp = null;
 private Canvas bmpCanvas;
 private Paint paint;
 
public penView(Context context) {
 super(context);
 //初期設定で作成してしまう。
 paint = new Paint();
 paint.setColor(Color.BLUE);
 paint.setAntiAlias(true);
 paint.setStyle(Paint.Style.STROKE);
 paint.setStrokeWidth(6);
 paint.setStrokeCap(Paint.Cap.ROUND);
 paint.setStrokeJoin(Paint.Join.ROUND);
}
 
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
 super.onSizeChanged(w,h,oldw,oldh);
 bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
 bmpCanvas = new Canvas(bmp);
 }
 
 
 public void onDraw(Canvas canvas) {
 canvas.drawBitmap(bmp, 0, 0, null);
 }
 
 public boolean onTouchEvent(MotionEvent e){
 switch(e.getAction()){
 case MotionEvent.ACTION_DOWN: //最初のポイント
 oldx = e.getX();
 oldy = e.getY();
 break;
 case MotionEvent.ACTION_MOVE: //途中のポイント
 bmpCanvas.drawLine(oldx, oldy, e.getX(), e.getY(), paint);
 oldx = e.getX();
 oldy = e.getY();
 invalidate();
 break;
 default:
 break;
 }
 return true;
 }
 
}

線毎のデータを保持する必要がなければこの方法が一番シンプルな気がします。

カテゴリー:描画

公開中のアプリ、是非ダウンロードしてみてください

2chまとめのたね

RSSを利用してさまざまなブログの情報をキュレーションしてくれるアプリ

インストールする

ひらがな戦記

OPENGL ES2 を利用したカルタのソーシャルゲーム

インストールする