dispatchDraw()とは何か、呼び出しメカニズムとonDrawとの違い

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

dispatchDraw() はViewGroupクラスのメソッドで、Androidの階層構造におけるすべての子Viewの再帰的な描画を担当します。Android Developers Documentation (2026) によると、dispatchDrawは親Viewのdraw()メソッド内でonDrawの後に自動的に呼び出され、すべての子要素を走査してそれぞれのdraw()を呼び出します。開発者はカスタムViewGroupでdispatchDrawをオーバーライドして、エフェクトの追加、子要素へのグラフィックスの重ね合わせ、描画順序の変更などを行います。

重要ポイント

  • dispatchDraw(Canvas canvas) はViewGroupのprotectedメソッドで、子Viewを描画するためにonDrawの後に呼び出される
  • draw(Canvas canvas) はViewのpublic finalメソッドで、onDraw、dispatchDraw、onDrawForegroundを順に呼び出す
  • onDraw はView自身のコンテンツを描画し、dispatchDraw はすべての子要素を描画する
  • setWillNotDraw(true) は自身のコンテンツを描画せず子要素のみを描画するViewGroupの最適化
  • dispatchDrawのオーバーライドはエフェクト(blur、overlay)の適用や子要素の描画アニメーションに使用される

dispatchDraw()とは?

dispatchDraw(Canvas canvas) はViewGroupクラスのprotectedメソッドで、Androidシステムが現在のコンテナのすべての子Viewを描画するために呼び出します。dispatchDrawは標準的な描画パイプラインの一部です。最初にonDraw(View自身のコンテンツの描画)、次にdispatchDraw(子の再帰的描画)、最後にonDrawForeground(フォアグラウンドとスクロールバーの描画)が実行されます。このメソッドはアプリケーションコードから直接呼び出すことを想定していません。

ViewGroupの標準的な dispatchDraw の実装は、すべての子Viewを走査し、可視性を確認してそれぞれに draw(Canvas) を呼び出します。走査順序は子のインデックス(0からchildCount - 1まで)に対応します。子Viewが重なる場合、リストの後ろにあるものが前のものの上に描画されます。dispatchDrawで順序を変更すると、描画のZ-orderを変更できます。

View(ViewGroupではない)の場合、dispatchDrawメソッドは空です。通常のViewには子要素がないため、描画するものはありません。このメソッドは基本Viewクラスに存在しますが、ViewGroupインスタンスでのみ呼び出されます。開発者はdispatchDrawのデフォルト動作を通じてViewに子要素があるかどうかを確認できますが、多くの場合は instanceof ViewGroup をチェックする方が簡単です。

描画パイプライン:onDraw、dispatchDraw、onDrawForeground

Viewクラスの draw(Canvas) メソッドは3段階の描画パイプラインを編成します。第1段階は onDraw(canvas) の呼び出しで、Viewが自身のコンテンツを描画します。第2段階は dispatchDraw(canvas) の呼び出しで、子Viewの描画を開始します。第3段階は onDrawForeground(canvas) で、フォアグラウンドレイヤー、スクロールバー、リップルエフェクトを担当します。この順序によりグラフィカルレイヤーの正しい重なり順序が保証されます。

onDraw は常にdispatchDrawより前に実行されます。つまり、親Viewのコンテンツは子Viewの下に表示されます。子の上に何かを描画する必要がある場合は、onDrawForegroundで行うか、オーバーライドしたdispatchDrawで super.dispatchDraw(canvas) を呼び出してから上に描画します。背景はonDrawよりも前、draw()メソッド内のdrawBackground(canvas)で描画されます。

ハードウェアアクセラレーション を使用すると、描画パイプラインはGPUを介して動作します。ViewはDisplayList(キャッシュされて再利用される描画コマンドのリスト)に描画されます。ハードウェアアクセラレーションモードでは、dispatchDrawは子ViewのDisplayListをシーンの全体DisplayListに追加します。dispatchDrawを変更するとDisplayListが無効になり、キャッシュの再構築が発生してパフォーマンスに影響を与える可能性があります。

メソッド呼び出し順序目的
drawBackground1背景の描画(background drawable)
onDraw2View自身のコンテンツの描画
dispatchDraw3すべての子Viewの描画(ViewGroupのみ)
onDrawForeground4フォアグラウンド、スクロールバー、リップルエフェクトの描画

dispatchDrawとonDrawの違い

onDraw はView自身のコンテンツ(この特定の要素に属する図形、テキスト、画像)の描画を担当します。 dispatchDraw は子View(ViewGroup内に含まれるすべての要素)の描画を担当します。ViewGroupがdispatchDrawをオーバーライドしない場合、ViewGroupの実装が使用され、childCountを再帰的に走査して各子のdrawを呼び出します。

自身のコンテンツを描画しない ViewGroup(FrameLayout、LinearLayoutなど)の場合、setWillNotDraw(true) を設定することでonDrawを最適化できます。この場合、onDrawは決して呼び出されず、リソースを節約できます。dispatchDrawは引き続き動作し、子要素のdrawを呼び出します。すべての標準的なViewGroup(LinearLayout、RelativeLayout、ConstraintLayout)はsetWillNotDraw(true)を使用します。

super.dispatchDraw(canvas) を呼び出さずに dispatchDraw をオーバーライドすると、子Viewは描画されません。これはすべての子を一時的に非表示にするのに役立つ場合がありますが、ほとんどの場合エラーを引き起こします。 推奨される方法:オーバーライドしたメソッドの先頭でsuper.dispatchDraw(canvas)を呼び出し、その後に子の上に独自のグラフィックス(アルファチャンネル付きオーバーレイなど)を追加します。

KotlinでのdispatchDrawオーバーライドの例

OverlayViewGroup を作成しましょう。これはすべての子Viewの上にテキストラベル付きの半透明レイヤーを追加するカスタムViewGroupです。dispatchDrawは最初にsuperを呼び出し(すべての子を描画)、次にオーバーレイの四角形とテキストを描画します。 Paint はコンストラクタで作成され、描画ループでのアロケーションを回避します。

kotlin
class OverlayViewGroup(context: Context)
    : ViewGroup(context) {

    private val overlayPaint = Paint().apply {
        color = Color.parseColor("#66000000")
        style = Paint.Style.FILL
    }

    private val textPaint = Paint().apply {
        color = Color.WHITE
        textSize = 36f
        isAntiAlias = true
        textAlign = Paint.Align.CENTER
    }

    init {
        setWillNotDraw(false)
    }

    override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        canvas.drawRect(0f, 0f,
                        width.toFloat(),
                        height.toFloat(), overlayPaint)
        canvas.drawText("プレビューモード",
            width / 2f,
            height / 2f, textPaint)
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var top = t
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            val ch = child.measuredHeight
            child.layout(l, top, l + cw, top + ch)
            top += ch
        }
    }

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        val maxWidth = resolveSize(
            getChildAt(0).measuredWidth,
            widthMeasureSpec)
        var totalHeight = 0
        for (i in 0 until childCount) {
            totalHeight += getChildAt(i).measuredHeight
        }
        setMeasuredDimension(maxWidth,
            resolveSize(totalHeight, heightMeasureSpec))
    }
}

dispatchDrawによるアニメーションZ-order

次の例は、子要素の描画 順序の変更 を示しています。CircularRevealLayoutはdispatchDrawをオーバーライドし、子を逆順に描画して逆Z-order効果を作成します。アニメーションのために、アニメーション化されたoffset値に基づいて描画インデックスの循環シフトが追加されます。

kotlin
class ReverseOrderLayout(context: Context)
    : ViewGroup(context) {

    private var reverse = false

    override fun dispatchDraw(canvas: Canvas) {
        if (!reverse) {
            super.dispatchDraw(canvas)
            return
        }
        for (i in childCount - 1 downTo 0) {
            val child = getChildAt(i)
            if (child.visibility == GONE) continue
            drawChild(canvas, child, getDrawingTime())
        }
    }

    fun toggleReverse() {
        reverse = !reverse
        invalidate()
    }

    override fun onLayout(changed: Boolean,
                       l: Int, t: Int,
                       r: Int, b: Int) {
        var left = l
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val cw = child.measuredWidth
            child.layout(left, t,
                left + cw, t + child.measuredHeight)
            left += cw
        }
    }

    override fun onMeasure(widthMeasureSpec: Int,
                       heightMeasureSpec: Int) {
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        var totalWidth = 0
        for (i in 0 until childCount) {
            totalWidth += getChildAt(i).measuredWidth
        }
        setMeasuredDimension(
            resolveSize(totalWidth, widthMeasureSpec),
            resolveSize(getChildAt(0).measuredHeight,
                heightMeasureSpec))
    }
}

AndroidでdispatchDrawを使用するタイミング

dispatchDraw は、ViewGroup自身のコンテンツに影響を与えずに子Viewの描画プロセスに影響を与える必要があるシナリオで使用されます。主なシナリオ:すべての子の上に共通のエフェクト(オーバーレイ、シャドウ、グラデーション)を適用する、深度効果を作成するためにZ-orderを変更する、Canvas変換を介して子要素の出現や消滅をアニメーション化する。

子要素の 下にグラフィックス を適用する必要がある場合(背景効果)、onDrawを使用します。dispatchDrawはその後で呼び出されます。グラフィックスが子の上にあるべき場合は、最初にsuperを呼び出してからCanvas.drawを使用するdispatchDrawを使用します。グローバルな色や透明度の変更が必要な場合は、dispatchDrawで子の描画をラップするために canvas.saveLayerAlpha() を使用すると便利です。

推奨されない dispatchDrawの使用法:1)リアルタイムでの複雑なアニメーション描画(invalidateとonDrawを使用)、2)ViewGroupのスクリーンショット作成(buildDrawingCache() または View.draw(Canvas) を使用)、3)子Viewの変更(座標、サイズはonLayoutのタスクであり、dispatchDrawのタスクではありません)。dispatchDrawは視覚効果の適用のみを目的としています。

よくある質問

dispatchDrawをシステムなしで直接呼び出せますか?

いいえ、dispatchDrawはprotectedメソッドで、Androidシステムがパブリックなdraw()メソッド内から呼び出します。dispatchDrawの直接呼び出しは意味がありません。準備アクション(Canvasの保存、背景とフォアグラウンドの描画)を実行しないからです。代わりに、プログラムによる任意のCanvasへの描画には View.draw(Canvas) を使用してください。

カスタム描画の後にsuper.dispatchDrawを呼び出すとどうなりますか?

独自の描画操作の後に super.dispatchDraw(canvas) を呼び出すと、子Viewはカスタムグラフィックスの上に描画されます。これによりレイヤーの順序が変わります。最初にカスタムレイヤーが描画され、次に子が描画されます。逆の状況(子の上にグラフィックス)が必要な場合は、最初にsuper.dispatchDrawを呼び出し、次に独自の操作を行います。

dispatchDrawはパフォーマンスにどのような影響を与えますか?

dispatchDraw は各子Viewのdraw()を呼び出し、合計時間は子の数に比例します。ハードウェアアクセラレーションを使用する場合、dispatchDrawにCanvas操作を追加するとDisplayListが無効になり、キャッシュの再構築が発生する可能性があります。5~10個の子では影響は最小限ですが、50個以上の子では複雑なオーバーレイをキャッシュすることをお勧めします。

ViewでdispatchDrawが呼び出されないのはなぜですか?

View(ViewGroupではない)には子要素がないため、dispatchDrawは有用な作業を実行しません。ただし、ポリモーフィズムのために基本Viewクラスにメソッドが存在します。draw()内のコードは任意のViewに対してdispatchDrawを呼び出しますが、Viewの実装には子の描画ロジックが含まれていません。dispatchDrawをオーバーライドするのはViewGroupだけです。

dispatchDrawでCanvasを変更できますか?

はい、dispatchDrawはCanvasを受け取り、super.dispatchDrawを呼び出す前に変換(translate、rotate、scale)できます。これは子のセット全体を1つのユニットとしてアニメーション化するために使用されます。super.dispatchDrawの完了後は、Canvasを元の状態に戻すことをお勧めします。

まとめ

  • dispatchDraw(Canvas) はonDraw後にすべての子Viewを再帰的に描画するViewGroupメソッド
  • 描画パイプライン:drawBackground → onDraw → dispatchDraw → onDrawForeground
  • onDraw はView自身のコンテンツを描画し、dispatchDraw は子要素のコンテンツを描画する
  • setWillNotDraw(true) はonDrawをスキップしてViewGroupを最適化するが、dispatchDrawは引き続き動作する
  • dispatchDrawのオーバーライドはエフェクトの適用、Z-orderの変更、子のCanvas変換に使用される
  • super.dispatchDraw とカスタム描画の順序が、子の上に表示されるものを決定する
  • 通常のView(ViewGroupではない)の場合、dispatchDrawは空である — 子要素がないため

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

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

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

こちらもお読みください