Jetpack Composeが提供する既存のUI要素(Compose UI)は、必ずModifierを引数に持ちます。
このModifierの役割はUI要素へ装飾や機能拡張を追加することですが、裏でアプリ画面の描画処理と密接に関連しており、UI要素よりもシステム側に近い存在です。
理解せずに誤った使い方をすれば、装飾や機能拡張の域を脱してUI要素が表示されないこともあり、思ったようなアプリ画面は望めません。
Modifierについて、まとめます。
※環境:Android Studio Hedgehog | 2023.1.1
Kotlin 1.8.10
Compose Compiler 1.4.3
目次
Modifierとは
ModifierはUI要素に対する修飾子の要素(変更因子)を収集する(コレクション)クラスです。
この「修飾子の要素」をCompose修飾子といいます。
UI要素(Composable関数)へCompose修飾子を指定することで、装飾や拡張機能を追加できます。
例えば、次のようなことができます。
※詳細は「Compose修飾子の一覧」(後述)を参照
- サイズ、レイアウト、動作、外観を変更
- ユーザを補助する情報を追加
- ユーザ入力を処理
- ユーザ操作を処理
※ユーザ操作:クリック、スクロール、ドラッグ、ズームなど
Compose修飾子は拡張関数を実行することで収集され、順序付けられてModifierオブジェクトへ格納されます。
@Composable
fun Greeting(name: String) {
val _color = remember{ mutableStateOf(Color.Black) }
Text(
text = "Hello $name!",
modifier = Modifier // Modifierオブジェクト作成&指定
.clickable { _color.value = Color.Red }, // Compose修飾子の収集
color = _color.value
)
}
格納されたCompose修飾子は、順番に必要なものが取り出されて、描画処理の各フェーズで処理されます。ただし、前回の描画処理から変更のない修飾子は処理しません。そして、処理する修飾子の無いフェーズはスキップされます。

※描画処理についていは「Jetpack Compose:Composeによるアプリ画面の描画」を参照
Compose修飾子の一覧
システムにより準備されているCompose修飾子の一覧です。
| カテゴリ | 関数(Modifier.***) | 機能 |
|---|---|---|
| アクション | clickable combinedClickable※ draggable selectableGroup selectable swipeable toggleable triStateToggleable | 画面の入力イベントを受信 (イベント:クリック、タッチなど) |
| 位置揃え | align alignBy alignByBaseline | Box/Column/Rowの要素の位置を揃える |
| アニメ | animateItemPlacement※ animateEnterExit※ | Lazyリストのアイテム配置をアニメ化 |
| 枠線 | border | 要素の外観に枠線を追加 |
| 描画 | alpha background clip drawBehind drawWithCache drawWithContent indication paint shadow safeDrawingPadding zIndex | 装飾・図形の描画 (装飾:影、視覚効果など) |
| フォーカス | onFocusChanged onFocusEvent focusTarget focusProperties focusRequester focusGroup※ focusable onFocusedBoundsChanged※ | フォーカスイベントの受信 |
| グラフィック | graphicsLayer toolingGrapchicsLayer | 描画レイヤへコンテンツを描画 |
| キーボード | onKeyEvent OnPreviewKeyEvent | ハードウェアキーイベントを受信 |
| レイアウト | layoutId layout onGloballyPositioned | 要素の測定とレイアウト方法を変更 |
| パディング | paddingFrom paddingFromBaseline absolutePadding padding captionBarPadding displayCutoutPadding imePadding mandatorySystemGesturesPadding navigationBarsPadding safeContentPadding safeGesturesPadding statusBarsPadding systemBarsPadding SystemGesturesPadding waterfallPadding windowInsetsPadding | パディングを追加 |
| ポインタ | pointerHoverIcon pointerInteropFilter※ PointerInput | ポインタ(カーソル)入力を処理 |
| 位置 | absoluteOffset offset tabIndicatorOffset | コンテンツの表示をオフセット |
| セマンティクス | progressSemantics clearAndSetSemantics semantics | アプリの状況を出力 (状況:テスト・プロファイル情報など) |
| スクロール | clipScrollableContainer nestedScroll overscroll※ onPreRotaryScrollEvent※ onRotaryScrollEvent scrollAway horizontalScroll verticalScroll scrollable imeNestedScroll※ | スクロール機能を追加 |
| サイズ | animateContentSize aspectRatio width / height / size requiredWidth / Height onSizeChanged defaultMinSize fillMaxWidth / Height / Size heightInt requiredWidth / Height / Size requiredWidthIn / HeightIn / SizeIn wrapContentWidth / Height / Size windowInsetsBottomHeight windowInsetsEndWidth windowInsetsStartWidth windowinsetsTopHeight fillParentMaxWidth / Height / Size matchParentSize weight | コンテンツのサイズを指定 (レイアウト制約の変更) |
| テスト | testTag | 変更された要素をテストで検出 |
| 変換 | rotate scale transformable | コンテンツの図形的な変換 |
| その他 | blue | コンテンツへぼかし効果を追加 |
| bringIntoViewRequester※ composed hoverable inspectable magnifier※ modifierLocalConsumer※ modifierLocalProvider※ onPlaced motionEventSpy※ pullRefreshIndicatorTransform※ pullrefresh edgeSwipeToDismiss systemGestureExclusion cunsumedWindowInsets※ withConsumeWindowInsets※ | その他 | |
| ※@ExperimentalFoundationApi付(実験的な実装) | ||
修飾子チェーン
Compose修飾子を収集する際に実行される拡張関数は、Modifierインスタンスを返す決まりになっています。
ですので、複数のCompose修飾子を指定したければ、(0)~(4)のように関数の実行を連結できます。これを「修飾子チェーン」といいます。
Image(
painterResource(R.drawable.azami200x150),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier ...(0)
.background(Color.LightGray) ...(1)
.padding(20.dp) ...(2)
.clip(CircleShape) ...(3)
.clickable { Log.i(TAG, "Click") } ...(4)
)
修飾子チェーンはKotlinの構文解釈に従い、(0)~(4)の順番(左から右へ)で実行されます。そして、Compose修飾子がコンテンツ(例では画像)に与える影響も、この順番になります。
この時、「実行済みの修飾子」は「実行中の修飾子」へ影響を与える点に注意してください。

修飾子の順序
Compose修飾子は実行済み修飾子の影響を受けるため、順序は重要です。順序の違いで、コンテンツの表示や追加される機能が変わってしまいます。
以下は、その例です。
Box(modifier = Modifier.fillMaxSize()) { // Boxサイズ:320x240
Image(
painterResource(R.drawable.azami200x150), // Imageサイズ:200x150
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.background(Color.LightGray)
.padding(20.dp)
.clip(CircleShape)
.clickable { Log.i(TAG, "Click") }
)
}


Box(modifier = Modifier.fillMaxSize()) { // Boxサイズ:320x240
Image(
painterResource(R.drawable.azami200x150), // Imageサイズ:200x150
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.background(Color.LightGray)
.clip(CircleShape)
.clickable { Log.i(TAG, "Click") }
.padding(20.dp)
)
}


Box(modifier = Modifier.fillMaxSize()) { // Boxサイズ:320x240
Image(
painterResource(R.drawable.azami200x150), // Imageサイズ:200x150
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.background(Color.LightGray)
.clickable { Log.i(TAG, "Click") }
.padding(20.dp)
.clip(CircleShape)
)
}


修飾子の重複
修飾子チェーンにおいて、同じCompose修飾子を複数指定した場合の動作は、修飾子によって異なります。統一されていないので注意が必要です。
次に挙げる3つのタイプがあります。
上書き
前の修飾子を後ろの修飾子で上書きするタイプです。後ろの修飾子が有効になります。
Text(
text = "Click",
fontSize = 48.sp,
modifier = Modifier
.background(Color.LightGray)
.background(Color.Cyan) // 有効
)

以下は同じタイプのCompose修飾子です。
- background
- clickable
追加加算
前の修飾子と後の修飾子を加算するタイプです。加算した結果が修飾子の値になります。
Image(
painterResource(R.drawable.azami100x75),
contentDescription = null,
modifier = Modifier
.background(Color.LightGray)
.padding(10.dp)
.padding(20.dp)
)

以下は同じタイプのCompose修飾子です。
- padding
- offset
- rotate
始め優先
始めの修飾子を採用し、後の修飾子は無視するタイプです。
Image(
painterResource(R.drawable.azami100x75),
contentDescription = null,
modifier = Modifier
.background(Color.LightGray)
.size(width = 200.dp, height = 150.dp) // 採用
.size(width = 100.dp, height = 75.dp) // 無視
)

描画処理のLayoutフェーズにおいて、UI要素のサイズは「レイアウト制約」と「コンテンツのサイズ」により決まります。
レイアウト制約とは「UI要素の許容サイズ」のことです。
この「レイアウト制約」を変更するCompose修飾子は、始め優先タイプになります。
※レイアウト制約については「制約と修飾子の順序」を参照
以下は同じタイプのCompose修飾子です。
- width
- height
- size … widthとheightを同時に指定
- fillMaxSize … widthとheightを同時に指定
修飾子チェーンの連結
Modifier#then関数を使用すると、修飾子チェーンの連結が出来ます。
@Suppress("ModifierFactoryExtensionFunction")
@Stable
@JvmDefaultWithCompatibility
interface Modifier {
...
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
...
}
then関数はModifierオブジェクトを引数(other)に持ちます。このotherがModifierインスタンスの後ろに連結されます。
ですので、下記の_type1,2,3は同じ修飾子チェーンです。
val _modifierA = Modifier.background(Color.LightGray) val _modifierB = Modifier.padding(10.dp).clip(CircleShape) val _type1 = Modifier.background(Color.LightGray).padding(10.dp).clip(CircleShape) val _type2 = Modifier.background(Color.LightGray).then(_modifierB) val _type3 = _modifierA.then(_modifierB)
また、中値記法(infix notation)を用いて表せます。
val _type1 = Modifier.background(Color.LightGray).padding(10.dp).clip(CircleShape) val _type2 = Modifier.background(Color.LightGray) then _modifierB val _type3 = _modifierA then _modifierB
val _modifierA = Modifier.background(Color.LightGray)
val _modifierB = Modifier.padding(10.dp).clip(CircleShape)
Image(
painterResource(R.drawable.azami120x90),
contentDescription = null,
contentScale = ContentScale.Crop,
// modifier = Modifier // Type 1
// .background(Color.LightGray).padding(10.dp).clip(CircleShape)
// modifier = Modifier // Type 2
// .background(Color.LightGray).then(_modifierB)
modifier = _modifierA.then(_modifierB) // Type 3
)

修飾子チェーンの選択
Modifier#then関数で連結する修飾子チェーンを選択すれば、UI要素の表示を動的に切り替えることが出来ます。
val _modifierB = Modifier.padding(10.dp).clip(CircleShape)
val _modifierC = Modifier.padding(10.dp).clip(RectangleShape)
val _flag = remember { mutableStateOf( false ) }
Image(
painterResource(R.drawable.azami120x90),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.background(Color.LightGray)
.then(if (_flag.value) _modifierB else _modifierC)
)
Button(
onClick = { _flag.value = !_flag.value },
modifier = Modifier.align(Alignment.CenterHorizontally)
) { Text(text = "Click !") }

関連記事:
