onDraw() は、Android SDK の View クラスの主要なメソッドで、ビューのコンテンツを描画する責任を担います。システムは View の外観が更新されるたびに onDraw を呼びます — 初期レンダリング中、invalidate() の後、またはサイズが変わったときです。Android Developers Documentation (2026) によると、Canvas API を使用した onDraw のオーバライドは、Android アプリケーションでカスタムグラフィックを作成する主な方法です。
まとめ
onDraw(Canvas canvas) は android.view.View クラスの protected メソッドで、View を描画する必要があるときに Android システムが呼び出します。開発者は、カスタム描画(形、テキスト、画像、グラデーション、アニメーション)を実装するためにこのメソッドをオーバライドします。Canvas パラメータは、すべての描画操作が実行されるグラフィカル表面です。
メソッドのシグネチャはシンプルです: override fun onDraw(canvas: Canvas)。このメソッド内では、新しいオブジェクトを作成したり、長時間の操作を実行したり、ネットワークにアクセスしたりするべきではありません(GC のオーバーヘッドを避けるため)。View は、onLayout(位置計算)と dispatchDraw(チャイルド要素の描画)の間のライフサイクルに onDraw メソッドも含みます。
View から継承する場合、すべてのコンテンツを表示するには onDraw のオーバライドが必須です — さもなければ View は空になります。既存のサブクラス(TextView、ImageView、Button)から継承する場合、onDraw のオーバライドはオプショナルで、標準コンテンツの上に追加のグラフィックを追加するために使用されます。
Android は、メインスレッド(UI スレッド)から呼ばれる厳密に定義された順序で View の描画を実行します。完全なサイクルは3つのフェーズから成ります: measure(寸法測定)、layout(位置計算)、draw(レンダリング)。onDraw は draw フェーズの一部であり、View の寸法と位置が決定された後に呼ばれます。
draw(Canvas canvas) は View の公開メソッドで、onDraw と dispatchDraw を呼びます。draw メソッドはオーバライド用に設計されていません — 代わりに onDraw がオーバライドされます。現在の View で onDraw が実行された後、dispatchDraw が呼ばれ、すべてのチャイルド View に対して再帰的に draw を呼び出します(現在の View が ViewGroup コンテナである場合)。
onDraw は次の条件で呼ばれます: 1) View が初めて可視になった場合(setVisibility(VISIBLE) の後); 2) アプリがこの View またはその親で invalidate() を呼んだ場合; 3) View のサイズが変わった場合(新しい layout); 4) View に関連するアニメーションが変わった場合(ValueAnimator または ObjectAnimator を通じて); 5) View の状態が変わった場合(pressed、focused、enabled)。
Android 3.0 (API 11) 以降、多くの Canvas 操作は GPU を通じてハードウェア加速されます。開発者は canvas.isHardwareAccelerated() で加速が有効かどうか確認できます。ハードウェア加速では、いくつかの Canvas 操作(例えば非矩形領域での clipPath)がサポートされず、無視されたりエラーの原因になったりします。
Canvas は豊富な描画メソッドセットを提供します。すべてのメソッドは、変換(translate、rotate、scale、skew)によって変更できる現在の坐標系で動作します。各メソッドは、色、スタイル、線の太さ、効果、アンチエイリアスを定義する Paint オブジェクトを受け入れます。
| Canvas メソッド | 目的 | Paint の主なパラメータ |
|---|---|---|
| drawCircle | 円を描く | centerX, centerY, radius, paint |
| drawRect | 矩形を描く | left, top, right, bottom, paint |
| drawLine | 線を描く | startX, startY, endX, endY, paint |
| drawPath | 任意のパスを描く | path, paint |
| drawBitmap | ビットマップ画像を描く | bitmap, srcRect, dstRect, paint |
| drawText | テキストを描く | text, x, y, paint |
| drawArc | 弧またはセクタを描く | ovalRect, startAngle, sweepAngle, useCenter, paint |
| drawOval | 楓円を描く | ovalRect, paint |
Paint は描画スタイルを定義します: 色 (setColor)、線の太さ (setStrokeWidth)、塗りつぶしスタイル (setStyle — FILL、STROKE、FILL_AND_STROKE)、アンチエイリアス (setAntiAlias)、効果 (setShadowLayer、setMaskFilter)、グラデーション (setShader)。描画ループでのオブジェクト作成を避けるため、Paint は onDraw 内ではなく、View のコンストラクタで一度作成および構成することを推奨します。
Canvas は、X 軸が右向き、Y 軸が下向きの坐標系を使用します。原点 (0, 0) は View の左上の角です。draw メソッドを呼ぶ前に、開発者は canvas.translate(dx, dy)、canvas.rotate(degrees, px, py)、canvas.scale(sx, sy, px, py) で坐標系を変更できます。その後の描画操作はすべて変換後の系で実行されます。
onDraw はメイン UI スレッドで実行され、その所要時間はフレームレート (fps) に直接影響します。60 fps を維持するには、onDraw メソッドは 16 ミリ秒以内に完了する必要があります。遅延があるとフレームドロップ(jank)が発生し、アニメーションのもじれやスクロールの遅延として視覚的に現れます。
主なルール onDraw の最適化には次のようなことがあります: 1) onDraw 内でオブジェクトを作成しない — すべての Paint、Path、Rect オブジェクトはクラスフィールドにする; 2) メモリを割り当てない (new) — これはガーベジコレクション (GC) とフレームドロップの原因になる; 3) 複雑な操作を Bitmap.createBitmap で Bitmap にキャッシュし、準備した Bitmap を描く; 4) I/O 操作やリソースの読み込みを行わない。
clipRect は描画範囲を指定された矩形に制限します。可視領域以外のピクセルを描くのに時間を消うのを防げます。特に大きなリストをスクロールするときやズームするときに効果的です: clipRect を可視領域(dirty rect)に設定すると、Canvas は見えているものだけを描きます。
canvas.save() は現在の Canvas の状態(変換と clip)をスタックに保存します。canvas.restore() は最後に保存した状態を復元します。一時的な変換に必要です: 状態を保存し、translate/rotate を適用し、描き、その後復元します。save と restore は軽量で、適切に使用した場合(10–15 レベルのネスト以下)パフォーマンスに影響しません。
invalidate() は View のメソッドで、View のコンテンツが古くなり再描画が必要であることをシステムに知らせます。invalidate を呼ぶと、onDraw が呼ばれる新しい draw サイクルが開始されます。invalidate は UI スレッド(メインスレッド)からのみ呼ぶ必要があります。バックグラウンドスレッドから呼ぶには postInvalidate を使用します。
postInvalidate() は invalidate のスレッドセーフなバージョンで、Handler を通じてメインスレッドのキューに再描画リクエストを送信します。データ読み込みや計算の完了後に View を更新する必要があるバックグラウンドタスクで使用されます。UI の同期には runOnUiThread と組み合わせることができます。
Partial redrawing には、invalidate(Rect dirty) または invalidate(int l, int t, int r, int b) を使用します。これにより、View 全体ではなく、指定された領域のみが変化したことをシステムに知らせます。Android は dirty rect のみを再描画してレンダリングを最適化し、部分更新を伴うアニメーションのパフォーマンスを大幅に向上させます。
Kotlin を使って Android でカスタム View を作成するための onDraw のオーバライドの実践例を見てみましょう。
この例は、アニメーション付きの円形プログレスインジケーターを作成します。onDraw は2つの円を描きます: 灰色の背景円と、当前の進行状況によって弧が決定される色付きの塗り円。setProgress で invalidate を呼ぶと、値が変わったときに再描画されます。
class CircularProgressView
@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val bgPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 12f
color = Color.parseColor("#E0E0E0")
isAntiAlias = true
}
private val progressPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = 12f
color = Color.BLUE
isAntiAlias = true
strokeCap = Paint.Cap.ROUND
}
var progress: Float = 0f
set(value) {
field = value.coerceIn(0f, 1f)
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val size = minOf(width, height) - 24f
val oval = RectF(12f, 12f,
12f + size, 12f + size)
canvas.drawArc(oval, 270f, 360f, false, bgPaint)
canvas.drawArc(oval, 270f,
progress * 360f,
false, progressPaint)
}
}
この例は、グラデーション付きの矩形とその上にテキストを描きます。グラデーションは LinearGradient (Shader) で作成され、テキストは drawText で描かれます。両方の Paint オブジェクトはコンストラクタで一度作成され、onDraw は現在の View の幅に基づいて矩形のサイズを更新するだけです。
class GradientTextView(context: Context)
: View(context) {
private val shaderPaint = Paint().apply {
isAntiAlias = true
}
private val textPaint = Paint().apply {
color = Color.WHITE
textSize = 48f
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val gradient = LinearGradient(
0f, 0f, width.toFloat(), 0f,
Color.parseColor("#FF6B35"),
Color.parseColor("#FF0040"),
Shader.TileMode.CLAMP
)
shaderPaint.shader = gradient
canvas.drawRect(0f, 0f,
width.toFloat(),
height.toFloat(), shaderPaint)
val xCenter = width / 2f
val yCenter = height / 2f
val yOffset = (textPaint.descent() +
textPaint.ascent()) / 2f
canvas.drawText("Hello from Canvas",
xCenter, yCenter - yOffset,
textPaint)
}
}
この例は、1000 ミリ秒ごとに描画を更新するアニメーション時計を作成します。onDraw 内の postDelayed は 1 秒後に invalidate を呼び、無限の再描画ループを作ります。時計の振り子は、異なる長さと太さで drawLine によって描かれます。
class AnalogClockView(context: Context)
: View(context) {
private val handPaint = Paint().apply {
style = Paint.Style.STROKE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 20f
val now = Calendar.getInstance()
val hours = now.get(Calendar.HOUR) % 12
val minutes = now.get(Calendar.MINUTE)
val seconds = now.get(Calendar.SECOND)
val secondAngle = Math.toRadians(
seconds * 6f - 90f)
handPaint.strokeWidth = 2f
handPaint.color = Color.RED
canvas.drawLine(cx, cy,
cx + cos(secondAngle) * radius * 0.8f,
cy + sin(secondAngle) * radius * 0.8f,
handPaint)
postDelayed({ invalidate() }, 1000)
}
}
よくある質問
いいえ、onDraw を直接呼ぶべきではありません。再描画をリクエストするには、invalidate()(UI スレッドから)または postInvalidate()(バックグラウンドスレッドから)を使用してください。システムが onDraw を呼ぶタイミングを決め、複数の invalidate をひとつのパスにまとめて描画を最適化します。
原因: View が非表示になっている (setVisibility(GONE) または INVISIBLE); View が Window 階層に追加されていない; View のサイズが 0 (幅または高さ = 0); setWillNotDraw(true) が呼ばれている (ViewGroup ではデフォルトで true)。デバッグ時にはこれらの各項目を確認してください。
60 fps では、onDraw は秒間に最大 60 回呼ばれます。120 fps (120 Hz ディスプレイのデバイス)では — 最大 120 回。各呼出しは約10–16 ミリ秒以内に完了する必要があります。ObjectAnimator または ValueAnimator によるアニメーション中は、頻度はアニメーションのフレーム数によって決定されます。
onDraw は View 自体のコンテンツを描画します。dispatchDraw はチャイルド View (ViewGroup 内) を描画します。dispatchDraw は onDraw の後に呼ばれ、すべてのチャイルド要素を再帰的にトレースします。普通の View(ViewGroup 以外)では、dispatchDraw は空です。
はい、Canvas(bitmap) で Bitmap 用の Canvas を作成し、どのスレッドでも描画できます。これはバックグラウンドで複雑なグラフィックをプレレンダリングするために使用されます。ただし、onDraw に渡される Canvas はシステムに属し、onDraw メソッド内の UI スレッドでのみ使用できます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。