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 !") }
関連記事: