onMeasure() は、android.view.Viewクラスのprotectedメソッドであり、AndroidシステムがViewのサイズを決定するために呼び出します。システムはメソッドに2つのMeasureSpecオブジェクトを渡します。各オブジェクトには測定モード(EXACTLY、AT_MOST、またはUNSPECIFIED)と親コンテナが提案するサイズが含まれています。Android Developers Documentation(2026)によると、正確なサイズ制御が必要なすべてのカスタムViewおよびViewGroupでは、MeasureSpecを正しく処理するonMeasureのオーバーライドが必須です。
重要なポイント
onMeasure(int widthMeasureSpec, int heightMeasureSpec) はViewクラスのメソッドで、Androidシステムがビューの幅と高さを決定するために呼び出します。デベロッパーはこのメソッドをオーバーライドして、MeasureSpecで渡された制約に基づいてViewのサイズを指定します。正しいonMeasureのオーバーライドなしでは、カスタムViewが正しく表示されなかったり、まったく表示されなかったりする可能性があります。
システムはViewライフサイクルの measure フェーズ中にonMeasureを呼び出します。これはlayout(onLayout)とdraw(onDraw)フェーズの前にあります。ViewがonMeasureをオーバーライドしない場合、スーパークラスの実装が使用され、background drawableまたはlayout_paramsに基づいてデフォルトサイズが設定されます。super.onMeasure(widthMeasureSpec, heightMeasureSpec) の呼び出しは、TextViewやImageViewなどの標準的なViewサブクラスでのみ機能します。
onMeasureの重要な要件は、setMeasuredDimension(int, int) の呼び出しがメソッドの最後に必ず存在することです。この呼び出しがない場合、システムはIllegalStateExceptionをスローし、Viewが測定寸法を設定しなかったことを通知します。測定フェーズの完了後、最終的な寸法は getMeasuredWidth() および getMeasuredHeight() ゲッターを介して利用可能になります。
MeasureSpec は32ビットの整数で、上位2ビットが測定モードをエンコードし、下位30ビットがサイズをエンコードします。モードは、Viewが自身のサイズを選択する自由度を決定します。Androidは3つのモードを提供します:EXACTLY、AT_MOST、UNSPECIFIED。各モードはonMeasureで異なる処理ロジックを指示します。
| MeasureSpecモード | 値 | 動作 |
|---|---|---|
| EXACTLY | 親が正確なサイズを指定 | Viewは境界を超えない限り、指定されたサイズに正確に収まる必要がある |
| AT_MOST | 親が最大サイズを設定 | Viewは0から指定された最大値までの任意のサイズを選択できる |
| UNSPECIFIED | 親が制約を課さない | Viewは上限なしで任意の希望サイズを選択できる |
MeasureSpecからモードとサイズを抽出するには、MeasureSpec クラスの静的メソッドを使用します:MeasureSpec.getMode(int) は3つのモード(EXACTLY、AT_MOST、UNSPECIFIED)のいずれかを返し、MeasureSpec.getSize(int) はピクセル単位の数値サイズを返します。カスタムMeasureSpecを作成するには、MeasureSpec.makeMeasureSpec(int size, int mode) を使用します。これら3つのメソッドで、onMeasureでのサイズ操作のすべてのシナリオをカバーします。
標準パターン:モードがEXACTLYの場合は渡されたサイズを最終的なサイズとして使用します。AT_MOSTの場合は、希望サイズ(Viewのコンテンツ)と渡された最大値の最小値を選択します。UNSPECIFIEDの場合は、制約なしでViewの希望サイズを使用します。このパターンにより、親の制約に関係なく正しい動作が保証されます。
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val desiredWidth = 200
val desiredHeight = 100
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> minOf(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> minOf(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
標準ロジックを簡略化するために、Androidは resolveSizeAndState メソッドを提供しています。これは希望サイズとMeasureSpecを受け取り、正しいモードで最終サイズを返します。このメソッドは、上記のパターンを1行のコードで実装します。resolveSize(int size, int measureSpec) 関数も利用可能で、状態ビットなしのクリーンなサイズを返します。
Android は2パス測定アルゴリズムを使用して、階層内のすべてのViewが親の制約と子の設定を考慮して正しい寸法を取得することを保証します。最初のパスでは、親が制約付きのMeasureSpecを子Viewに渡し、子Viewが希望サイズを計算します。2番目のパスでは、親がサイズに関する最終決定を行います。
ViewGroup の場合、測定プロセスはより複雑です。親は最初にすべての子を測定し、次にそれらのサイズに基づいて自身のサイズを決定する必要があります。measureChildren(int widthMeasureSpec, int heightMeasureSpec) を呼び出すと、すべての子Viewを反復処理し、それぞれに対して measure(child, childWidthSpec, childHeightSpec) を呼び出します。すべての子を測定した後、ViewGroupは自身の寸法で setMeasuredDimension を呼び出します。
重要なニュアンス:measure メソッド(public、final)はオーバーライドできません。代わりにonMeasureがオーバーライドされます。これにより、システムはonMeasureの前後に、サイズ変更の確認や後続の描画のためのダーティエリアの計算などのメンテナンスタスクを実行できます。Viewに固定の寸法がある場合、onMeasureのオーバーライドは不要な場合があります。
MeasureSpec にはサイズとモードだけでなく、MeasureSpec.getMode() を介してアクセス可能な状態ビットも含まれています。setMeasuredDimension を呼び出した後、状態はViewの測定寸法の一部となり、getMeasuredState() を介して確認できます。これはScrollViewやその他のスクロール可能なコンテナで、子に制約を正しく渡すために使用されます。
正方形表示のためにonMeasureをオーバーライドしたカスタムViewを作成する 実践例 を見てみましょう。SquareViewクラスはViewを拡張し、渡されたMeasureSpecに関係なく、幅と高さが常に等しいことを保証します。onMeasureでは、最小の辺が決定され、正方形のサイズが設定されます。
class SquareView(context: Context)
: View(context) {
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
val widthSize =
MeasureSpec.getSize(widthMeasureSpec)
val heightSize =
MeasureSpec.getSize(heightMeasureSpec)
val size = minOf(widthSize, heightSize)
setMeasuredDimension(size, size)
}
}
ViewGroup はより複雑なonMeasureロジックを必要とします。最初に子を測定し、次にViewGroup自身のサイズを決定する必要があるためです。CascadeLayoutの例では、子をオフセット付きでカスケード状に配置します。measureChildWithMarginsを介してすべての子を測定した後、全体の幅と高さが計算されます。
class CascadeLayout(context: Context)
: ViewGroup(context) {
private val cascadeOffset = 40
override fun onMeasure(widthMeasureSpec: Int,
heightMeasureSpec: Int) {
var maxWidth = 0
var totalHeight = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
measureChildWithMargins(child,
widthMeasureSpec,
cascadeOffset * i,
heightMeasureSpec, 0)
maxWidth = maxOf(maxWidth,
child.measuredWidth +
cascadeOffset * i)
totalHeight += child.measuredHeight
}
setMeasuredDimension(
resolveSize(maxWidth, widthMeasureSpec),
resolveSize(totalHeight, heightMeasureSpec))
}
override fun generateLayoutParams(attrs: AttributeSet?)
: LayoutParams = MarginLayoutParams(context, attrs)
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 left = l + cascadeOffset * i
child.layout(left, top,
left + child.measuredWidth,
top + child.measuredHeight)
top += child.measuredHeight
}
}
}
setMeasuredDimensionの呼び出し忘れ は最も一般的なミスです。デベロッパーがonMeasureをオーバーライドしてもsetMeasuredDimensionを呼び出さないと、アプリケーションはIllegalStateExceptionでクラッシュします。これは特に、メソッドに条件分岐があり、いずれかの分岐で呼び出しがない場合に発生します。onMeasure内のすべてのコード分岐は、setMeasuredDimensionの呼び出しで終了する必要があります。
AT_MOSTモードの無視 は2番目に多いミスです。AT_MOSTモードのViewがコンテンツに基づく計算ではなく常に渡されたサイズを使用する場合、親コンテナはスペースを正しく分配できません。例えば、AT_MOSTのTextViewはテキスト幅を計算し、希望する幅と渡された幅の最小値を使用する必要があります。AT_MOSTを無視すると、コンテンツが小さい場合でもViewが利用可能なすべてのスペースを占有します。
onMeasure内でのオブジェクト作成 は古典的なパフォーマンスのミスです。onMeasureは複数回呼び出される可能性があるため(レイアウト要求のたびに)、このメソッド内でオブジェクト(Paint、Rect、String)を作成するとメモリを圧迫し、ガベージコレクションを引き起こします。すべてのオブジェクトはViewコンストラクタで1回作成し、onMeasureではサイズ計算ロジックのみを実行する必要があります。同じルールが onDrawおよびonLayout にも適用されます。
measureChildWithMargins はViewGroupのプロテクテッドメソッドで、MarginLayoutParamsを考慮して単一の子Viewを測定します。このメソッドは親のMeasureSpecと累積された幅と高さのオフセットを受け取ります。親のパディングと子のマージンを差し引いて子ViewのMeasureSpecを自動的に調整し、調整されたMeasureSpecをchild.measure()に渡します。
高度な 測定ロジック の場合、ViewGroupは measureChild(View child, int parentWidthSpec, int parentHeightSpec) をオーバーライドするか、各子に対して直接MeasureSpecを操作できます。例えば、LinearLayoutはonMeasureですべての子Viewを反復処理し、各子のlayout_weightを考慮して測定し、残りのスペースを比例配分します。このアプローチにより、任意のレイアウトアルゴリズムを実装できます。
測定キャッシュメカニズムによる 測定 結果のキャッシュは、特定のViewGroupで setMeasureWithLargestChildEnabled フラグを介して利用可能です。ただし、ほとんどの場合、onMeasureはレイアウト変更のたびに再呼び出しされ、キャッシュは適用されません。カスタムViewGroupでは、キャッシュに依存するのではなく、onMeasureでの計算を最小限に抑えることをお勧めします。
よくある質問
はい、カスタムViewがViewクラスから直接継承する場合必要です。TextView、ImageView、Buttonから標準サイズで継承する場合は、onMeasureを変更せずにそのままにできます。ViewGroupの場合は、常にonMeasureのオーバーライドが必要です。そうしないと、子が正しく測定されません。
Androidシステムは “The View did not call setMeasuredDimension” というメッセージで IllegalStateException をスローします。この例外は、onMeasure完了後に measure() メソッドで発生します。最終的な寸法がゼロのままの場合です。try-catchで処理しないと、アプリケーションがクラッシュします。
getMeasuredWidth() はonMeasure(測定フェーズ)で設定されたサイズを返します。getWidth() はonLayoutで位置調整後にViewが受け取った実際のサイズを返します。ほとんどのViewではこれらの値は一致しますが、カスタムViewGroupでは異なる場合があります。
いいえ、onMeasureはサイズ計算専用です。このメソッド内で状態変更、アニメーション開始、ネットワーキング、データ更新を行うと、Androidアーキテクチャに違反し、再帰的なmeasure呼び出しを引き起こす可能性があります。状態変更がrequestLayoutをトリガーするためです。
ConstraintLayout は定義された制約に基づいて子の測定を独立して管理します。ConstraintLayout内のカスタムViewがonMeasureをオーバーライドする場合、ConstraintLayoutから渡されたMeasureSpecを正しく処理する必要があります。そうしないと、制約が機能しない可能性があります。ConstraintLayoutは計算に独自のWidgetContainerを使用した2パスアルゴリズムを採用しています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。