onDraw() — 定義、呼び出し機構とメソッドのオーバライド

著者: IT Sectr 公開日: 2026-07-22 読了時間: 10 分

onDraw() は、Android SDK の View クラスの主要なメソッドで、ビューのコンテンツを描画する責任を担います。システムは View の外観が更新されるたびに onDraw を呼びます — 初期レンダリング中、invalidate() の後、またはサイズが変わったときです。Android Developers Documentation (2026) によると、Canvas API を使用した onDraw のオーバライドは、Android アプリケーションでカスタムグラフィックを作成する主な方法です。

まとめ

  • onDraw(Canvas) — Canvas 上にコンテンツを描画するためにシステムが呼ぶ View メソッド
  • invalidate() — View の再描画リクエスト、次のフレームサイクルで onDraw を呼び出す
  • Canvas API は drawCircle、drawBitmap、drawText、drawPath などの描画メソッドを提供する
  • Android システムは UI スレッドで描画を実行するため、onDraw はライトで高速である必要がある
  • Hardware acceleration は Android 3.0 (API 11) より GPU を通じて描画を自動的に加速する

onDraw() とは?

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 のオーバライドはオプショナルで、標準コンテンツの上に追加のグラフィックを追加するために使用されます。

View の描画サイクル

Android は、メインスレッド(UI スレッド)から呼ばれる厳密に定義された順序で View の描画を実行します。完全なサイクルは3つのフェーズから成ります: measure(寸法測定)、layout(位置計算)、draw(レンダリング)。onDraw は draw フェーズの一部であり、View の寸法と位置が決定された後に呼ばれます。

Draw フェーズ: dispatchDraw と onDraw

draw(Canvas canvas) は View の公開メソッドで、onDraw と dispatchDraw を呼びます。draw メソッドはオーバライド用に設計されていません — 代わりに onDraw がオーバライドされます。現在の View で onDraw が実行された後、dispatchDraw が呼ばれ、すべてのチャイルド View に対して再帰的に draw を呼び出します(現在の View が ViewGroup コンテナである場合)。

システムが onDraw を呼ぶタイミング

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 API

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 オブジェクト

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 のパフォーマンス

onDraw はメイン UI スレッドで実行され、その所要時間はフレームレート (fps) に直接影響します。60 fps を維持するには、onDraw メソッドは 16 ミリ秒以内に完了する必要があります。遅延があるとフレームドロップ(jank)が発生し、アニメーションのもじれやスクロールの遅延として視覚的に現れます。

onDraw のパフォーマンスルール

主なルール onDraw の最適化には次のようなことがあります: 1) onDraw 内でオブジェクトを作成しない — すべての Paint、Path、Rect オブジェクトはクラスフィールドにする; 2) メモリを割り当てない (new) — これはガーベジコレクション (GC) とフレームドロップの原因になる; 3) 複雑な操作を Bitmap.createBitmap で Bitmap にキャッシュし、準備した Bitmap を描く; 4) I/O 操作やリソースの読み込みを行わない。

clipRect を使った最適化

clipRect は描画範囲を指定された矩形に制限します。可視領域以外のピクセルを描くのに時間を消うのを防げます。特に大きなリストをスクロールするときやズームするときに効果的です: clipRect を可視領域(dirty rect)に設定すると、Canvas は見えているものだけを描きます。

レイヤー — 保存と復元

canvas.save() は現在の Canvas の状態(変換と clip)をスタックに保存します。canvas.restore() は最後に保存した状態を復元します。一時的な変換に必要です: 状態を保存し、translate/rotate を適用し、描き、その後復元します。save と restore は軽量で、適切に使用した場合(10–15 レベルのネスト以下)パフォーマンスに影響しません。

invalidate と postInvalidate

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 のコード例

Kotlin を使って Android でカスタム View を作成するための onDraw のオーバライドの実践例を見てみましょう。

カスタム View — 円形プログレス

この例は、アニメーション付きの円形プログレスインジケーターを作成します。onDraw は2つの円を描きます: 灰色の背景円と、当前の進行状況によって弧が決定される色付きの塗り円。setProgress で invalidate を呼ぶと、値が変わったときに再描画されます。

kotlin
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)
    }
}

カスタム View — テキスト付きグラデーション背景

この例は、グラデーション付きの矩形とその上にテキストを描きます。グラデーションは LinearGradient (Shader) で作成され、テキストは drawText で描かれます。両方の Paint オブジェクトはコンストラクタで一度作成され、onDraw は現在の View の幅に基づいて矩形のサイズを更新するだけです。

kotlin
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)
    }
}

invalidate を使ったアニメーション時計

この例は、1000 ミリ秒ごとに描画を更新するアニメーション時計を作成します。onDraw 内の postDelayed は 1 秒後に invalidate を呼び、無限の再描画ループを作ります。時計の振り子は、異なる長さと太さで drawLine によって描かれます。

kotlin
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 を直接呼ぶことはできますか?

いいえ、onDraw を直接呼ぶべきではありません。再描画をリクエストするには、invalidate()(UI スレッドから)または postInvalidate()(バックグラウンドスレッドから)を使用してください。システムが onDraw を呼ぶタイミングを決め、複数の invalidate をひとつのパスにまとめて描画を最適化します。

invalidate 後に onDraw が呼ばれないのはなぜ?

原因: View が非表示になっている (setVisibility(GONE) または INVISIBLE); View が Window 階層に追加されていない; View のサイズが 0 (幅または高さ = 0); setWillNotDraw(true) が呼ばれている (ViewGroup ではデフォルトで true)。デバッグ時にはこれらの各項目を確認してください。

onDraw はどのくらいの頻度で呼ばれますか?

60 fps では、onDraw は秒間に最大 60 回呼ばれます。120 fps (120 Hz ディスプレイのデバイス)では — 最大 120 回。各呼出しは約10–16 ミリ秒以内に完了する必要があります。ObjectAnimator または ValueAnimator によるアニメーション中は、頻度はアニメーションのフレーム数によって決定されます。

onDraw と dispatchDraw の違いは?

onDraw は View 自体のコンテンツを描画します。dispatchDraw はチャイルド View (ViewGroup 内) を描画します。dispatchDraw は onDraw の後に呼ばれ、すべてのチャイルド要素を再帰的にトレースします。普通の View(ViewGroup 以外)では、dispatchDraw は空です。

Canvas を onDraw 以外で使用できますか?

はいCanvas(bitmap) で Bitmap 用の Canvas を作成し、どのスレッドでも描画できます。これはバックグラウンドで複雑なグラフィックをプレレンダリングするために使用されます。ただし、onDraw に渡される Canvas はシステムに属し、onDraw メソッド内の UI スレッドでのみ使用できます。

まとめ

  • onDraw(Canvas) — Android で Canvas API を使ったカスタム描画のための主な View メソッド
  • 描画サイクル は measure、layout、draw のフェーズから成る — onDraw は draw フェーズに呼ばれる
  • Canvas API には drawCircle、drawRect、drawPath、drawBitmap、drawText などが含まれる
  • invalidate() は UI スレッドから再描画をリクエストし、postInvalidate() はバックグラウンドスレッドからリクエストする
  • 部分再描画 invalidate(Rect) により、dirty 領域のみを更新してレンダリングを最適化する
  • onDraw のパフォーマンス は気をつけるべき点: メソッド内でのオブジェクト作成は GC とフレームドロップの原因
  • ハードウェア加速 (API 11 より) GPU を通じて Canvas 操作を自動的に加速する

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください