invalidate() はAndroidのViewクラスのメソッドで、ビューを再描画が必要な状態としてマークします。invalidate()を呼び出すと、次の画面更新サイクルでビューの再描画がトリガーされ、カスタムコンポーネントの視覚的状態を更新する主要なメカニズムとなります。Android Developers Documentation(2025)によると、invalidate()は90%のカスタムViewでデータ変更を画面表示と同期するために使用されています。このメソッドは非同期で動作し、dirtyフラグを設定するだけで即座に制御を返します。
重要なポイント
invalidate() はandroid.view.Viewクラスのメソッドで、ビューの視覚的表現が古くなったことをAndroidシステムに通知します。メソッドを呼び出した後、システムはビューをdirtyとしてマークし、次の画面更新サイクル(通常60 FPSで16 ms)で再描画をスケジュールします。
invalidate()メソッドにはさまざまな形式があります:パラメータなし(完全再描画)、Rectパラメータ付き(部分)、ltrbパラメータ(left, top, right, bottom)付き。すべてのバージョンは非同期で動作し、UIスレッドから呼び出す必要があります。バックグラウンドスレッドからの呼び出しにはpostInvalidate()があります。
Androidの再描画メカニズムはViewRootImplに基づいています — View Hierarchyを描画用のSurfaceに接続する内部コンポーネントです。invalidate()が呼び出されると、ViewRootImplはビュー領域をdirtyとしてマークし、Choreographerを介して再描画リクエストを送信します — 画面リフレッシュレートに描画を同期するシステムサービスです。
ChoreographerはVsyncから信号を受け取り、measure、layout、drawの3段階のプロセスを開始します。ただし、invalidate()はdrawフェーズのみに影響し、requestLayout()が呼び出されない限りmeasureおよびlayoutフェーズは実行されません。これが重要な違いです:invalidate()はジオメトリを再計算しないため、requestLayout()よりも軽量です。
class CustomChartView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private var dataPoints: List<Float> = emptyList()
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
fun updateData(newPoints: List<Float>) {
dataPoints = newPoints
invalidate() // 再描画リクエスト
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.color = Color.BLUE
paint.strokeWidth = 4f
paint.style = Paint.Style.STROKE
// グラフの線を描画中
val path = Path()
dataPoints.forEachIndexed { index, value ->
val x = index * width / max(dataPoints.size - 1, 1)
val y = height - value * height
if (index == 0) path.moveTo(x, y)
else path.lineTo(x, y)
}
canvas.drawPath(path, paint)
}
}
この例では、グラフを描画するカスタムViewがデータ更新時にinvalidate()を呼び出します。システムは階層内の他の要素に影響を与えずにこのViewのみを再描画します。onDraw()はPathを介して線を描画するためのCanvasを受け取ります。
invalidate()とpostInvalidate()の主な違いはスレッドセーフティにあります。invalidate()はUIスレッド(メインスレッド)からのみ呼び出す必要があります。postInvalidate()は任意のスレッドから呼び出すことができ、Handlerを介してUIスレッドに再描画リクエストを送信します。
| 特性 | invalidate() | postInvalidate() |
|---|---|---|
| 呼び出しスレッド | UIスレッド(メインスレッド) | 任意のスレッド |
| メカニズム | dirtyフラグの直接更新 | Handler.post()を介してUIスレッドへ |
| レイテンシ | 最小、現在のサイクル内 | 次のUIスレッドサイクルまで |
| パフォーマンス | 高い | Handlerのわずかなオーバーヘッド |
| 推奨 | UIスレッドでは常にinvalidate() | バックグラウンドスレッドのみ |
実際には、postInvalidate()はネットワークデータの読み込み、センサー結果の処理、バックグラウンド計算などのシナリオで使用されます。UIスレッドにいる場合は、最小レイテンシのために常にinvalidate()を使用してください。
// UIスレッドから呼び出されました
view.invalidate()
// バックグラウンドスレッドから呼び出されました
Thread {
// 重い計算
val result = performHeavyCalculation()
runOnUiThread {
updateUi(result)
}
}.start()
invalidate(Rect) と invalidate(int l, int t, int r, int b) は再描画領域を制限することを可能にします。これはパフォーマンスにとって重要です:Viewの一部のみが変更される場合(カーソルの移動、インジケータの変更など)、ビュー全体を再描画する必要はありません。
システムは指定されたdirty矩形をonDraw()にcanvas.clipBoundsを介して渡します。onDraw()内でclipBoundsを確認し、その領域内でのみ描画できます。Android Canvasはdirty矩形の外側の描画を自動的にクリップします。
// 部分更新:カーソル領域のみ
private val cursorRect = Rect()
fun moveCursorTo(newX: Int, newY: Int) {
// 古い位置を無効化
invalidate(cursorRect)
cursorRect.set(newX - 5, newY - 5,
newX + 5, newY + 5)
// 新しい位置を無効化
invalidate(cursorRect)
}
部分再描画がない場合、カーソルが移動するたびにView全体が再描画され、大きなグラフでは数十ピクセルではなく数千ピクセルの再描画が必要になります。invalidate(Rect)はエディタ、描画キャンバス、アニメーションコンポーネントにとって必須のテクニックです。
よくある間違いは、invalidate()で十分な場所でrequestLayout()を呼び出すこと、またはその逆です。違いは根本的です:invalidate()はdrawフェーズのみに影響しますが、requestLayout()は完全なmeasure → layout → drawサイクルをトリガーします。
| 側面 | invalidate() | requestLayout() |
|---|---|---|
| サイクルのフェーズ | drawのみ | measure + layout + draw |
| 使用時 | レンダリングのみ変更(色、テキスト、グラフィック) | Viewのサイズまたは位置が変更 |
| パフォーマンス | 軽量 — 再描画のみ | 重量 — 階層の再計算 |
| 階層への影響 | 現在のViewのみ | 親コンテナに影響する可能性あり |
TextViewでテキストを変更する場合、Viewのサイズが変わらないためinvalidate()で十分です。テキストが改行されて高さが増える可能性がある場合は、requestLayout()が必要です。Android Lintはパフォーマンスルールを通じてこのようなエラーの追跡を支援します。
過剰なinvalidate()呼び出しは、AndroidのカスタムViewにおけるパフォーマンス低下の主な原因の1つです。最適化テクニックを見てみましょう。
データが高頻度(センサー、アニメーション、ビデオ)で更新される場合、変更のたびにinvalidate()を呼び出さないでください。画面リフレッシュレートに同期するには、ValueAnimatorまたはChoreographer.FrameCallbackを使用してください。これにより、invalidate()が1フレームに1回以上呼び出されないことが保証されます。
API 14以降、AndroidはGPUを介したハードウェアアクセラレーションをサポートしています。カスタムViewがCanvas API(drawRect、drawCircle、drawPath)のみを使用する場合、アクセラレーションは透過的に機能します。DisplayList互換の操作では、invalidate()の処理が大幅に高速化されます。
// Vsync同期にChoreographerを使用
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
updateAnimation(frameTimeNanos)
invalidate()
Choreographer.getInstance().postFrameCallback(this)
}
fun startAnimation() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
ターゲットを絞った更新にはinvalidate(Rect)を使用し、onDraw()からinvalidate()を呼び出すことを避け(無限ループ)、常にデバイス上でGPU Profile Renderingを使用してプロファイリングしてください。これにより各フレームの正確なレンダリング時間が表示され、問題のある領域を特定するのに役立ちます。
よくある質問
いいえ、onDraw()内でinvalidate()を呼び出すと、無限の再描画ループが発生します:onDraw()がinvalidate()を呼び出し、それが再びonDraw()をトリガーします。これによりCPU使用率が100%になり、フレームドロップが発生します。ValueAnimatorまたはChoreographerを介してアニメーションを使用してください。
invalidate()はUIスレッドでのみ動作し、dirtyフラグを即座に更新します。postInvalidate()はHandlerを介してUIスレッドにリクエストを送信し、任意のバックグラウンドスレッドから呼び出すことができます。UIスレッドにいる場合は、最小レイテンシのためにinvalidate()を使用してください。
はい、TextViewのsetText()メソッドは内部的にテキスト更新後にinvalidate()を呼び出します。テキストがViewの寸法を変更する場合は、requestLayout()も呼び出されます。標準ウィジェットを使用する場合、開発者が手動でinvalidate()を呼び出す必要はありません。
各invalidate()呼び出しは、次のVsync(16 msごと)で再描画をスケジュールします。onDraw()に16 ms以上かかる場合、フレームドロップが発生します。onDraw()を最適化してください — Bitmapをキャッシュし、アロケーションを避け、GPUレンダリングにハードウェアアクセラレーションを使用してください。
はい、Paintのプロパティ(色、太さ、スタイル)を変更した後は、invalidate()を呼び出す必要があります。ViewはPaintオブジェクトの変更を自動的に追跡しないためです。システムはPaintが変更されたことを認識せず、明示的なリクエストなしではonDraw()を呼び出しません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。