P65 ボール転がし

Android開発に関するメモランダム

カテゴリー: Androidプログラミング入門 - 独りで学べるスマホアプリの作り方 -  閲覧数:310 配信日:2018-05-14 13:12


コード


▼/sample3/MainActivity.java
package biz.answerlead.sample3;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/** メイン画面 */
public class MainActivity extends AppCompatActivity implements SensorEventListener, SurfaceHolder.Callback {

/** ファイル選択インテント用リクエストコード */
private static final int FILE_SELECT_CODE = 1;

/** メニュー項目用ID */
private static final int MENU_SELECT_PICTURE = 1;

/** 画像読み込みサイズ */
private static final int PICTURE_SIZE = 300;

/** 1ループの時間 */
private static final int LOOP_DELAY = 20;

/** 減速率 */
private static final float SPEED_DECREASE = 0.97f;

/** 描画領域のビュー */
SurfaceView screen;

/** 描画領域管理オブジェクト */
SurfaceHolder screen_holder;

/** ボール画像 */
Bitmap ball_image;

/** ボール座標 */
float ball_x, ball_y;

/** ボール速度 */
float vx, vy;

/** ボール加速度 */
float ax, ay;

/** タイマー処理用ハンドラ */
Handler handler = new Handler();

// センサマネージャを取得
SensorManager sensor_manager;

/** カメラ撮影用一時ファイル */
File temp_file;

/** 初期化処理 */
@Override
protected void onCreate(Bundle savedInstanceState) {

// 用意された初期化処理
super.onCreate(savedInstanceState);

// 画面を設定
setContentView(R.layout.activity_main);

// SurfaceViewからSurfaceHolderを取得し、コールバックを設定する
screen = (SurfaceView) findViewById(R.id.Screen);
screen.getHolder().addCallback(this);

// センサマネージャを取得
sensor_manager = (SensorManager) getSystemService(SENSOR_SERVICE);

// 繰り返し処理
timer_event.run();

// 一時ファイルパスを作成
temp_file = new File(getExternalCacheDir(), "temp.jpg");

// 画像選択処理
selectPicture();
}

/** 画像選択イベント */
private void selectPicture() {

// 画像ファイル選択Intentを作成
Intent intent_file = new Intent(Intent.ACTION_GET_CONTENT);
intent_file.setType("image/*");

// カメラ起動Intentを作成
Intent intent_camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent_camera.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(temp_file));

// カメラ起動Intentとファイル選択Intentを結合
Intent intent = Intent.createChooser(intent_camera, getString(R.string.select_picture));
intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{intent_file});

// Intentを戻り値有りで発行(リクエストコードに「FILE_SELECT_CODE」を指定)
startActivityForResult(intent, FILE_SELECT_CODE);
}

/** Activityの戻り値を取得するイベント */
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

// リクエストコードが「FILE_SELECT_CODE」のイベントであれば
if (requestCode == FILE_SELECT_CODE) {

// 結果がOKであれば
if (resultCode == RESULT_OK) {

// 読込みURI
Uri uri;

// data.getDataの存在チェック
if (data != null && data.getData() != null) {

// data.getDataが空でなければ(ファイル選択)そのURIから読込み
uri = data.getData();
} else {

// data.getDataが空の場合(カメラ)、一時ファイルを指すURIを使用
uri = Uri.fromFile(temp_file);
}

// URIから画像を読込み
Bitmap bitmap = loadBitmap(uri);

// 一時ファイルを削除
if (temp_file.exists() && !temp_file.delete()) {
Log.e("ERROR", "Cannot delete file.");
}

// Bitmapが空でなければ
if (bitmap != null) {

// 丸型に切り出してボール画像とする
ball_image = getCircularImage(bitmap);
} else {

// 画像読み込みが失敗したことをToastで表示する
Toast.makeText(this, getString(R.string.load_failed), Toast.LENGTH_SHORT).show();
}
} else {

// 画像読み込みがキャンセルされたことをToastで表示する
Toast.makeText(this, getString(R.string.load_canceled), Toast.LENGTH_SHORT).show();
}
}
}

/** ファイルから画像を読み込む */
private Bitmap loadBitmap(Uri uri) {

try {
// URIから入力ストリームを取得
InputStream input = openInputStreamFromUri(uri);

// 画像サイズを取得するOptionを作成
BitmapFactory.Options get_size_option = new BitmapFactory.Options();

// データは読み込まず、画像サイズのみ取得するよう設定
get_size_option.inJustDecodeBounds = true;

// 画像サイズを取得(get_size_option変数のメンバ(outWidth,outHeight)に格納)
BitmapFactory.decodeStream(input, null, get_size_option);

// 入力ストリームを一旦閉じる
input.close();

// 短い方の辺を取得
int short_side = Math.min(get_size_option.outWidth, get_size_option.outHeight);

// 読込みたいサイズとの比率を取得(最低1)
int ratio = Math.max(short_side / PICTURE_SIZE, 1);

// 再度入力ストリームを取得
input = openInputStreamFromUri(uri);

// 実際に画像データを取得するためのOption
BitmapFactory.Options options = new BitmapFactory.Options();

// 読み込むデータのサンプリングの間隔を指定(読込みたいサイズの4倍なら、読み込むのは4分の1でよいため)
// ※ただし、実際は2の累乗数に丸められる。5~7を指定しても、4と同じ扱いとなる。
options.inSampleSize = ratio;

// 画像データを取得
Bitmap bitmap = BitmapFactory.decodeStream(input, null, options);

// 入力ストリームを閉じる
input.close();

// 再度入力ストリームを取得
input = openInputStreamFromUri(uri);

// 画像のExif情報を取得して、回転が必要かチェックする
// Orientation(向き)を示す値を取得する
int orientation = OrientationReader.get(input);

// 画像の向きが、通常か不明でなければ
if (orientation != ExifInterface.ORIENTATION_NORMAL && orientation != ExifInterface.ORIENTATION_UNDEFINED) {

// 回転・反転用Matrix
Matrix mat = new Matrix();

// 要左右反転の向きなら、左右反転を行う
switch (orientation) {
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
case ExifInterface.ORIENTATION_TRANSPOSE:
case ExifInterface.ORIENTATION_TRANSVERSE:
mat.postScale(-1, 1);
break;
}

// 向きに応じて回転
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
mat.postRotate(270);
break;

case ExifInterface.ORIENTATION_ROTATE_180:
case ExifInterface.ORIENTATION_TRANSPOSE:
mat.postRotate(180);
break;

case ExifInterface.ORIENTATION_ROTATE_90:
case ExifInterface.ORIENTATION_TRANSVERSE:
mat.postRotate(90);
break;
}

// Matrixを使用して新しいBitmapを作成
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), mat, true);
}

// bitmapを返却
return bitmap;

} catch (Exception e) {
e.printStackTrace();
}

return null;
}

/** URIからファイルパスを取得 */
private InputStream openInputStreamFromUri(Uri uri) throws IOException {

// 選択結果のURIをデバッグログに表示
Log.d("DEBUG", "Selected File URI = " + uri.toString());

// URIのスキームをチェック
switch (uri.getScheme()) {

// コンテンツの場合はコンテンツプロバイダを通してInputStreamを返す
case "content":
return getContentResolver().openInputStream(uri);

// ファイルの場合はgetPathでファイルパスが得られるので、FileInputStreamを返す
case "file":
return new FileInputStream(uri.getPath());

// その他はサポートしないものとして例外を発生
default:
throw new IOException("Not support URI.");
}
}

/** Bitmapを円形の画像に切り出す */
private Bitmap getCircularImage(Bitmap original) {

// ベースとなるBitmapを作成
Bitmap bitmap = Bitmap.createBitmap(PICTURE_SIZE, PICTURE_SIZE, Config.ARGB_8888);

// 中心座標をあらかじめ計算
float half = PICTURE_SIZE / 2.0f;

// 描画用のキャンバスを作成
Canvas canvas = new Canvas(bitmap);

// 白でアンチエイリアス有りのPaint設定
Paint white = new Paint();
white.setColor(Color.WHITE);
white.setAntiAlias(true);

// Bitmapに白で塗りつぶした円を描く
canvas.drawCircle(half, half, half, white);

// 選択画像描画用のPaint設定
// Xfermodeに「SRC_ATOP」を指定すると、現在透明な部分には描画されないようマスク出来る。
// つまり、上で描画した円の外には描画されない設定のPaintとなる。
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP));

// 画像を中央に表示するためのMatrix
float ratio = (float) PICTURE_SIZE / Math.min(original.getWidth(), original.getHeight());
Matrix matrix = new Matrix();
matrix.postTranslate(
-(original.getWidth() - PICTURE_SIZE) / 2,
-(original.getHeight() - PICTURE_SIZE) / 2
);
matrix.postScale(ratio, ratio, half, half);

// 選択画像を描画
canvas.drawBitmap(original, matrix, paint);

return bitmap;
}

/** メニュー項目作成 */
@Override
public boolean onCreateOptionsMenu(Menu menu) {

/** 「画像選択」の項目を追加 */
menu.add(Menu.NONE, MENU_SELECT_PICTURE, Menu.NONE, R.string.select_picture);

return super.onCreateOptionsMenu(menu);
}

/** メニュー項目選択イベント */
@Override
public boolean onOptionsItemSelected(MenuItem item) {

// 「画像選択」が選択されていたら画像選択処理を呼び出す
if (item.getItemId() == MENU_SELECT_PICTURE) {
selectPicture();
}

return super.onOptionsItemSelected(item);
}

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

// 加速度センサの一覧を取得(通常は1つ)
Sensor sensor = sensor_manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

// 加速度センサが存在する場合は、加速度センサの値の変化を検知するようリスナを設定する
if (sensor != null) {
sensor_manager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}

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

// 加速度センサのリスナを削除する
sensor_manager.unregisterListener(this);
}

/** センサの値が変化した際のイベント */
@Override
public void onSensorChanged(SensorEvent event) {

// 取得したイベントが加速度センサのイベントならば
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

// 取得した加速度をボールの加速度とする
ax = -event.values[0];
ay = event.values[1];

// 加速度の値をデバッグログに表示する
//Log.d("DEBUG", String.format("X=%8.5f, Y=%8.5f", ax, ay));
}
}

/** センサの精度が変化した際のイベント */
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// 処理なし
}

/** サーフェスビューの準備が完了した際のイベント */
@Override
public void surfaceCreated(SurfaceHolder holder) {
screen_holder = holder;
}

/** サーフェスビューのサイズが変化した際のイベント */
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 処理なし
}

/** サーフェスビューが破棄された際のイベント */
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
screen_holder = null;
}

/** タイマーイベント */
private Runnable timer_event = new Runnable() {
public void run() {

// 描画領域とボール画像が準備できているなら
if (screen_holder != null) {

// 描画用のキャンバスを取得
Canvas canvas = screen_holder.lockCanvas();

// 初めにキャンバスを白でクリア
canvas.drawColor(Color.WHITE);

// ボール画像の読み込みが完了していたら
if (ball_image != null) {

// 速度に加速度を加算
vx += ax;
vy += ay;

// 座標に速度を加算
ball_x += vx;
ball_y += vy;

// 速度を落とす
vx *= SPEED_DECREASE;
vy *= SPEED_DECREASE;

// ボール可動範囲計算(画面サイズからボールのサイズを引いた分が可動範囲)
float wx = canvas.getWidth() - ball_image.getWidth();
float wy = canvas.getHeight() - ball_image.getHeight();

// 端まで到達した場合は速度を反転
if ((ball_x < 0) || (ball_x > wx)) {
vx = -vx;
ball_x = Math.min(Math.max(0, ball_x), wx);
}
if ((ball_y < 0) || (ball_y > wy)) {
vy = -vy;
ball_y = Math.min(Math.max(0, ball_y), wy);
}

// キャンバスにボールを描画
canvas.drawBitmap(ball_image, ball_x, ball_y, null);
}

// キャンバスを反映
screen_holder.unlockCanvasAndPost(canvas);
}

// LOOP_DELAY(20)ms後に再度実行
handler.postDelayed(this, LOOP_DELAY);
}
};
}


▼/sample3/OrientationReader.java
package biz.answerlead.sample3;

import java.io.IOException;
import java.io.InputStream;

/** Orientation読込みクラス */
public class OrientationReader {

/** Exifの入ったデータかチェックするための値 */
private static final Byte[] EXIF_HEADER = {(byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE1, null, null, 'E',
'x', 'i', 'f', 0, 0};

/** Orientationタグの値 */
private static final short TAG_ORIENTATION = 274;

/** InputStreamからOrientationを取得する */
public static int get(InputStream is) {

try {

// Exifが存在するか判断するために12バイト読み込む
byte[] exif_header = new byte[12];
if (is.read(exif_header) == exif_header.length) {

// ヘッダの内容を確認してExifが存在するかチェック
boolean is_exif = true;
for (int i = 0; i < EXIF_HEADER.length; i++) {
if (EXIF_HEADER[i] != null && EXIF_HEADER[i] != exif_header[i]) {
is_exif = false;
break;
}
}

// Exifが存在するなら
if (is_exif) {

// TIFFヘッダ(8バイト)を読み込む
byte[] tiff_header = new byte[8];
if (is.read(tiff_header) == tiff_header.length) {

// ビッグエンディアン(MM)かリトルエンディアン(II)か読み込む
boolean is_little_endian = tiff_header[0] == 'I' && tiff_header[1] == 'I';

// タグ数(2バイト)を読み込む
short tag_num = getShort(is.read(), is.read(), is_little_endian);

// タグ数分ループ
while (tag_num-- > 0) {

// Exifタグ(12バイト)を読み込む
byte[] body = new byte[12];
if (is.read(body) == body.length) {

// 先頭2バイトからタグ種別を取得
int tag = getShort(body[0], body[1], is_little_endian);

// 種別がOrientationなら
if (tag == TAG_ORIENTATION) {

// 値の型を取得
int type = getShort(body[2], body[3], is_little_endian);

// 型別に値を読んで返す
switch (type) {
case 1:
case 2:
return body[8];
case 3:
return getShort(body[8], body[9], is_little_endian);
case 4:
case 9:
return getInt(body[8], body[9], body[10], body[11], is_little_endian);
}

}
}
}
}
}
}

} catch (Exception e) {
// IGNORE
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return 0;
}

/** 2バイトをshortに変換して返す */
private static short getShort(int a, int b, boolean reverse) {
if (a < 0 || b < 0)
throw new IllegalArgumentException();

return (short) (reverse ? (b << 8 | a) : (a << 8 | b));
}

/** 4バイトをintに変換して返す */
private static int getInt(int a, int b, int c, int d, boolean reverse) {
if (a < 0 || b < 0 || c < 0 || d < 0)
throw new IllegalArgumentException();

return reverse ? (d << 24 | c << 16 | b << 8 | a) : (a << 24 | b << 16 | c << 8 | d);
}
}


週間人気ページランキング / 2-16 → 2-22
順位 ページタイトル抜粋 アクセス数
アクセスが、ありませんでした! 0
2025/2/23 1:01 更新
指定期間人気ページランキング / 1970-1-1 → 2025-2-22
順位 ページタイトル抜粋 アクセス数
アクセスが、ありませんでした! 0
2025/2/23 1:01 更新