dispatchDraw() はViewGroupクラスのメソッドで、Androidの階層構造におけるすべての子Viewの再帰的な描画を担当します。Android Developers Documentation (2026) によると、dispatchDrawは親Viewのdraw()メソッド内でonDrawの後に自動的に呼び出され、すべての子要素を走査してそれぞれのdraw()を呼び出します。開発者はカスタムViewGroupで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 をチェックする方が簡単です。
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が無効になり、キャッシュの再構築が発生してパフォーマンスに影響を与える可能性があります。
| メソッド | 呼び出し順序 | 目的 |
|---|---|---|
| drawBackground | 1 | 背景の描画(background drawable) |
| onDraw | 2 | View自身のコンテンツの描画 |
| dispatchDraw | 3 | すべての子Viewの描画(ViewGroupのみ) |
| onDrawForeground | 4 | フォアグラウンド、スクロールバー、リップルエフェクトの描画 |
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)を呼び出し、その後に子の上に独自のグラフィックス(アルファチャンネル付きオーバーレイなど)を追加します。
OverlayViewGroup を作成しましょう。これはすべての子Viewの上にテキストラベル付きの半透明レイヤーを追加するカスタムViewGroupです。dispatchDrawは最初にsuperを呼び出し(すべての子を描画)、次にオーバーレイの四角形とテキストを描画します。 Paint はコンストラクタで作成され、描画ループでのアロケーションを回避します。
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))
}
}
次の例は、子要素の描画 順序の変更 を示しています。CircularRevealLayoutはdispatchDrawをオーバーライドし、子を逆順に描画して逆Z-order効果を作成します。アニメーションのために、アニメーション化されたoffset値に基づいて描画インデックスの循環シフトが追加されます。
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))
}
}
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はprotectedメソッドで、Androidシステムがパブリックなdraw()メソッド内から呼び出します。dispatchDrawの直接呼び出しは意味がありません。準備アクション(Canvasの保存、背景とフォアグラウンドの描画)を実行しないからです。代わりに、プログラムによる任意のCanvasへの描画には View.draw(Canvas) を使用してください。
独自の描画操作の後に super.dispatchDraw(canvas) を呼び出すと、子Viewはカスタムグラフィックスの上に描画されます。これによりレイヤーの順序が変わります。最初にカスタムレイヤーが描画され、次に子が描画されます。逆の状況(子の上にグラフィックス)が必要な場合は、最初にsuper.dispatchDrawを呼び出し、次に独自の操作を行います。
dispatchDraw は各子Viewのdraw()を呼び出し、合計時間は子の数に比例します。ハードウェアアクセラレーションを使用する場合、dispatchDrawにCanvas操作を追加するとDisplayListが無効になり、キャッシュの再構築が発生する可能性があります。5~10個の子では影響は最小限ですが、50個以上の子では複雑なオーバーレイをキャッシュすることをお勧めします。
View(ViewGroupではない)には子要素がないため、dispatchDrawは有用な作業を実行しません。ただし、ポリモーフィズムのために基本Viewクラスにメソッドが存在します。draw()内のコードは任意のViewに対してdispatchDrawを呼び出しますが、Viewの実装には子の描画ロジックが含まれていません。dispatchDrawをオーバーライドするのはViewGroupだけです。
はい、dispatchDrawはCanvasを受け取り、super.dispatchDrawを呼び出す前に変換(translate、rotate、scale)できます。これは子のセット全体を1つのユニットとしてアニメーション化するために使用されます。super.dispatchDrawの完了後は、Canvasを元の状態に戻すことをお勧めします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。