📚

ビュー描画の基礎(View / Canvas / Paint)

2022/11/23に公開

はじめに

View・Canvas・Paintの簡単な使用例を備忘録として記述しています。

開発環境

Android 10
AndroidStudio Bumblebee
macOS 12.3
minSdk 28
targetSdk 31
実行端末 Pixel3a

参考資料

View
https://developer.android.com/reference/android/view/View
Canvas
https://developer.android.com/reference/android/graphics/Canvas
Paint
https://developer.android.com/reference/android/graphics/Paint

ビューとは

Android開発で使用される様々なビュー(Button,EditTextなど)は、元を辿れば、Viewクラスを継承したView派生クラスです。
独自にビューを作成したい場合はViewクラスを継承し、独自の描画処理を実装することで実現できる。

Viewクラス : ビューの描画に関する基本的な機能だけを提供するクラス

android.view.View(全てのビューのスーパークラス)
|
|____ android.widget.TextView
|             |____ android.widget.Button
|             |____ android.widget.EditText
|             |____ ...
|____ ...
|
|____ android.view.ViewGroup
              |____ android.widget.FrameLayout
              |____ android.widget.LinerLayout
              |____ ...

カスタムビューを描く

Java側

SampleView.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);
	// drawPoint(X座標, Y座標, 描画スタイル)
        canvas.drawPoint(100.0f, 100.0f, paint);
    }
}

onDraw

Androidがビューを表示/更新する際に呼び出すメソッド。
具体的な描画処理はここに記述する。

onDrawメソッド
    /**
     * onDraw
     * @param canvas 描画に使用するキャンバス
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
	...
    }

xml側

activity.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"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
	
    <!-- 以下の書き方でも問題ない -->
    <view 
        class="com.example.canvastestjava.custom_view.SampleView"
        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"/>
</androidx.constraintlayout.widget.ConstraintLayout>

様々な図形

点の描画を行います。
各点の座標を少しずらし表示させてみたいと思います。

Java側

DotView.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);
    }
}

xml側

上記の書き方を参照

直線

直線の描画を行います。
先端の形状を指定することが可能です。

drawLine
https://developer.android.com/reference/android/graphics/Canvas#drawLine(float, float, float, float, android.graphics.Paint)

Java側

LineView.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);
    }

先端の形状

列挙値 内容
Paint.Cap.BUTT 先端処理なし
Paint.Cap.ROUND 先端を丸める(終点を中心とした半円)
Paint.Cap.SQUARE 先端を四角形にする(終点を中心とした四角形)

xml側

上記の書き方を参照

折れ線

折れ線の描画を行います。

drawLines
https://developer.android.com/reference/android/graphics/Canvas#drawLines(float[], android.graphics.Paint)

Java側

LinesView.java
public class Lines extends View {
    private Paint paint;

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

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

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float[] ps = {
                50, 100, 350, 350,  // (50, 100) ~ (350, 350)
                350, 350, 575, 100, // (350, 350) ~ (575, 100)
                575, 100, 720, 350, // (575, 100) ~ (720, 350)
                720, 350, 900, 100  // (720, 350) ~ (900, 100)
        };
        canvas.drawLines(ps, paint);
    }
}

xml側

上記の書き方を参照

矩形

矩形の描画を行います。

drawRect
https://developer.android.com/reference/android/graphics/Canvas#drawRect(float, float, float, float, android.graphics.Paint)

Java側

RectView.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);
    }
}

スタイル

列挙値 内容
Paint.Style.FILL 図形を塗りつぶす(枠線描画なし)
Paint.Style.ROUND 図形を塗りつぶし、枠線も描画
Paint.Style.SQUARE 図形の枠線のみを描画

xml側

上記の書き方を参照

円 / 楕円

drawCircle
https://developer.android.com/reference/android/graphics/Canvas#drawCircle(float, float, float, android.graphics.Paint)

drawOval
https://developer.android.com/reference/android/graphics/Canvas#drawOval(android.graphics.RectF, android.graphics.Paint)

Java側

CircleView.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);
    }
}

xml側

上記の書き方を参照

直線

直線の描画を行います。
先端の形状を指定することが可能です。

drawLine
https://developer.android.com/reference/android/graphics/Canvas#drawLine(float, float, float, float, android.graphics.Paint)

Java側

LineView.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);
    }
}

先端の形状

列挙値 内容
Paint.Cap.BUTT 先端処理なし
Paint.Cap.ROUND 先端を丸める(終点を中心とした半円)
Paint.Cap.SQUARE 先端を四角形にする(終点を中心とした四角形)

xml側

上記の書き方を参照

テキスト

drawText
https://developer.android.com/reference/android/graphics/Canvas#drawText(java.lang.String, float, float, android.graphics.Paint)

Java側

TextView.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);
    }
}

xml側

上記の書き方を参照

画像

drawBitmap
https://developer.android.com/reference/android/graphics/Canvas#drawBitmap(android.graphics.Bitmap, float, float, android.graphics.Paint)

Java側

ImageView.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);
    }
}

xml側

上記の書き方を参照

パス

drawPath
https://developer.android.com/reference/android/graphics/Canvas#drawPath(android.graphics.Path, android.graphics.Paint)

Java側

PathView.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側

上記の書き方を参照

Discussion