marunomaruno-memo

marunomaruno-memo

[Android] インベーダー・ゲームを作る (2) オブジェクト指向

2012年04月21日 | Android
[Android] インベーダー・ゲームを作る (2) オブジェクト指向
================================================================================

[Android] インベーダー・ゲームを作る (1) シンプル
http://blog.goo.ne.jp/marunomarunogoo/d/20120405
のものを、クラスを導入して作る。

ゲームの制御をするのは、CanvasView クラス。
ゲームで出てくるアイテムとしては、インベーダー、砲台、弾だが、それぞれ、Invader、
Gun、Bullet クラスとして定義する。
それぞれは、下記のインターフェースのいくつかを実装する形で作る。
    IDrawable      描画できる項目のインターフェース
    IAttacher      攻撃物のインターフェース
    ITarget        攻撃目標になるアイテムのインターフェース
    IMoveAutomatic 自動で動くアイテムのインターフェース
    IMoveManual    手動で動かすアイテムのインターフェース

クラスの概要(アクティビティを除く。スーパークラス、インターフェースも記述)
---
AbstractItem  インベーダー・ゲーム上のアイテムのスーパークラス。
              IDrawable

Bullet        弾のクラス。
              AbstractItem、IAttacher、IMoveAutomatic

CanvasView    インベーダー・ゲームの制御。

Gun           砲台のクラス。
              AbstractItem、IMoveManual

Invader       インベーダーのクラス
              AbstractItem、ITarget、IMoveAutomatic
---

クラス図
---
IDrawable
   A                                |---|> IAttacher
   |              |----  Bullet  ---|
   |              |                 |---|> IMoveAutomatic
   |              |                   |    
AbstractItem <|---+----  Invader -----+-|> ITarget
                  |
                  |---   Gun     -------|> IMoveManual
---


▲ アクティビティ

□ Invader11Activity.java

※ Invader01Activity.java と同じ


▲ ビュー

表示とあわせて、ゲームをコントロールする。

定数
---
int    BLOCK_SIZE        描画のときのブロックの大きさ
int    DRAWING_INTERVAL  描画間隔(ミリ秒)
int    NOT_VISIBLE       非表示
---

フィールド
---
Bullet    bullet     弾
Gun       gun        砲台
Invader   invader    インベーダー
int       score      得点
TextView  scoreView  得点を表示するビュー
---

メソッド
---
public     TextView  getScoreView() 
                     scoreView を取得する。
public     boolean   onTouchEvent(android.view.MotionEvent event) 
           
public     void      setScoreView(android.widget.TextView scoreView) 
                     scoreView を設定する。
protected  void      onDraw(android.graphics.Canvas canvas) 
           
protected  void      onSizeChanged(int w, int h, int oldw, int oldh) 
           
private    void      clearScreen(android.graphics.Canvas canvas) 
                     canvasをクリアする。
private    void      drawScore(int score) 
                     スコアを表示する。
private    void      init() 
                     すべてのコンストラクターで共通の処理。
---


□ CanvasView.java
---
package jp.marunomaruno.android.invader;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * インベーダー・ゲーム。
 *
 * @author marunomaruno
 * @version 1.0, 2012-03-06
 */
public class CanvasView extends ImageView {

    /**
     * 描画間隔(ミリ秒)
     */
    public static final int DRAWING_INTERVAL = 50;

    /**
     * 得点
     */
    private int score = 0;

    /**
     * 得点を表示するビュー
     */
    private TextView scoreView;

    /**
     * インベーダー
     */
    private Invader invader;

    /**
     * 砲台
     */
    private Gun gun;

    /**
     * 弾
     */
    private Bullet bullet;

    public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CanvasView(Context context) {
        super(context);
        init();
    }

    /**
     * すべてのコンストラクターで共通の処理。
     */
    private void init() {
        score = 0; // 得点

        // インベーダーの生成(画面左上から)
        invader = new Invader(this);

        // 弾の生成(まだ非表示)
        bullet = new Bullet(this);

        System.out.println(invader);
        System.out.println(bullet);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        System.out.printf("onSizeChanged: (%d, %d)%n", w, h);

        // 砲台の設定
        gun = new Gun(this);

        System.out.println(gun);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 画面をクリアする
        clearScreen(canvas);

        // インベーダーを表示する
        invader.draw(canvas);
        invader.resetColor();

        // 弾を表示する
        bullet.draw(canvas);

        // 砲台を表示する
        gun.draw(canvas);

        // インベーダーを指定方向に移動する
        invader.move();

        // 弾を移動する
        bullet.move();

        // 弾が当たったら得点する
        if (invader.isHit(bullet)) {
            score++;
            invader.setColor(Invader.HIT_COLOR);
            bullet.setNotVisible(); // 弾の表示を消す
            // 得点を表示する
            drawScore(score);
        }

        // 指定ミリ秒分ウエイトする
        try {
            Thread.sleep(DRAWING_INTERVAL);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: // 指をタッチした
            assert true; // 何もしない
            break;

        case MotionEvent.ACTION_MOVE: // 指を動かしている
            gun.move((int) event.getX(), gun.y); // 横に動かす
            break;

        case MotionEvent.ACTION_UP: // 指を離した
            bullet = gun.shot();
            break;

        default:
            assert true; // 何もしない
            break;
        }

        invalidate();

        return true;
    }

    /**
     * scoreView を取得する。
     *
     * @return scoreView オブジェクト
     */
    public TextView getScoreView() {
        return scoreView;
    }

    /**
     * scoreView を設定する。
     *
     * @param scoreView
     */
    public void setScoreView(TextView scoreView) {
        this.scoreView = scoreView;
    }

    /**
     * canvasをクリアする。
     */
    private void clearScreen(Canvas canvas) {
        invalidate();
    }

    /**
     * スコアを表示する。
     *
     * @param score
     *            スコア
     */
    private void drawScore(int score) {
        assert scoreView != null;

        scoreView.setText(Integer.toString(score));
    }
}
---


▲ インターフェース

アイテムの性質によって、つぎのインターフェースを作った。

インターフェースとそれぞれのメソッド
---
IDrawable       描画できる項目のインターフェース
     void       draw(android.graphics.Canvas canvas) 
     int        getColor() 
     int        getHeight() 
     int[][]    getImageTable() 
     int        getWidth() 
     int        getX() 
     int        getY() 
     boolean    isVisible() 
     void       setNotVisible() 

IAttacher       攻撃物のインターフェース
     boolean    isVisible() 
            
ITarget         攻撃目標になるアイテムのインターフェース
    boolean     isHit(IAttacher attacher) 
     void       resetColor() 
     void       setColor(int color) 
            
IMoveAutomatic  自動で動くアイテムのインターフェース
     boolean    isBorder() 
     void       move() 
            
IMoveManual     手動で動かすアイテムのインターフェース
     void       move(int x, int y) 
---


□ IDrawable インターフェース

描画できる項目のインターフェース。
描画に必要なものとして、
・描画のデータ
・色
・左上の座標
がある。これらに対するゲッターを用意している。

メソッド
---
void       draw(android.graphics.Canvas canvas) 
int        getColor() 
int        getHeight() 
int[][]    getImageTable() 
int        getWidth() 
int        getX() 
int        getY() 
boolean    isVisible() 
void       setNotVisible() 
---


・IDrawable.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;

/**
 * 描画できる項目のインターフェース
 *
 * @author maruno
 *
 */
public interface IDrawable {

    /**
     * 描画のときのブロックの大きさ
     */
    public static final int BLOCK_SIZE = 20;
    /**
     * 非表示
     */
    public static final int NOT_VISIBLE = -1;

    /**
     * 図形描画のテーブルを取得する。
     *
     * @return 図形描画のテーブル
     */
    public int[][] getImageTable();

    /**
     * X 座標を取得する。
     *
     * @return X 座標
     */
    public int getX();

    /**
     * Y 座標を取得する。
     *
     * @return Y 座標
     */
    public int getY();

    /**
     * 図形の色を取得する。
     *
     * @return 図形の色
     */
    public int getColor();

    /**
     * 図形の幅を取得する。
     *
     * @return 図形の幅
     */
    public int getWidth();

    /**
     * 図形の高さを取得する。
     *
     * @return 図形の高さ
     */
    public int getHeight();

    /**
     * 図形を描画する。
     *
     * @param canvas
     *            キャンバス
     */
    public void draw(Canvas canvas);

    /**
     * この図形が可視かどうかを返す。
     *
     * @return 可視の場合true
     */
    public boolean isVisible();

    /**
     * 図形を非表示にする。
     */
    public void setNotVisible();

}
---


□ IMoveAutomatic インターフェース

自動で動くアイテムのインターフェース。
自身が描画域の端にいるのかどうかを判断するメソッドを用意する。

メソッド
---
boolean    isBorder() 
void       move() 
---


・ IMoveAutomatic.java
---
package jp.marunomaruno.android.invader;

/**
 * 自動で動くアイテムのインターフェース
 *
 * @author maruno
 *
 */
public interface IMoveAutomatic {
    /**
     * アイテムが動く。
     */
    public void move();

    /**
     * 端かどうかを判断する。
     *
     * @return 端のときは true
     */
    public boolean isBorder();
}
---


□ IMoveManual インターフェース

手動で動かすアイテムのインターフェース。
このため、移動先の座標を指定して移動するメソッドを用意する。

メソッド
---
void       move(int x, int y) 
---


・ IMoveManual.java
---
package jp.marunomaruno.android.invader;

/**
 * 手動で動かすアイテムのインターフェース
 * @author maruno
 *
 */
public interface IMoveManual {
    /**
     * アイテムを指定座標に動かす。
     * @param x X 座標
     * @param y Y 座標
     */
    public void move(int x, int y);
}
---


□ IAttacher インターフェース

相手を攻撃する物体のインターフェース。
可視かどうかを判断することができるようにする。

メソッド
---
boolean    isVisible() 
---


・ IAttacher.java
---
package jp.marunomaruno.android.invader;

/**
 * 攻撃物のインターフェース
 * @author maruno
 *
 */
public interface IAttacher {
    public boolean isVisible();
}
---


□ ITarget インターフェース

攻撃目標になるアイテムのインターフェース。
攻撃物が自身に当たったかどうかを判断できる。
また、あたったときに、自身の色を変えることができる。

メソッド
---
boolean    isHit(IAttacher attacher) 
void       resetColor() 
void       setColor(int color) 
---


・ ITarget.java
---
package jp.marunomaruno.android.invader;

/**
 * ITarget        攻撃目標になるアイテムのインターフェース
 * 
 * @author maruno
 * 
 */
public interface ITarget {
    public boolean isHit(IAttacher attacher);

    /**
     * 図形の色を設定する。
     * 
     * @param color
     *            色
     */
    public void setColor(int color);

    /**
     * 図形の色をデフォルトの色にリセットする。
     */
    public void resetColor();
}
---


▲ アイテム

スーパークラスとして、AbstractItem をつくり、これを継承するクラスとして、イン
ベーダー、砲台、弾 をつくる。


□ AbstractItem クラス

すべてのアイテムのスーパークラスになるクラス。

図形の形、色、描画するときの左上の座標を持つ。
とくに、図形の左上の座標は public にしている。
図形の形、色は、このクラスのサブクラスで設定する。


フィールドの概要
---
public     int         x       図形の x 座標
public     int         y       図形の y 座標
protected  int         color   図形の色
protected  int         height  図形の高さ
protected  int[][]     image   図形の形
protected  CanvasView  view    図形を描画するビュー
protected  int         width   図形の幅
---


メソッドの概要
---
public     int      getColor() 
                    図形の色を取得する。
public     int      getHeight() 
                    図形の高さを取得する。
public     int[][]  getImageTable() 
                    図形描画のテーブルを取得する。
public     int      getWidth() 
                    図形の幅を取得する。
public     int      getX() 
                    X 座標を取得する。
public     int      getY() 
                    Y 座標を取得する。
public     boolean  isVisible() 
                    この図形が可視かどうかを返す。
public     void     setNotVisible() 
                    図形を非表示にする。
protected  void     draw(Canvas canvas, Paint paint, int x, int y, int[][] image) 
                    図形を描画する。
---


・ AbstractItem.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Paint;

/**
 * インベーダー・ゲーム上のアイテムのスーパークラス。 
 * アイテムの座標、色を持っている。
 *
 * @author maruno
 *
 */
public abstract class AbstractItem implements IDrawable {

    /**
     * 図形の x 座標
     */
    protected int[][] image;

    /**
     * 図形の x 座標
     */
    public int x;
    /**
     * 図形の y 座標
     */
    public int y;

    /**
     * 図形の色
     */
    protected int color;

    /**
     * 図形の幅
     */
    protected int width;
    /**
     * 図形の高さ
     */
    protected int height;
    /**
     * 図形を描画するビュー
     */
    protected CanvasView view;

    protected AbstractItem(CanvasView view, int x, int y, int[][] image,
            int width, int height, int color) {
        super();
        this.view = view;
        this.x = x;
        this.y = y;
        this.image = image;
        this.width = width;
        this.height = height;
        this.color = color;
    }

    /**
     * 図形を描画する。
     *
     * @param canvas
     *            キャンバス
     * @param paint
     *            ペイント
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param image
     *            図形を表現した2次元配列
     */
    protected void draw(Canvas canvas, Paint paint, int x, int y, int[][] image) {
        for (int i = 0; i < image.length; i++) {
            for (int j = 0; j < image[0].length; j++) {
                if (image[i][j] == 1) {
                    canvas.drawRect(x + j * BLOCK_SIZE, y + i * BLOCK_SIZE, x
                            + j * BLOCK_SIZE + BLOCK_SIZE, y + i * BLOCK_SIZE
                            + BLOCK_SIZE, paint);
                }
            }
        }
    }

    @Override
    public int[][] getImageTable() {
        return image;
    }

    @Override
    public int getX() {
        return x;
    }

    @Override
    public int getY() {
        return y;
    }

    @Override
    public int getColor() {
        return color;
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    @Override
    public boolean isVisible() {
        return x >= 0 && y >= 0;
    }

    @Override
    public void setNotVisible() {
        x = NOT_VISIBLE;
        y = NOT_VISIBLE;
    }
}
---


□ Bullet クラス

弾を管理するクラス。
弾は、一度発射されれば自動で動くので、IMoveAutomatic インターフェースを、また、
相手を攻撃するので、IAttacher インターフェースを実装する。

定数
---
int      COLOR        図形の色
int      HEIGHT       描画高
int[][]  IMAGE        図形イメージ
int      MOVING_UNIT  移動単位(ピクセル)
int      WIDTH        描画幅
---


・ Bullet.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

/**
 * 弾のクラス。
 * 
 * @author marunomaruno
 */
public class Bullet extends AbstractItem implements IMoveAutomatic, IAttacher {

    /**
     * 図形イメージ
     */
    public static final int[][] IMAGE = { { 1 }, };

    /**
     * 描画幅
     */
    public static final int WIDTH = IMAGE[0].length * BLOCK_SIZE;
    /**
     * 描画高
     */
    public static final int HEIGHT = IMAGE.length * BLOCK_SIZE;

    /**
     * 移動単位(ピクセル)
     */
    public static final int MOVING_UNIT = 5;

    /**
     * 図形の色
     */
    public static final int COLOR = Color.GREEN;

    /**
     * 弾オブジェクトを生成する。
     * 
     * @param view
     *            ビュー
     * @param x
     *            図形の左上のX座標
     * @param y
     *            図形の左上のY座標
     */
    public Bullet(CanvasView view, int x, int y) {
        super(view, x, y, IMAGE, WIDTH, HEIGHT, COLOR);
    }

    /**
     * 弾オブジェクトを非表示の状態で生成する。
     * 
     * @param view
     *            ビュー
     */
    public Bullet(CanvasView view) {
        this(view, NOT_VISIBLE, NOT_VISIBLE);
        ;
    }

    @Override
    public void draw(Canvas canvas) {
        if (isBorder()) {
            return;
        }

        Paint paint = new Paint();
        paint.setColor(color);
        draw(canvas, paint, x, y, IMAGE);
    }

    @Override
    public void move() {
        y = y - MOVING_UNIT;
    }

    @Override
    public boolean isBorder() {
        return !isVisible();
    }

    @Override
    public String toString() {
        return String.format("Bullet[%d, %d]", x, y);
    }
}
---


□ Gun クラス

砲台を管理するクラス。
砲台は、利用者の指示で動くので、IMoveManual インターフェースを実装する。


メソッド
---
public  Bullet  shot()  弾を発射する。
---


・ Gun.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

/**
 * 砲台のクラス。
 * @author marunomaruno
 */
public class Gun extends AbstractItem implements IMoveManual {

    /**
     * 図形イメージ
     */
    public static final int[][] IMAGE = {
            { 0, 0, 1, 1, 0, 0 },
            { 1, 1, 1, 1, 1, 1 },
    };

    /**
     * 描画幅
     */
    public static final int WIDTH = IMAGE[0].length * BLOCK_SIZE;
    /**
     * 描画高
     */
    public static final int HEIGHT = IMAGE.length * BLOCK_SIZE;

    /**
     * 移動単位(ピクセル)
     */
    public static final int MOVING_UNIT = 5;

    /**
     * 図形の色
     */
    public static final int COLOR = Color.BLUE;

    /**
     * 砲台オブジェクトを生成する。
     * @param view ビュー
     * @param x 図形の左上のX座標
     * @param y 図形の左上のY座標
     */
    public Gun(CanvasView view, int x, int y) {
        super(view, x, y, IMAGE, WIDTH, HEIGHT, COLOR);
    }

    /**
     * 砲台オブジェクトを生成する。
     * 砲台は画面の下段中央に配置する。
     * @param view ビュー
     */
    public Gun(CanvasView view) {
        this(view, (view.getWidth() - WIDTH) / 2, view.getHeight() - HEIGHT);
    }

    /**
     * 弾を発射する。
     */
    public Bullet shot() {
        return new Bullet(view, x + (Gun.WIDTH - Bullet.WIDTH) / 2, y);
    }

    @Override
    public void draw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(color);
        draw(canvas, paint, x, y, IMAGE);
    }

    @Override
    public void move(int x, int y) {
        this.x = Math.max(Math.min(x, view.getWidth() - WIDTH), 0);
    }

    @Override
    public String toString() {
        return String.format("Gun[%d, %d]", x, y);
    }
}
---


□ Invader クラス

インベーダーを管理するクラス。
インベーダーは、ゲームの最初から自動で動くので、IMoveAutomatic インターフェース
を、また、相手の攻撃を受けるので、ITarget インターフェースを実装する。


定数
---
int      COLOR            図形の色
int      DIRECTION_LEFT   移動方向 左
int      DIRECTION_RIGHT  移動方向 右
int      HEIGHT           描画高
int      HIT_COLOR        当たったときの図形の色
int[][]  IMAGE            図形イメージ
int      MOVING_UNIT      移動単位(ピクセル)
int      WIDTH            描画幅
---

フィールド
---
private  int  movingUnit  移動単位(ピクセル)
---


メソッド
---
public  void  invert()    移動方向を反転する。
---


・ Invader.java
---
package jp.marunomaruno.android.invader;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

/**
 * インベーダーのクラス
 * @author maruno
 *
 */
public class Invader  extends AbstractItem implements IMoveAutomatic, ITarget {

    /**
     * 図形イメージ
     */
    public static final int[][] IMAGE = {
            { 0, 0, 0, 1, 0, 1, 0, 0, 0 },
            { 1, 0, 1, 1, 1, 1, 1, 0, 1 },
            { 1, 1, 1, 0, 1, 0, 1, 1, 1 },
            { 0, 0, 1, 1, 1, 1, 1, 0, 0 },
            { 0, 0, 1, 1, 0, 1, 1, 0, 0 },
            { 0, 0, 1, 0, 0, 0, 1, 0, 0 },
    };

    /**
     * 描画幅
     */
    public static final int WIDTH = IMAGE[0].length * BLOCK_SIZE;
    /**
     * 描画高
     */
    public static final int HEIGHT = IMAGE.length * BLOCK_SIZE;

    /**
     * 移動単位(ピクセル)
     */
    public static final int MOVING_UNIT = 1;

    /**
     * 図形の色
     */
    public static final int COLOR = Color.GREEN;
    /**
     * 当たったときの図形の色
     */
    public static final int HIT_COLOR = Color.RED;

    /**
     * 移動方向 左
     */
    public static final int DIRECTION_LEFT = -1;
    /**
     * 移動方向 右
     */
    public static final int DIRECTION_RIGHT = 1;

    /**
     * 移動単位(ピクセル)
     */
    private int movingUnit = DIRECTION_RIGHT * MOVING_UNIT;

    /**
     * インベーダー・オブジェクトを生成する。
     * @param view ビュー
     * @param x 図形の左上のX座標
     * @param y 図形の左上のY座標
     */
    public Invader(CanvasView view, int x, int y) {
        super(view, x, y, IMAGE, WIDTH, HEIGHT, COLOR);
    }

    /**
     * インベーダー・オブジェクトを生成する。
     * インベーダーは画面の左上から出現する。
     * @param view ビュー
     */
    public Invader(CanvasView view) {
        this(view, 0, 0);
    }

    /**
     * 移動方向を反転する。
     */
    public void invert() {
        movingUnit = -movingUnit;
    }

    @Override
    public void draw(Canvas canvas) {
        Paint paint = new Paint();
        paint.setColor(color);
        draw(canvas, paint, x, y, IMAGE);    }

    @Override
    public void setColor(int color) {
        this.color = color;
    }

    @Override
    public void resetColor() {
        color = COLOR;
    }

    /**
     * インベーダーを動かす。
     * 動かすのは、単純に左右に動かし、壁にぶつかったら反転する。
     */
    @Override
    public void move() {
        x += movingUnit;

        // 左右の端にぶつかったら反転する
        if (isBorder()) {
            invert();
        }
    }

    @Override
    public boolean isBorder() {
        return (x < 0) || (x > view.getWidth() - WIDTH);
    }

    @Override
    public boolean isHit(IAttacher attacher) {
        Bullet bullet = (Bullet) attacher;

        return attacher.isVisible()
                && (bullet.y + Bullet.HEIGHT < this.y + HEIGHT)
                && (this.x < bullet.x)
                && (bullet.x + Bullet.WIDTH < this.x + WIDTH);
    }

    @Override
    public String toString() {
        return String.format("Invader[%d, %d]", x, y);
    }
}
---


▲ レイアウト

□ res/layout/main.xml

※ Invader01 プロジェクトと同じ

▲ リソース

□ res/values/strings.xml

※ Invader01 プロジェクトと同じ

                                                                            以上


[Android] インベーダー・ゲームを作る (1) シンプル

2012年04月05日 | Android
[Android] インベーダー・ゲームを作る (1) シンプル
================================================================================

インベーダー・ゲームを作ってみる。

ゲームの概要:
・画面上部にインベーダーがあり、これが左右に動く(端まで行ったら折り返す程度)
・画面下部に砲台があり、ユーザーのタッチムーブ(ドラッグ)によって左右に動く
・画面をタッチすると、砲台から弾が上部向けてに発射する
・弾がインベーダーに当たると得点する

ゲーム自体は、ImageView クラスを継承したクラス CanvasView を作って、それの上で行
う。


▲ アクティビティ

□ Invader01Activity.java
---
package jp.marunomaruno.android.invader;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class InvaderActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TextView scoreView = (TextView) findViewById(R.id.score);
        CanvasView canvasView = (CanvasView) findViewById(R.id.canvasView);
        canvasView.setScoreView(scoreView);
    }
}
---


▲ ビュー

ゲーム自体は、ImageView クラスを継承したクラス CanvasView を作って、それの上で行
う。

CanvasView は以下のようなメンバーからなる。

定数
---
int     BLOCK_SIZE          描画のときのブロックの大きさ
int     BULLET_COLOR        弾の色
int     BULLET_HEIGHT       弾の描画高
int[][] BULLET_IMAGE        弾の図形イメージ
int     BULLET_MOVING_UNIT  弾の移動する単位(ピクセル)
int     BULLET_WIDTH        弾の描画幅
int     DIRECTION_LEFT      移動方向 左
int     DIRECTION_RIGHT     移動方向 右
int     DRAWING_INTERVAL    描画間隔(ミリ秒)
int     GUN_COLOR           砲台の色
int     GUN_HEIGHT          砲台の描画高
int[][] GUN_IMAGE           砲台の図形イメージ
int     GUN_MOVING_UNIT     弾の移動する単位(ピクセル)
int     GUN_WIDTH           砲台の描画幅
int     INVADER_COLOR       正常時のインベーダーの色
int     INVADER_HEIGHT      インベーダーの描画高
int     INVADER_HITL_COLOR  当たったときのインベーダーの色
int[][] INVADER_IMAGE       インベーダーの図形イメージ
int     INVADER_MOVING_UNIT インベーダーの移動する単位(ピクセル)
int     INVADER_WIDTH       インベーダーの描画幅
int     NOT_VISIBLE         非表示
---

フィールド
---
private  int       bulletPointX        弾の x 座標
private  int       bulletPointY        弾の y 座標
private  int       gunPointX           砲台の x 座標
private  int       gunPointY           砲台の y 座標
private  int       invaderColor        インベーダーの表示色
private  int       invaderMovingUnit   インベーダーの移動方向
private  int       invaderPointX       インベーダーの x 座標
private  int       invaderPointY       インベーダーの y 座標
private  int       score               得点
private  TextView  scoreView           得点を表示するビュー
---

public メソッド
---
TextView getScoreView()                   scoreView を取得する。
void     setScoreView(TextView scoreView) scoreView を設定する。
void     shot()                           弾を発射する。
---

private メソッド
---
void  clearScreen(Canvas canvas)           canvasをクリアする。
void  drawBullet(Canvas canvas, int x, int y, int color)  弾を描画する。
void  drawGun(Canvas canvas, int x, int y, int color)     砲台を描画する。
void  drawImage(Canvas canvas, Paint paint, int x, int y, int[][] image)
                                                          図形を描画する。
void  drawInvader(Canvas canvas, int x, int y, int color) インベーダーを描画する。
void  drawScore(int score)                 スコアを表示する。
void  init()                               すべてのコンストラクターで共通の処理。
boolean isBorder(int x, int width)         端かどうかを判断する。
boolean isHit()                            端が当たったかどうかをを判断する。
void  moveGun(int x)                       砲台の位置をずらす。
---

オーバーライドしているメソッド
---
void    onDraw(Canvas canvas) 
void    onSizeChanged(int w, int h, int oldw, int oldh) 
boolean onTouchEvent(MotionEvent event) 
---


□ CanvasView.java
---
package jp.marunomaruno.android.invader;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * インベーダー・ゲーム。
 *
 * @author marunomaruno
 * @version 1.0, 2012-03-06
 */
/**
 * @author marunomaruno
 *
 */
public class CanvasView extends ImageView {

    // 描画イメージ
    /**
     * 描画のときのブロックの大きさ
     */
    public static final int BLOCK_SIZE = 20; // (1)
    /**
     * 非表示
     */
    public static final int NOT_VISIBLE = -1;
    /**
     * 描画間隔(ミリ秒)
     */
    public static final int DRAWING_INTERVAL = 50;

    // インベーダー関係
    /**
     * インベーダーの図形イメージ
     */
    public static final int[][] INVADER_IMAGE = {
            { 0, 0, 0, 1, 0, 1, 0, 0, 0 },
            { 1, 0, 1, 1, 1, 1, 1, 0, 1 },
            { 1, 1, 1, 0, 1, 0, 1, 1, 1 },
            { 0, 0, 1, 1, 1, 1, 1, 0, 0 },
            { 0, 0, 1, 1, 0, 1, 1, 0, 0 },
            { 0, 0, 1, 0, 0, 0, 1, 0, 0 },
    };    // (2)

    /**
     * インベーダーの描画幅
     */
    public static final int INVADER_WIDTH = INVADER_IMAGE[0].length
            * BLOCK_SIZE;// (3)
    /**
     * インベーダーの描画高
     */
    public static final int INVADER_HEIGHT = INVADER_IMAGE.length * BLOCK_SIZE;// (4)

    /**
     * 正常時のインベーダーの色
     */
    public static final int INVADER_COLOR = Color.GREEN;
    /**
     * 当たったときのインベーダーの色
     */
    public static final int INVADER_HITL_COLOR = Color.RED;

    /**
     * インベーダーの移動する単位(ピクセル)
     */
    public static final int INVADER_MOVING_UNIT = 1;
    /**
     * 移動方向 左
     */
    public static final int DIRECTION_LEFT = -1;
    /**
     * 移動方向 右
     */
    public static final int DIRECTION_RIGHT = 1;

    // 砲台関係
    /**
     * 砲台の図形イメージ
     */
    public static final int[][] GUN_IMAGE = {
            { 0, 0, 1, 1, 0, 0 },
            { 1, 1, 1, 1, 1, 1 },
    };

    /**
     * 砲台の描画幅
     */
    public static final int GUN_WIDTH = GUN_IMAGE[0].length * BLOCK_SIZE;
    /**
     * 砲台の描画高
     */
    public static final int GUN_HEIGHT = GUN_IMAGE.length * BLOCK_SIZE;

    /**
     * 弾の移動する単位(ピクセル)
     */
    public static final int GUN_MOVING_UNIT = 5;

    /**
     * 砲台の色
     */
    public static final int GUN_COLOR = Color.BLUE;

    // 弾関係
    /**
     * 弾の図形イメージ
     */
    public static final int[][] BULLET_IMAGE = {
            { 1 },
    };

    /**
     * 弾の描画幅
     */
    public static final int BULLET_WIDTH = BULLET_IMAGE[0].length * BLOCK_SIZE;
    /**
     * 弾の描画高
     */
    public static final int BULLET_HEIGHT = BULLET_IMAGE.length * BLOCK_SIZE;

    /**
     * 弾の移動する単位(ピクセル)
     */
    public static final int BULLET_MOVING_UNIT = 5;

    /**
     * 弾の色
     */
    public static final int BULLET_COLOR = Color.GREEN;

    /**
     * 得点
     */
    private int score = 0;

    // インベーダーの設定
    /**
     * インベーダーの x 座標
     */
    private int invaderPointX = 0;
    /**
     * インベーダーの y 座標
     */
    private int invaderPointY = 0;
    /**
     *  インベーダーの移動方向
     */
    private int invaderMovingUnit = DIRECTION_RIGHT * INVADER_MOVING_UNIT; // (5)
    /**
     * インベーダーの表示色
     */
    private int invaderColor = INVADER_COLOR;

    // 砲台の設定
    /**
     * 砲台の x 座標
     */
    private int gunPointX;
    /**
     * 砲台の y 座標
     */
    private int gunPointY;

    // 弾の設定
    /**
     * 弾の x 座標
     */
    private int bulletPointX = NOT_VISIBLE; // 弾は最初は非表示
    /**
     * 弾の y 座標
     */
    private int bulletPointY = NOT_VISIBLE; // 弾は最初は非表示

    /**
     * 得点を表示するビュー
     */
    private TextView scoreView;

    public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CanvasView(Context context) {
        super(context);
        init();
    }

    /**
     * すべてのコンストラクターで共通の処理。
     */
    private void init() {
        score = 0; // 得点

        // インベーダーの設定
        invaderPointX = 0;
        invaderPointY = 0;
        invaderMovingUnit = DIRECTION_RIGHT * INVADER_MOVING_UNIT; 
                                                    // インベーダーの移動方向
        invaderColor = INVADER_COLOR;

        // 弾の設定
        bulletPointX = NOT_VISIBLE; // 弾は最初は非表示
        bulletPointY = NOT_VISIBLE; // 弾は最初は非表示

        System.out.printf("Canvas: (%d, %d)%n", getWidth(), getHeight()); // (6)
        System.out.printf("Invader: (%d, %d)%n", invaderPointX, invaderPointY);
        System.out.printf("Gun: (%d, %d)%n", gunPointX, gunPointY);
        System.out.printf("Bullet: (%d, %d)%n", bulletPointX, bulletPointY);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    // (7)
        super.onSizeChanged(w, h, oldw, oldh);
        System.out.printf("onSizeChanged: (%d, %d)%n", w, h);

        // 砲台の設定
        gunPointX = (w - GUN_WIDTH) / 2;
        gunPointY = h - GUN_HEIGHT;

        System.out.printf("Gun: (%d, %d)%n", gunPointX, gunPointY);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 画面をクリアする
        clearScreen(canvas);

        // インベーダーを表示する
        drawInvader(canvas, invaderPointX, invaderPointY, invaderColor);
        invaderColor = INVADER_COLOR;

        // 弾を表示する
        drawBullet(canvas, bulletPointX, bulletPointY, BULLET_COLOR);

        // 砲台を表示する
        drawGun(canvas, gunPointX, gunPointY, GUN_COLOR);

        // インベーダーを指定方向に移動する
        invaderPointX = invaderPointX + invaderMovingUnit;

        // 左右の端にぶつかったら反転する
        if (isBorder(invaderPointX, INVADER_WIDTH)) {
            invaderMovingUnit = -invaderMovingUnit;
        }

        // 弾を移動する
        bulletPointY = bulletPointY - BULLET_MOVING_UNIT;

        // 弾が当たったら得点する
        if (isHit()) {
            score++;
            invaderColor = INVADER_HITL_COLOR;
            bulletPointX = NOT_VISIBLE; // 弾の表示を消す
            bulletPointY = NOT_VISIBLE; // 弾の表示を消す
            // 得点を表示する
            drawScore(score);
        }

        // 指定ミリ秒分ウエイトする
        try {
            Thread.sleep(DRAWING_INTERVAL);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN: // 指をタッチした
            assert true; // 何もしない
            break;

        case MotionEvent.ACTION_MOVE: // 指を動かしている    // (8)
            moveGun((int) event.getX());
            break;

        case MotionEvent.ACTION_UP: // 指を離した    // (9)
            shot();
            break;

        default:
            assert true; // 何もしない
            break;
        }

        invalidate();

        return true;
    }

    /**
     * 端が当たったかどうかをを判断する。
     *
     * @return 当たったときは true
     */
    private boolean isHit() {
        return bulletPointY >= 0    // 弾が表示されている
              && (bulletPointY + BULLET_HEIGHT < invaderPointY + INVADER_HEIGHT)
              && (invaderPointX < bulletPointX)
              && (bulletPointX + BULLET_WIDTH < invaderPointX + INVADER_WIDTH); 
                                                                       // (10)
    }

    /**
     * 端かどうかを判断する。
     *
     * @param x
     *            X座標
     * @param width
     *            図形の幅
     * @return 端のときは true
     */
    private boolean isBorder(int x, int width) {
        return (x < 0) || (x > getWidth() - width);
    }

    /**
     * canvasをクリアする。
     */
    private void clearScreen(Canvas canvas) {
        invalidate();
    }

    /**
     * インベーダーを描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param color
     *            図形の色
     */
    private void drawInvader(Canvas canvas, int x, int y, int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        drawImage(canvas, paint, x, y, INVADER_IMAGE);
    }

    /**
     * 弾を描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param color
     *            図形の色
     */
    private void drawBullet(Canvas canvas, int x, int y, int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        drawImage(canvas, paint, x, y, BULLET_IMAGE);
    }

    /**
     * 砲台を描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param color
     *            図形の色
     */
    private void drawGun(Canvas canvas, int x, int y, int color) {
        Paint paint = new Paint();
        paint.setColor(color);
        drawImage(canvas, paint, x, y, GUN_IMAGE);
    }

    /**
     * 図形を描画する。
     *
     * @param x
     *            X座標
     * @param y
     *            Y座標
     * @param image
     *            図形を表現した2次元配列
     */
    private void drawImage(Canvas canvas, Paint paint, int x, int y,
            int[][] image) {
        if (x == NOT_VISIBLE || y == NOT_VISIBLE) {
            return;
        }

        for (int i = 0; i < image.length; i++) {
            for (int j = 0; j < image[0].length; j++) {
                if (image[i][j] == 1) {
                    canvas.drawRect(x + j * BLOCK_SIZE, y + i * BLOCK_SIZE, x
                            + j * BLOCK_SIZE + BLOCK_SIZE, y + i * BLOCK_SIZE
                            + BLOCK_SIZE, paint);    // (11)
                }
            }
        }
    }

    /**
     * スコアを表示する。
     *
     * @param score
     *            スコア
     */
    private void drawScore(int score) {
        assert scoreView != null;

        scoreView.setText(Integer.toString(score));
    }

    /**
     * 弾を発射する。
     */
    public void shot() {
        bulletPointX = gunPointX + (GUN_WIDTH - BULLET_WIDTH) / 2;
        bulletPointY = gunPointY;
    }

    /**
     * 砲台の位置をずらす。
     *
     * @param x
     *            位置
     */
    private void moveGun(int x) {
        gunPointX = Math.max(Math.min(x, getWidth() - GUN_WIDTH), 0);
    }

    /**
     * scoreView を取得する。
     * @return scoreView オブジェクト
     */
    public TextView getScoreView() {
        return scoreView;
    }

    /**
     * scoreView を設定する。
     * @param scoreView
     */
    public void setScoreView(TextView scoreView) {
        this.scoreView = scoreView;
    }

}
---

(1)(2)(11) 図形の描画

ドット絵のように、単位となる四角(ブロック)を決めて、それの組み合わせで描画する。

単位となる四角の大きさを 20 と決める。

    public static final int BLOCK_SIZE = 20; // (1)

図形イメージは、2次元配列で定義する。このブロックを描画するところが 1 に設定して
いる。

    public static final int[][] INVADER_IMAGE = { // インベーダーの図形イメージ
			{ 0, 0, 0, 1, 0, 1, 0, 0, 0 },
			{ 1, 0, 1, 1, 1, 1, 1, 0, 1 },
			{ 1, 1, 1, 0, 1, 0, 1, 1, 1 },
			{ 0, 0, 1, 1, 1, 1, 1, 0, 0 },
			{ 0, 0, 1, 1, 0, 1, 1, 0, 0 },
			{ 0, 0, 1, 0, 0, 0, 1, 0, 0 },
    };    // (2)

ブロックの2次元配列を使って、描画するのは、drawRect() を使う。左上の座標が 
(x, y) になっている。

    canvas.drawRect(x + j * BLOCK_SIZE, y + i * BLOCK_SIZE, x
            + j * BLOCK_SIZE + BLOCK_SIZE, y + i * BLOCK_SIZE
            + BLOCK_SIZE, paint);    // (11)


(3)(4) 図形の幅と高さ

描画する図形の幅と高さを、BLOCK_SIZE と、図形の2次元配列から算出する。

    public static final int INVADER_WIDTH = INVADER_IMAGE[0].length
            * BLOCK_SIZE; // インベーダーの描画幅    // (3)
    public static final int INVADER_HEIGHT = INVADER_IMAGE.length * BLOCK_SIZE;
                                            // インベーダーの描画高    // (4)



(5) インベーダーの移動単位

移動方向(DIRECTION_RIGHT) と、移動単位(INVADER_MOVING_UNIT) を使って決める。
インベーダーは、最初は画面左上から出現して、右方向に移動する。

    private int invaderMovingUnit = DIRECTION_RIGHT * INVADER_MOVING_UNIT; // (5)


(6)(7) キャンバスのサイズ

コンストラクターの中からなので、この時点では幅・高さともに 0 である。

    System.out.printf("Canvas: (%d, %d)%n", getWidth(), getHeight());    // (6)

キャンバスの実際の幅・高さは、このオブジェクトが生成されないとできない。このため、

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {    // (7)

をオーバーライドして設定する。onSizeChanged() メソッドは、View のサイズが変更さ
れたときに、呼び出されるメソッド。

この onSizeChanged() を利用して、砲台の初期位置を設定する。
砲台は、画面下部の中央に出るようにする。


(8) 指の動きにあわせて砲台を移動する

    case MotionEvent.ACTION_MOVE: // 指を動かしている    // (8)
        moveGun((int) event.getX());


(9) 指を離したら、弾を発射する

    case MotionEvent.ACTION_UP: // 指を離した    // (9)
        shot();


(10) インベーダー枠の中に弾の枠が入ったらあたったことにする

    return bulletPointY >= 0    // 弾が表示されている
        && (bulletPointY + BULLET_HEIGHT < invaderPointY + INVADER_HEIGHT)
        && (invaderPointX < bulletPointX)
        && (bulletPointX + BULLET_WIDTH < invaderPointX + INVADER_WIDTH); // (10)


▲ レイアウト

□ 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" >

    <jp.marunomaruno.android.invader.CanvasView
        android:id="@+id/canvasView"
        android:layout_width="match_parent"
        android:layout_height="400px" />

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/scoreTitle"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/score"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="0"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

</LinearLayout>
---


▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">インベーダー</string>
    <string name="scoreTitle">"得点: "</string>
</resources>
---

                                                                            以上


[Android] 簡易電卓アプリ

2012年04月02日 | Android
[Android] 簡易電卓アプリ
================================================================================

単純な機能の電卓アプリ。
つぎのように、単純な機能しか持たない。
    ・ひとつの演算子に対して必ず「=」を押す
    ・連続して複数の演算子を使えない
    ・「=」の後も続けて計算はできない

Android アプリケーションを作るときの基本的な要素しか使っていない。
    LinearLayout
    TextView
    Button

ボタン押下時のハンドラーも、android:onClick 属性で指定している。
    onNumberButtonClick      0 ~ 9 の数字ボタンをクリックしたとき
    onOperatorButtonClick    演算子のボタンをクリックしたとき
    onClearButtonClick       クリア・ボタンをクリックしたとき
    onEqualButtonClick       「=」ボタンをクリックしたとき


▲ レイアウト

4x4 を、LinearLayout と android:layout_weight を使って実現している。

□ 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" >

    <TextView
        android:id="@+id/RESULT_VIEW"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="right"
        android:text="0"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON7"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="7"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON8"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="8"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON9"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="9"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/DIVIDE_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="÷"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON4"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="4"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON5"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="5"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON6"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="6"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/MULTIPLY_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="×"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON1"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="1"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON2"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="2"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/NUMBER_BUTTON3"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="3"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/SUBTRACT_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="-"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/NUMBER_BUTTON0"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onNumberButtonClick"
            android:text="0"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/CLEAR_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClearButtonClick"
            android:text="C"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/EQUAL_BUTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onEqualButtonClick"
            android:text="="
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <Button
            android:id="@+id/ADD_BUTTON"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onOperatorButtonClick"
            android:text="+"
            android:textAppearance="?android:attr/textAppearanceLarge" />
    </LinearLayout>

    <TextView
        android:id="@+id/GUID_VIEW"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/guide" />

</LinearLayout>
---

---
★ @+id でつける名前だが、R クラスでは public static final int で宣言されている
ので、通常の Java の定数のように、すべて大文字で記述したほうがよいように思える。
アクティビティなんかで、switch-case を使っていると、大文字のほうがしっくりくる感
じだ。
---


▲ アクティビティ


□ Calculator01Activity.java
---
package jp.marunomaruno.android.calculator;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import jp.marunomaruno.android.calculator.R;

/**
 * 簡易電卓のアクティビティ。
 * @author marunomaruno
 *
 */
public class Calculator01Activity extends Activity {
    private int number = 0;    // 入力された数字
    private int result = 0;    // 計算結果
    private int operatorId;    // 演算子のリソースID

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    /**
     * 数字のボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onNumberButtonClick(View view) {
        Button button = (Button) view;
        number = number * 10 + Integer.parseInt(button.getText().toString());
        show(number);
    }

    /**
     * 演算子のボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onOperatorButtonClick(View view) {
        operatorId = view.getId();
        result = number;
        number = 0;
    }

    /**
     * 「=」ボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onEqualButtonClick(View view) {
        switch (operatorId) {
        case R.id.ADD_BUTTON:
            result += number;
            break;

        case R.id.SUBTRACT_BUTTON:
            result -= number;
            break;

        case R.id.MULTIPLY_BUTTON:
            result *= number;
            break;

        case R.id.DIVIDE_BUTTON:
            result /= number;
            break;

        default:
            assert false;
            break;
        }

        number = 0;
        show(result);
    }

    /**
     * クリア・ボタンがクリックされたときのハンドラー。
     * @param view
     */
    public void onClearButtonClick(View view) {
        number = 0;
        result = 0;
        operatorId = 0;
        show(number);
    }

    /**
     * 指定された数値を表示する。
     * @param number 表示する数値
     */
    private void show(int number) {
        TextView resultView = (TextView) findViewById(R.id.RESULT_VIEW);
        resultView.setText(Integer.toString(number));
    }
}
---


▲ リソース

□ res/values/strings.xml
---
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">簡易電卓</string>
    <string name="guide">
            ・ひとつの演算子に対して必ず「=」を押す¥n
            ・連続して複数の演算子を使えない¥n
            ・「=」の後も続けて計算はできない¥n
    </string>
</resources>
---


■ 発展

つぎのようなことを考慮して、改良する。
    ・レイアウトを整える
    ・演算子を連続して使えるようにする
    ・「=」の後に続けて演算子を使えるようにする
    ・式も表示できるようにする
    ・他の演算子(平方根や三角など)をつけて、関数電卓にする
    ・16進数が使えるようにする
    ・結果がlong型まで大丈夫にする
    ・数値の桁数の制限をなくす
    ・入力に対して undo する
    ・画面の縦横が変わっても、計算結果を失わないようにする
    ・例外を出さないようにする

                                                                            以上