AndroidのCanvasを使いこなす! - 基本的な描画

有山 圭二
262

みなさんこんにちは。有山圭二と申します。

Android 5.0(開発コード: Lollipop)の正式版の配信が開始され、いよいよAPI Level 21とMaterial Designへの対応に本腰を入れるシーズンになりましたね。

このブログはTech Blogと言うことなので、エンジニアに向けてLollipopで追加された新しいAPIを詳解する……という企画を最初考えたのですが、きっとLollipopに関する情報は他の新しいもの好きがQiitaなどに書くと思うので、今回は思い切ってAndroidのCanvasに焦点を当てることにしました。

「Lollipopの時代にまさかのCanvas」と、驚かれる人も多いかもしれませんが、頻繁に使う機会がない分、ネット上にまとまった情報も少ないので、備忘録を兼ねている面もあります。

1.1 Canvasとは

最古のAPI

Canvasは、AndroidのAPI Level 1の頃からあるとても古いAPIです。Canvasを使うと画面に単純な図形や文字を描画できます。

Canvasは、Androidでは重要な役割を担っています。それは間違いありません。しかし提供されているのが非常に低レベルなAPIなので、これを頻繁に使うという人はあまり多くないかもしれません。

Androidでは画面の部品は各種ウィジットが用意されていて、それを組み合わせると大抵のことが出来ますし、見た目はウィジットにスタイルを設定すれば簡単に変更することが出来ます。

Androidの黎明期にはXMLでの画面設計を好まず、Canvasでガリガリと描画するというとんでもないアプリもチラホラありましたが、今ではそういったアプリはほとんど見なくなりました。

Canvasの使いどころ

さて、それではAndroidアプリを開発する上でどのような場合にCanvasを使うのでしょうか。

真っ先に思い浮かぶのはゲームなどですが、筆者の場合、「地図のような非常に大きな(直接読み込むとOutOfMemoryErorで強制終了する)画像を小さな画像に分割(タイリング)して、表示されている部分のみを表示する」といったアプリを開発する際に、CanvasにBitmapを直接描画するカスタムビューを作った事があります。

(iOS開発者に言うと驚かれるのですが、Androidでは、大きなサイズの画像を表示して簡単に拡大縮小して操作できるようにする部品が標準では提供されていません。)

画面に表示するタイルの一つ一つをImageViewとして追加するわけにもいかず、また、ピンチイン・ピンチアウトで拡大縮小の倍率に応じて読み込む画像を切り替える必要があったので、結局カスタムViewとして実装しました。

その他にも単体の部品として実装したい場合や素材となる画像を加工しながらアニメーションする場合など、Canvasを使うと便利な局面があります。

1.2 Canvasで描画する

それでは早速Canvasでの描画を試してみましょう。

ここではサンプルのためにViewを継承したクラスを作り、onDrawメソッドをオーバーライドします(リスト1.1)。

onDrawメソッドは、システムがViewを描画する度に呼ばれるメソッドで、引数のCanvasに描画した内容がViewとして表示されます。

リスト1.1: 作成したカスタムView
package io.keiji.canvassample.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;

public class CanvasBasicView extends View {

    private Paint mPaint = new Paint();

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

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

        canvas.drawText("Hello Custom View!", 50, 50, mPaint);
    }
}

このカスタムViewをActivityで表示すると、図1.1のようになります。

図1.1: Hello CustomView! の文字列が表示される
図1.1: Hello CustomView! の文字列が表示される

基本的な描画

Canvasの基本的な描画として、

  • 矩形
  • 楕円
  • 角丸矩形
  • 画像
  • 文字列

があります。

矩形

四角形を描画します。四角形を表示する範囲を上下左右(left, top, right, bottom)の座標で指定します。

リスト1.2: 3つの矩形を描画
canvas.drawRect(100, 100, 200, 300, mPaint);
canvas.drawRect(300, 100, 400, 300, mPaint);
canvas.drawRect(600, 100, 700, 300, mPaint);
図1.2: 三つの短径が描かれている
図1.2: 三つの矩形が描かれている

drawRectメソッドには、描画する矩形の「幅と高さ」という概念はありません。描画の度に4つの座標を設定するのが煩雑なので、Rectクラスが用意されています。

Rectクラスは、上下左右(left, top, right, bottom)の値を持ち、offsetメソッドで大きさを保ったまま位置を移動したり、containsメソッドやintersectsメソッドでRectクラス同士の重なりを調べることが出来ます。

リスト1.3: offsetメソッドで移動する
Rect rect = new Rect(100, 100, 200, 300);

canvas.drawRect(rect, mPaint);
rect.offset(200, 0);
canvas.drawRect(rect, mPaint);
rect.offset(300, 0);
canvas.drawRect(rect, mPaint);

円を描画します。中心座標と半径を指定します。

リスト1.4: 中心座標(150, 150)に、半径の異なる三つの円を描画
mPaint.setStyle(Paint.Style.STROKE); // 塗りつぶし無し

canvas.drawCircle(150, 150, 25, mPaint); // 半径25
canvas.drawCircle(150, 150, 50, mPaint); // 半径50
canvas.drawCircle(150, 150, 100, mPaint); // 半径100
図1.3: 中心は同じ
図1.3: 中心は同じ

楕円

楕円を描画します。円のように中心座標ではなく、矩形のように描画範囲を指定すると、その範囲に収まる楕円が描画されます。

drawOvalに指定する座標はRectFクラスで指定します。RectFクラスは、Rectクラスと基本的に同じですが、座標をfloat型で表現します。

リスト1.5: 同じ範囲に矩形と楕円を描画する
mPaint.setStyle(Paint.Style.STROKE); // 塗りつぶし無し

RectF rectf = new RectF(100, 100, 200, 300);
canvas.drawRect(rectf, mPaint);
canvas.drawOval(rectf, mPaint);
図1.4: drawRectと同じ範囲に楕円が描かれている
図1.4: drawRectと同じ範囲に楕円が描かれている

なお、API Level 21(Lollipop)では、4つの座標(left, top, right, bottom)を指定して楕円を描けるAPI drawOvalメソッドが追加されています1)http://developer.android.com/reference/android/graphics/Canvas.html

弧を描画します。楕円と同じく描画範囲を指定します。さらに引数として弧の描画を開始する位置startAngleと、弧の描画を終了する角度sweepAngleをそれぞれ真上を0°として360°までの範囲で指定します。

3番目の引数useCenterでは、描かれた弧を塗りつぶすときに始点と終点を直線で結ぶか、中心で結ぶ範囲を塗りつぶすかを設定します。

リスト1.6: 3つの弧を描画
RectF rectf = new RectF(100, 100, 300, 300);

canvas.drawArc(rectf, 0, 360, false, mPaint);

rectf.offset(300, 0);
canvas.drawArc(rectf, 90, 270, false, mPaint);

rectf.offset(300, 0);
canvas.drawArc(rectf, 90, 270, true, mPaint);
図1.5: 3つの弧が描かれている。一番右側はuseCenterをtrueにした結果
図1.5: 3つの弧が描かれている。一番右側はuseCenterをtrueにした結果

なお、API Level 21(Lollipop)では、4つの座標(left, top, right, bottom)を指定して弧を描けるAPI drawArcメソッドが追加されています2)http://developer.android.com/reference/android/graphics/Canvas.html

角丸矩形

角の丸い矩形を描画します。基本的にはdrawOvalと同じで、座標はRectFクラスで指定し、四つ角の丸みをx、yの半径で指定します。

リスト1.7: 角の丸みの異なる三つの角丸矩形を描画する
mPaint.setStyle(Paint.Style.STROKE);

RectF rectf = new RectF(100, 100, 200, 300);

canvas.drawRoundRect(rectf, 10, 10, mPaint);

rectf.offset(200, 0);
canvas.drawRoundRect(rectf, 20, 20, mPaint);

rectf.offset(200, 0);
canvas.drawRoundRect(rectf, 30, 30, mPaint);
図1.6: 3つの角丸短径が描かれている
図1.6: 3つの角丸矩形が描かれている

なお、drawOval同様、API Level 21(Lollipop)では、4つの座標(left, top, right, bottom)を指定して角丸矩形を描けるAPI drawRoundRectメソッドが追加されています3)http://developer.android.com/reference/android/graphics/Canvas.html

点を描画します。点を描画する座標(x, y)を指定します。

リスト1.8: 点を描画
mPaint.setStrokeWidth(1.0f);
for (int i = 0; i < 300; i++) {
    if (i % 5 != 0) {
        continue;
    }

    canvas.drawPoint(100, 100 + i, mPaint);
}
図1.7: 点線が描画されている
図1.7: 点線が描画されている

また、点を描画する座標として、float型の配列を与えるdrawPointsメソッドもあります。配列には、x0, y0, x1, y1...のように各座標のx座標とy座標のセットで含めます。

リスト1.9: FloatBufferに格納した値をfloat型の配列としてdrawPointsメソッドに設定する
FloatBuffer fb = FloatBuffer.allocate(300);
mPaint.setStrokeWidth(1.0f);
for (int i = 0; i < 300; i++) {
    if (i % 5 != 0) {
        continue;
    }

    fb.put(100); // x座標
    fb.put(100 + i); // y座標
}

canvas.drawPoints(fb.array(), mPaint);

線を描画します。線を描画する始点と終点のそれぞれ座標(x始点, y始点, x終点,
y終点)を指定します。

リスト1.10: 線を描画
canvas.drawLine(100, 100, 100, 300, mPaint);
図1.8: 線が描画されている
図1.8: 線が描画されている

また、点と同様に、描画する座標をfloat型の配列で指定するdrawLinesメソッドもあります。
こちらはx始点、y始点、x終点、y終点の順番にputしていきます。

リスト1.11: FloatBufferに格納した値をfloat型の配列としてdrawLinesメソッドに設定する
mPaint.setStrokeWidth(1.0f);

FloatBuffer fb = FloatBuffer.allocate(300);
fb.put(100);
fb.put(100);
fb.put(200);
fb.put(200);

fb.put(200);
fb.put(200);
fb.put(250);
fb.put(200);

fb.put(250);
fb.put(200);
fb.put(200);
fb.put(300);

canvas.drawLines(fb.array(), mPaint);
図1.9: 複数の線が描画されている
図1.9: 複数の線が描画されている

配列には、x0, y0, x1, y1...のように各座標のx座標とy座標のセットを設定します。ここまでは点と同じですが、描画する線に対して「始点」と「終点」の座標が必要となります。そのため、線の場合、配列に含まれる要素は必ず4の倍数になります。

繰り返しになりますが、各線に対して「始点」と「終点」が必要となります。前の線の終点が、自動的に次の線の始点にはならないので注意してください。

画像

画像(Bitmap)を描画するには、描画する画像と座標を指定します。

リスト1.12: 画像を描画
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
canvas.drawBitmap(bmp, 100, 100, mPaint);
canvas.drawBitmap(bmp, 150, 150, mPaint);
図1.10: 2つのアイコン画像がそれぞれ指定した座標に描画されている
図1.10: 2つのアイコン画像がそれぞれ指定した座標に描画されている

また、描画をする画像の範囲と実際の描画範囲をセットにすることも出来ます(リスト1.13)。

こうすると、幾つかの画像をブロックとしてひとまとめにして必要に応じて描画することが出来るので、ゲームなどを作るときに便利です。

リスト1.13: 画像を描画
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

Rect srcRect1 = new Rect(0, 0, bmp.getWidth()/2, bmp.getHeight()/2);
Rect srcRect2 = new Rect(bmp.getWidth()/2, bmp.getHeight()/2,
                         bmp.getWidth(), bmp.getHeight());

Rect destRect1 = new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
destRect1.offset(100, 100);

Rect destRect2 = new Rect(0, 0, bmp.getWidth() * 2, bmp.getHeight() * 2);
destRect2.offset(300, 300);

canvas.drawBitmap(bmp, srcRect1, destRect1, mPaint);
canvas.drawBitmap(bmp, srcRect2, destRect2, mPaint);
図1.11: アイコン画像の一部がそれぞれ指定した範囲内に投影されている
図1.11: アイコン画像の一部がそれぞれ指定した範囲内に投影されている

文字列

文字列を描画します。描画する文字列と座標を指定します。文字列はどれほど長くても1行で表示されるので、折り返し等の処理は自分で実装する必要があります。

なお、文字列の大きさや各種設定については、後述するPaintで説明します。

リスト1.14: 文字列を描画
canvas.drawText("Hello Canvas!!", 100, 100, mPaint);
図1.12: 文字列が描画されている
図1.12: 文字列が描画されている

CanvasとPaint

Canvasに描画するときに、必ずと言っていいほどセットになっているのがPaintクラスです。

Paintクラスを通じて描画色や線の太さ、文字の大きさやフォントなど、実際に描画を行う際の様々な要素を設定できます。

描画色を設定する

描画色はPaintのsetColorメソッドで設定します。引数はint型で指定します。

リスト1.15: BlackはColorクラスに定義された定数
mPaint.setColor(Color.Black);
canvas.drawRect(0, 0, 100, 100, mPaint);

ちなみに、サーバーやJSONの設定ファイルなどから取得した16進数のカラーコードで色指定を行うケースでは、Integer.parseIntメソッドを使うと便利です。

リスト1.16: 16進数をデコードしている
String str = "ffffcc";
int color = Integer.parseInt(str, 16);
mPaint.setColor(color);

canvas.drawRect(0, 0, 100, 100, mPaint);

ただし、setColorに設定する値がARGB形式であることに注意してください。リスト1.16の場合、アルファチャンネル(不透明度)の値は0になっているため、描画がされません。その場合、不透明度の値を別途設定(0xff000000とのORをとるなど)する必要があります。

描画に指定するStyle

描画するスタイルを設定します。setStyleメソッドには、Paint.Styleの列挙型を指定します。

標準ではFILL_AND_STROKEが設定されていますが、例えばPaint.Style.STROKEを設定すれば、塗りつぶしをしない線のみで描画することが出来ます。

表1.1: Paint.Style
スタイル 説明
FILL_AND_STROKE 線を描画して塗りつぶし。スタイルを指定しない場合のデフォルト
STROKE 線のみ描画
FILL 塗りつぶしのみ。線の太さ(setStrokeWidth)で指定した値分は描画されない
リスト1.17: スタイルの異なる矩形を描画
mPaint.setStrokeWidth(20.0f);

Rect rect = new Rect(100, 100, 200, 200);

mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawRect(rect, mPaint);

mPaint.setStyle(Paint.Style.STROKE);
rect.offset(150, 0);
canvas.drawRect(rect, mPaint);

mPaint.setStyle(Paint.Style.FILL);
rect.offset(150, 0);
canvas.drawRect(rect, mPaint);
図1.13: スタイルの異なる短径を描画
図1.13: スタイルの異なる矩形を描画

アンチエイリアス

描画はそのままでは設定されたドットのみになるため、斜め方向や曲線を表現する際にギザギザになります。

setAntiAliasメソッドでアンチエイリアスを有効にすると、ギザギザになる部分に中間色で補完して、なめらかに見せる事が出来ます。

図1.14: アンチエイリアスが無効な状態(左)と有効な状態(右)
図1.14: アンチエイリアスが無効な状態(左)と有効な状態(右)

特に文字列を描画する際には、有効にしておくべきでしょう。

文字列の設定

文字列の描画に関する設定は、Paintクラスで行います。

フォントのサイズ

フォントのサイズは、setTextSizeメソッドで設定します。引数はfloat型で指定します。

ここで指定する単位はピクセルなので、SP (Scale-independent Pixels)で指定されたリソースから値を取る場合は、getResources().getDimensionPixelSizeメソッドを使います。

また、将来的にカスタムビューとして作り込むなら、スタイルで設定できるようにすると良いでしょう。

ゆがみ(skewX)

setTextSkewXメソッドを設定すると、フォントを横方向(X軸方向)にゆがめて表示します。

図1.15: setSkewXに-0.5(上)、0.5(下)をそれぞれ設定
図1.15: setSkewXに-0.5(上)、0.5(下)をそれぞれ設定

なお、setTextSkewYメソッドはありません。

カスタムフォント

通常のテキスト描画は、システムの標準フォントが使われますが、アプリに組み込んだ独自フォントを設定することも可能です。

独自フォントは、Typefaceオブジェクトとして読み込んで、Paintに設定します。

リスト1.18: assetsからフォントを読み込んで設定する
Typeface typeface = Typeface.createFromAsset(getContext().getAssets(),
                    "NotoSansJP-Thin.otf");
mPaint.setTypeface(typeface);

canvas.drawText("Hello Canvas!!", 0, 0, mPaint);
図1.16: 上がシステムのデフォルト、下がNotoSansJP-Thin.otf
図1.16: 上がシステムのデフォルト、下がNotoSansJP-Thin.otf

Typefaceオブジェクトとして読み込める形式として、OTF (Open Type Font)TTF (True Type Font)などがあります。

余談になりますが、TypefaceオブジェクトはTextViewにもsetTypefaceメソッドを使って設定できます。Canvasだけでなく、Androidのウィジットでも独自のフォントを表示することは出来ます。

ただし、フォントをアプリに組み込むとそれだけでアプリのサイズが大きくなってしまいますし、余り多くのフォントを使うとユーザーの混乱を招く可能性があります。あくまで統一されたデザインの中で必要に応じてフォントを適用するのが良いでしょう。

また、フォントによってはアプリへの組み込みを認めていないものもあるので、組み込みに当たってはライセンスを確認するなどの注意が必要です。

Canvasを使いこなす

さて、ここまででCanvasを使った基本的な描画を見てきました。

ここからは、Canvasそのものの設定を紹介します。

translate

translateは、Canvasの原点を変更します。

通常、Canvasの座標は、左上を(0, 0)として描画しますが、translateメソッドを使うと原点を別の座標に設定できます。

リスト1.19: 原点を移動しながら5つの円を描く
for(int i=0; i < 5; i++) {
    canvas.drawCircle(0, 0, 50, mPaint);
    canvas.translate(100, 100);
}
図1.17: 5つの円が描かれている
図1.17: 5つの円が描かれている

図1.17はリスト1.19を実行した結果です。

最初の円を描画する時点で原点は(0, 0)の状態です。円の描画で指定する座標は円の中心座標なので、(0, 0)を中心として円を描画した結果、円は4分の1しか表示されません。

以降、原点をxy方向にそれぞれ100ずつ移動しています。translateはその時点の原点からの移動になりますから、5回目の円が描かれた時点で、xy方向にそれぞれ400、原点が移動した状態になります。

このようにして、原点を移動させることでそれぞれの描画で座標を計算する必要がなくなります。これは、スクロールなどの処理を追加したいときや、後述するrotateで回転軸を変えるときなどに便利です。

rotate

rotateを設定すると、Canvasを回転した状態で描画できます。

rotateで回転するのは、それまで描画してきた内容ではなく、rotateの設定以降に描画する内容である点に注意してください。

リスト1.20: 10°ずつ回転して36個の正方形を描画する
Rect rect = new Rect(100, 0, 150, 50);

for (int i = 0; i < 36; i++) {
    canvas.rotate(10);
    canvas.drawRect(rect, mPaint);
}
図1.18: 左上を軸として回転している
図1.18: 左上を軸として回転している

rotateメソッドで回転する軸になるのは、そのCanvasの原点となる座標です。したがって、translateメソッドで原点を変更していない場合は標準の原点(0, 0)となり、左上を中心に回転することになります(リスト1.20)。

図1.19は、描画前にtranslateメソッドで原点を(200, 200)に移動した場合の結果です。

このように、回転軸が変わって、全ての描画が画面の中に収まっています。

図1.19: 回転軸が変わっている
図1.19: 回転軸が変わっている

scale

scaleは、Canvasに描画する際の倍率を指定して、描画を一度で拡大、縮小できます。

rotate同様、scaleで拡大縮小されるのは、それまで描画してきた内容ではなく、scaleの設定以降に描画する内容である点に注意してください。

リスト1.21: 縦横2.5倍ずつで描画
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

canvas.drawBitmap(bmp, 0, 0, mPaint);

canvas.scale(2.5f, 2.5f);
canvas.drawBitmap(bmp, 0, 0, mPaint);

canvas.scale(2.5f, 2.5f);
canvas.drawBitmap(bmp, 0, 0, mPaint);
図1.20: 同じ画像で大きさが異なる
図1.20: 同じ画像で大きさが異なる

skew

skewは、Canvasに描画する際の歪みを指定します。textSkewはX方向のみの歪みしか設定できませんでしたが、Canvasではxy方向で歪みを設定できます。

リスト1.22:
canvas.drawRect(0, 0, 100, 100, mPaint);

canvas.translate(100, 100);
canvas.skew(0.2f, -0.8f);

canvas.drawRect(0, 0, 100, 100, mPaint);
図1.21: 短径が歪んで描画される
図1.21: 矩形が歪んで描画される

save/restore

saveメソッドを使うと、Canvasの現在の設定を保存して、あとから復元できます。

例えば、これまで見てきたtranslaterotatescaleskewなどのメソッドは、繰り返し使うとその値は加算されていきました。

rotate(90)を2回実行すると、rotate(180)を実行した時と同じ状態になります。このような場合、全く元の状態に戻すにはrotate(90)のあとにrotate(-90)をしなければなりません。

そこで、saveメソッドを使うと現在のCanvasの状態を保存できます。戻り値として得られるint型の値は保存した設定を示す値です。

最後にsaveした際の設定に戻すときはrestoreメソッドを使います。保存した設定を指定して復元したいときは、restoreToCountメソッドを使います。

リスト1.23: saveで状態を保存、restoreで復元
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

canvas.translate(100, 100);
canvas.save();

canvas.rotate(45);
canvas.drawBitmap(bmp, 0, 0, mPaint);

canvas.rotate(45);
canvas.drawBitmap(bmp, 0, 0, mPaint);

canvas.restore(); // save直前に戻る

canvas.drawBitmap(bmp, 0, 0, mPaint);
図1.22:
図1.22:

リスト1.23では、translateで原点を移動した後にsaveメソッドを実行して45°ずつ傾けて画像を二つ描画したあと、restoreメソッドでCanvasの状態を復元、最後に画像を描画しています。

図1.22の通り、45°、90°と回転した2つの画像に被さって回転していない画像が描画されていることで、rotateの設定がリセットされているのがわかります。

ここでは、rotateはリセットされていてもsave前に実行しているtranslateの効果が残っていることに注意してください。

SAVE_FLAG

saveメソッドにフラグを設定することで、保存対象の状態を限定することが出来ます。

表1.2: Canvas.*_SAVE_FLAG
フラグ 説明
MATRIX_SAVE_FLAG Matrix情報(translate, rotate, scale, skew)の情報を保存
CLIP_SAVE_FLAG クリップ領域を保存
HAS_ALPHA_LAYER_SAVE_FLAG アルファ(不透明度)レイヤーを保存
FULL_COLOR_LAYER_SAVE_FLAG カラーレイヤーを保存
CLIP_TO_LAYER_SAVE_FLAG クリップレイヤーとして保存
ALL_SAVE_FLAG 全ての状態を保存する
saveLayer

saveLayerメソッドは、saveメソッドと似ていますが、saveメソッドがCanvas全体に影響を与えるのに対して、saveLayerメソッドはCanvas中で指定された範囲の中だけに描画する点で異なります。

リスト1.24では、Canvasの一部の範囲を指定してsaveLayerメソッドを実行しています。

リスト1.24: saveLayerで領域の一部をクリッピング
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);

    RectF bounds = new RectF(0, 0, 300, 300);
    canvas.saveLayer(bounds, mPaint, Canvas.CLIP_TO_LAYER_SAVE_FLAG);

    canvas.drawColor(Color.GREEN);

    canvas.drawBitmap(bmp, 100, 200, mPaint);

    canvas.restore(); // saveLayer直前に戻る

    canvas.drawBitmap(bmp, 200, 200, mPaint);
図1.23:
図1.23:

続いて、drawColorで緑色に塗りつぶしていますが、図1.23にある通り、予めsaveLayerで設定した範囲しか塗りつぶしていません。

そして、restoreメソッドで復元後に描画した画像はsaveLayerで設定した範囲を超えて描画できています。このようにsaveLayerを設定すると、Canvasの指定された範囲に限定して描画処理を実行します。

saveLayerを実行すると、指定した範囲に対応するバッファ(off-screen buffer)が確保され、以降restoreメソッドが実行されるまでは、そのバッファに対して描画を行います。こうすることで、複雑な描画やアニメーション時のパフォーマンスを向上できます4)http://developer.android.com/guide/topics/graphics/hardware-accel.html#layers

また、saveLayerAlphaメソッドのように、レイヤーそのものにアルファ値(不透明度)を設定できるものもあり、活用の余地は大きいと筆者は考えています。

なお、saveLayerメソッドを実行する際には、フラグとしてCanvas.CLIP_TO_LAYER_SAVE_FLAGを設定しない場合、クリップされていないレイヤーとして認識されるため、描画パフォーマンスが低下するので注意が必要です5)Google I/O 2013 - Android Graphics Performance

1.3 まとめ

いかがでしたか?普段はあまり使わないCanvasですが、使い方は単純なのでグッと身近に感じるのではないでしょうか。

次回はもう一歩進んで「「PorterDuff Mode」の活用法について解説します。PorterDuff Modeを使うと、ある画像の一部をくりぬいて表示するなど、非常に柔軟な描画や表現が可能になります。

 

【修正 2014/11/25】
2014/11/25: fix typos. s/短径/矩形/