お絵描きアプリが深い

Androidアプリ初心者がお絵描きアプリを作る

興味があったので、Androidアプリを作ってみようと思います。
でまあ、手始めにお絵描きアプリでも作ろうと思います。

お絵描き用のクラスを作成

まず、Viewを継承したお絵描き用のクラスを作ります。

public class drawView extends View {

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

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return true;
    }

}

xmlのデザインタブでCustomViewをつくります。Viewはさっき作ったdrawViewを選択します。

ここから描画処理やら書いていきます。

描画処理

描画の処理をおこなうCanvasクラスを用います。

Canvasクラス

Androidの画面の描画を行うAPIです。
例えば、canvas.drawCircle(100,100,10,paint)みたいにすると、
x座標、y座標が100のところに半径10の円が描画できたりします。

paintというのは、ペンの太さや色、塗りつぶすかどうか、といった
描画に関する情報を指定するクラスで、drawCircleの前にあらかじめ
設定しておく必要があります。

とりあえず丸を描画してみる

では、赤で塗りつぶされた半径50の丸を座標(100,100)に描画してみます。

Paint paint = new Paint();

public drawView(Context context, AttributeSet attrs) {
    super(context, attrs);
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.FILL);
}

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(100,100,50,paint);
}

そのために、drawViewのなかのonDrawメソッドに書き加えます。
onDrawメソッドは起動時に呼び出され、Canvasクラスの引数を
制御することによって、画面に様々なものを表示することができます。
また、onDrawメソッドの外でpaintを設定しておきます。
ここでは、色を赤、スタイルを塗りつぶしと設定しています。

これで実行すると赤い丸が描画されると思います。

タッチイベントを取得

画面がタッチされると、drawViewのなかのonTouchEventメソッド
呼び出されます。MotionEventクラスの引数によって、タッチされた
座標などを取得することができます。

タッチしたところに丸を描画してみる

では、さっきの赤丸とonTouchEventを利用して、タッチした場所に
赤丸を描画してみます。
そのためにdrawViewにこんな感じに書き加えます。

public class drawView extends View {

    Paint paint = new Paint();

    float x=-1,y=-1;

    public drawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(x,y,50,paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        x = event.getX();
        y = event.getY();
        invalidate();
        return true;
    }

}

さっきのdrawCircleの座標を指定する部分を変数にして、タッチイベントが起こるたびにその変数にタッチされた場所の座標を入れて再描画しています。
座標を格納するための変数であるxとyは、最初は-1を入れておき、無効な値にしておきます。
タッチイベントがおこるとonTouchEventが呼び出され、xとyの値を更新し、invalidate()で再描画が行われます。再描画でonDrawが呼び出され、drawCircleで丸が描画されます。

この丸を描画する部分をラインにすればとりあえずお絵描きできそうです。

ということで線を描画してみる

線を描画するにはCanvasのdrawLineメソッドを使います。

Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);

canvas.drawLine(10,10,100,100,paint);

こうすると、座標(10,10)から(100,100)まで線を引くことができます。線を描画するため、paintのスタイルは塗りつぶさないように設定しておきます。また、塗りつぶさない場合は線の太さを設定する必要があり、ここでは10としています。

お絵描きする

では、ここまでやったことを利用してお絵描きできるようにしていきます。

public class drawView extends View {

    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(10);

    float oldx=-1, oldy=-1, x=-1, y=-1;

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

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(oldx, oldy,x,y,paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(oldx==-1){
            oldx = event.getX();
            oldy = event.getY();
        } else {
            oldx = x;
            oldy = y;
        }
        x = event.getX();
        y = event.getY();

        invalidate();
        return true;
    }

}

これでできるかと思いきやこれではまだダメです。
これだと一番新しい線しか描画できません。再描画するたびに前の線が消えていくためです。すべて描画させるには、いままでタッチされたすべての座標を記憶しておき、再描画するたびにその座標を線でつなぐ必要があります。座標を記憶しておくため、ArrayListを使います。

public class drawView extends View {

    Paint paint;
    ArrayList<Float> x = new ArrayList<Float>();
    ArrayList<Float> y = new ArrayList<Float>();

    public drawView(Context context, AttributeSet attrs) {
        super(context, attrs);

        paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(10);

        x.add((float) -1);
        y.add((float) -1);

    }

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

        for (int i = 1; i <= x.size()-1; i++) {
            if(x.get(i)!=-1 && x.get(i-1)!=-1)
                canvas.drawLine(x.get(i-1), y.get(i-1), x.get(i), y.get(i), paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                this.x.add(x);
                this.y.add(y);
                break;
            case MotionEvent.ACTION_MOVE:
                this.x.add(x);
                this.y.add(y);
                break;
            case MotionEvent.ACTION_UP:
                this.x.add(x);
                this.y.add(y);
                this.x.add((float) -1);
                this.y.add((float) -1);
                break;
        }
        invalidate();
        return true;
    }

}

すべての座標をつなげてしまうと、まえ描いた線の終点と次の線の視点もつながってしまうので、線を描き終わったことを知る必要があります。これにはMotionEventのgetActionを使います。getAction()では、3種類の値が返ってきます。


MotionEvent.ACTION_DOWN :   タッチしてない状態  → タッチ
MotionEvent.ACTION_MOVE :   タッチした状態     → タッチした状態
MotionEvent.ACTION_UP   :   タッチした状態    → タッチしてない状態


こんな感じです。線を描き終わるのはACTION_UPが返ってきたときなので、getAction()がACTION_UPであればxとyに-1を追加するようにしています。そうして、onDrawのなかで-1以外の場合に線を描画するようにすればオッケーです。

これで超雑なお絵描きアプリ?ができました。

まとめ

とりあえず完成ですが、まだまだお絵描きアプリと呼ぶにはほど遠いです。
ラインをつなげただけなので、曲線を描くとがたがたになったり...
記事長くなりすぎたり...
まだまだ改善点山盛りなので、また改善していきたいと思います。

では、