🧑‍🎨

AndroidでのView描画の基礎

2024/06/01に公開

概要

View・Canvas・Paintの基礎を抑える

開発環境

タイトル 内容
Android 10
AndroidStudio Bumblebee
macOS 12.3
minSdk 28
targetSdk 31
実行端末 Pixel3a

参考資料

  • 「はじめてのAndroidアプリ開発 Java編」07-01
  • View
  • Canvas
  • Paint

ビューとは

ビュー(ウィジェット/レイアウト)はViewの派生クラス。

なので、独自のビューを作成したい場合はViewを継承する。

カスタムビューの基本

java

public class SampleView extends View {
    private Paint paint;

    public SampleView(Context context) {
        super(context);
    }

    public SampleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        // スタイル準備
        paint = new Paint();
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(100);
    }

    public SampleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        canvas.drawPoint(100.0f, 100.0f, paint);
    }
}

xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <com.example.canvastestjava.custom_view.SampleView
        android:id="@+id/main_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

具体的な描画処理はonDrawに記述する

protected void onDraw(Canvas canvas)

Canvas

  • 基本的な描画の種類
    • 矩形・円・楕円・角丸矩形・弧・点・線・画像・文字列

Paint

  • スタイル情報を管理するためのクラス
  • 基本的な指定
    • setColor・setWidth

java

public class DotView extends View {
    private Paint paint;

    public DotView(Context context) {
        super(context);
    }

    public DotView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(30);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.DKGRAY);
        // 30づつ座標をずらす
        float[] ps = {
                50, 100,  // 1個目 (x:50,  y:100)
                80, 130,  // 2個目 (x:80,  y:130)
                110, 160, // 3個目 (x:110, y:160)
                140, 190,  // 4個目 (x:140, y:190)
        };
        canvas.drawPoints(ps, paint);
    }
}

直線

line.png

java

public class LineView extends View {
    private Paint buttPaint;
    private Paint roundPaint;
    private Paint squarePaint;

    public LineView(Context context) {
        super(context);
    }

    public LineView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        buttPaint = new Paint();
        roundPaint = new Paint();
        squarePaint = new Paint();

        buttPaint.setColor(Color.CYAN);
        roundPaint.setColor(Color.CYAN);
        squarePaint.setColor(Color.CYAN);

        buttPaint.setStrokeWidth(50);
        roundPaint.setStrokeWidth(50);
        squarePaint.setStrokeWidth(50);

        // Paint.Cap.BUTT   先端処理なし
        buttPaint.setStrokeCap(Paint.Cap.BUTT);
        // Paint.Cap.ROUND  先端を丸める
        roundPaint.setStrokeCap(Paint.Cap.ROUND);
        // Paint.Cap.SQUARE 先端を四角形にする
        squarePaint.setStrokeCap(Paint.Cap.SQUARE);
    }

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

        float[] buttPositions = {100.0f, 500.0f, 1000.0f, 500.0f};
        float[] roundPositions = {100.0f, 700.0f, 1000.0f, 700.0f};
        float[] squarePositions = {100.0f, 900.0f, 1000.0f, 900.0f};

        canvas.drawLine(
                buttPositions[0], // 開始点X座標
                buttPositions[1], // 開始点Y座標
                buttPositions[2], // 終了点X座標
                buttPositions[3], // 終了点Y座標
                buttPaint);
        canvas.drawLine(
                roundPositions[0], // 開始点X座標
                roundPositions[1], // 開始点Y座標
                roundPositions[2], // 終了点X座標
                roundPositions[3], // 終了点Y座標
                roundPaint);
        canvas.drawLine(
                squarePositions[0], // 開始点X座標
                squarePositions[1], // 開始点Y座標
                squarePositions[2], // 終了点X座標
                squarePositions[3], // 終了点Y座標
                squarePaint);
    }
}

矩形

java

public class RectView extends View {
    private Paint fillPaint;
    private Paint fillAndStrokePaint;
    private Paint strokePaint;

    public RectView(Context context) {
        super(context);
    }

    public RectView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        fillPaint = new Paint();
        fillAndStrokePaint = new Paint();
        strokePaint = new Paint();

        fillPaint.setColor(Color.CYAN);
        fillAndStrokePaint.setColor(Color.CYAN);
        strokePaint.setColor(Color.RED);

        fillPaint.setStrokeWidth(20);
        fillAndStrokePaint.setStrokeWidth(20);
        strokePaint.setStrokeWidth(20);

        fillPaint.setStyle(Paint.Style.FILL);
        fillAndStrokePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        strokePaint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.DKGRAY);
        float[] fillPs = {100, 100, 300, 300};
        float[] fillAndStrokePs = {400, 100, 600, 300};
        float[] strokePs = {700, 100, 900, 300};

        canvas.drawRect(fillPs[0], fillPs[1], fillPs[2], fillPs[3], fillPaint);
        canvas.drawRect(fillAndStrokePs[0], fillAndStrokePs[1], fillAndStrokePs[2], fillAndStrokePs[3], fillAndStrokePaint);
        canvas.drawRect(strokePs[0], strokePs[1], strokePs[2], strokePs[3], strokePaint);
    }
}

円/楕円

java

public class CircleView extends View {
    private Paint circlePaint;
    private Paint ellipsePaint;

    private final RectF rectf = new RectF(400, 100, 900, 400);

    public CircleView(Context context) {
        super(context);
    }

    public CircleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        circlePaint = new Paint();
        circlePaint.setColor(Color.CYAN);
        circlePaint.setStrokeWidth(20);
        circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);

        ellipsePaint = new Paint();
        ellipsePaint.setColor(Color.BLUE);
        ellipsePaint.setStrokeWidth(20);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.DKGRAY);
        canvas.drawCircle(200, 200, 100, circlePaint);
        canvas.drawOval(rectf, ellipsePaint);
    }
}

円弧

java

public class ArcView extends View {
    private Paint paint;
    private Paint centerLinePaint;

    private final RectF rectf = new RectF(300, 200, 800, 700);
    float[] canterLinePositions = {100.0f, 450.0f, 1000.0f, 450.0f};

    public ArcView(Context context) {
        super(context);
    }

    public ArcView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(5);
        paint.setStyle(Paint.Style.FILL_AND_STROKE);

        centerLinePaint = new Paint();
        centerLinePaint.setColor(Color.WHITE);
        centerLinePaint.setStrokeWidth(2);
    }

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

        float startAngle = 90.0f;
        float sweepAngle = 150.0f;
        canvas.drawArc(rectf, startAngle, sweepAngle, true, paint);
        canvas.drawLine(
                canterLinePositions[0], // 開始点X座標
                canterLinePositions[1], // 開始点Y座標
                canterLinePositions[2], // 終了点X座標
                canterLinePositions[3], // 終了点Y座標
                centerLinePaint
        );
    }
}

テキスト

java

public class TextView extends View {
    private final String TAG = getClass().getSimpleName();
    private Paint paint;

    public TextView(Context context) {
        super(context);
    }

    public TextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setStrokeWidth(5);
        // フォントの種類
        paint.setTypeface(Typeface.SANS_SERIF);
        // テキストサイズ(sp)
        paint.setTextSize(40.0f);
        // 表示位置
        paint.setTextAlign(Paint.Align.CENTER);
        // 水平方向への拡大率
        paint.setTextScaleX(1.0f);
        // 水平方向にずらす度合い
        paint.setTextSkewX(0.0f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d(TAG, "onDraw: ");
        canvas.drawColor(Color.DKGRAY);
        canvas.drawText("TextViewテスト", 300, 300, paint);
    }
}

画像

java

public class ImageView extends View {
    private Paint paint;
    private Bitmap baseBmp;
    private Rect rect = new Rect(150, 100, 450, 400);
    private RectF rectF = new RectF(0, 400, 800, 800);

    public ImageView(Context context) {
        super(context);
    }

    public ImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        baseBmp = BitmapFactory.decodeResource(getResources(), R.drawable.luffy);
        paint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.DKGRAY);
        canvas.drawBitmap(baseBmp, 0, 10, paint);
        canvas.drawBitmap(baseBmp, rect, rectF, paint);
    }
}

拡大・縮小・回転

java

public class OperationView extends View {
    private Paint originPaint;
    private Paint paint;

    public OperationView(Context context) {
        super(context);
    }

    public OperationView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(5);

        originPaint = new Paint();
        originPaint.setColor(Color.LTGRAY);
        originPaint.setStrokeWidth(5);
        originPaint.setStyle(Paint.Style.STROKE);
    }

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

        canvas.translate(200, 200);
        canvas.scale(1.5f, 1.5f);

        // 回転後
        canvas.rotate(60);
        canvas.drawRect(0, 0, 200, 200, paint);

        canvas.rotate(-60);
        canvas.drawRect(0, 0, 200, 200, originPaint);

        canvas.skew(-0.1f, 0.5f);
        canvas.drawRect(0, 500, 200, 700, paint);

        canvas.skew(+0.1f, -0.5f);
        canvas.drawRect(0, 500, 200, 700, originPaint);
    }
}

パス

java

public class PathView extends View {
    private Paint paint;
    private Path path;

    public PathView(Context context) {
        super(context);
    }

    public PathView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        path = new Path();
        paint.setColor(Color.CYAN);
        paint.setStrokeWidth(20);
        paint.setStyle(Paint.Style.STROKE);
    }

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

        // 平たい角
        path.reset();
        path.moveTo(210, 400);
        path.lineTo(240, 750);
        path.lineTo(270, 400);
        paint.setStrokeJoin(Paint.Join.BEVEL);
        canvas.drawPath(path, paint);

        // 鋭角
        path.reset();
        path.moveTo(400, 400);
        path.lineTo(440, 750);
        path.lineTo(470, 400);
        paint.setStrokeMiter(30);
        paint.setStrokeJoin(Paint.Join.MITER);
        canvas.drawPath(path, paint);

        // 丸角
        path.reset();
        path.moveTo(600, 400);
        path.lineTo(640, 750);
        path.lineTo(670, 400);
        paint.setStrokeJoin(Paint.Join.ROUND);
        canvas.drawPath(path, paint);
    }
}

属性の追加

xml

<com.example.canvastestjava.custom_view.CanvasAttrView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:color="@color/purple_200"
        app:type="circle"/>

java

public class CanvasAttrView extends View {
    // 属性値のフォールド
    private int color = Color.BLACK;
    private int type = 0;

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
        // 再描画
        invalidate();
        requestLayout();
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
        // 再描画
        invalidate();
        requestLayout();

    }

    private Paint paint = new Paint();

    public CanvasAttrView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        // 属性取得
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(
                attrs, R.styleable.CanvasAttrView, 0, 0);
        try {
            color = typedArray.getColor(R.styleable.CanvasAttrView_color, Color.BLACK);
            type = typedArray.getInt(R.styleable.CanvasAttrView_type, 0);
        } finally {
            typedArray.recycle();
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setColor(color);
        paint.setStrokeWidth(100);

        switch (type) {
            case 0:
                canvas.drawCircle(200, 200, 100, paint);
                break;
            case 1:
                canvas.drawRect(100, 100, 400, 400, paint);
                break;
            case 2:
                canvas.drawLine(10, 20, 800, 500, paint);
                break;
            default:
                break;
        }
    }
}

お絵描きアプリ

MotionEvent(主なメソッド)

メソッド 概要
getAction タッチの種類
ACTION_DOWN
ACTION_MOVE
ACTION_UP
ACTION_CANCEL
getDownTime タッチし続けた時間(ミリ秒)
getEdgeFlags スクリーンの端を判定
EDGE_TOP 上端に到達
EDGE_BOTTOM 下端に到達
EDGE_LEFT 左端に到達
EDGE_RIGHT 右端に到達
getX タッチされた位置(X軸)
getY タッチされた位置(Y軸)

onTaxhEventメソッド

アクション 処理
ACTION_DOWN movetToでパスの起点を定義
ACTION_MOVE lineToで前の座標からの直線を描画
ACTION_UP lineToで前の座標から終点までの直線を描画

マルチタッチ関連のメソッド

メンバー 概要
getpointerCount タッチされたポイント数
getpointerId(int pointerIndex) 引数番目のポインターidを取得
findPointerIndex(int id) ポインターidからポインターを検索

java

public class DrawingView extends View {
    private final String TAG = getClass().getSimpleName();
    private Path path;
    private Paint paint;

    public DrawingView(Context context) {
        super(context);
    }

    public DrawingView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(3);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);

        path = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(path, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                path.moveTo(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                path.lineTo(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_UP:
                performClick();
                path.lineTo(event.getX(), event.getY());
                break;
            default:
                break;
        }
        // onDrawメソッドが呼ばれる(ビューを無効化し再描画をOSに通知)
        invalidate();
        return true;
    }

    /**
     * performClick
     * 呼び出し元からOnClickListenerを利用している場合必要
     *
     * @return true
     */
    @Override
    public boolean performClick() {
        super.performClick();
        return true;
    }

    public void clearCanvas() {
        Log.d(TAG, "clearCanvas: ");
        path.reset();
        invalidate();
    }
}

Discussion