Jetpack Compose:Modifier(Compose修飾子)

投稿日:  更新日:

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修飾子は実行済み修飾子の影響を受けるため、順序は重要です。順序の違いで、コンテンツの表示や追加される機能が変わってしまいます。

以下は、その例です。

修飾子の順序(1)修飾子の順序(2)修飾子の順序(3)
    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") }
        )
	}

修飾子の順序(1)
修飾子の順序(1)

    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)
        )
    }

修飾子の順序(2)
修飾子の順序(2)

    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)
        )
	}

修飾子の順序(3)
修飾子の順序(3)

スポンサーリンク

修飾子の重複

修飾子チェーンにおいて、同じCompose修飾子を複数指定した場合の動作は、修飾子によって異なります。統一されていないので注意が必要です。

次に挙げる3つのタイプがあります。

上書き

前の修飾子を後ろの修飾子で上書きするタイプです。後ろの修飾子が有効になります。

        Text(
            text = "Click",
            fontSize = 48.sp,
            modifier = Modifier
                .background(Color.LightGray)
                .background(Color.Cyan)       // 有効
        )

backgroundの重複

以下は同じタイプのCompose修飾子です。

  • background
  • clickable

追加加算

前の修飾子と後の修飾子を加算するタイプです。加算した結果が修飾子の値になります。

        Image(
            painterResource(R.drawable.azami100x75),
            contentDescription = null,
            modifier = Modifier
                .background(Color.LightGray)
                .padding(10.dp)
                .padding(20.dp)
        )

paddingの重複

以下は同じタイプの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)   // 無視
        )

sizeの重複

描画処理の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 !") }

修飾子チェーンの選択の例

スポンサーリンク

関連記事:

プロジェクトのビルドで「Something went wrong while checking for version compatibility between the Compose Compiler and the Kotlin Compiler.」とメッセージを吐き、エラーになる場合があります。 既存のプロジェクトを新しくリリースされたAndroid Studioでビルドした場合に頻発します。 先日、「Giraffe|2022.3.1」がリリース(2023.07)されて、早速、ビルドをしたら発生しました。 その対処方法を説明します。 ※環境:Android Studio Giraffe | 2022.3.1 ...
表示の変わらないUI要素(Composable関数)に対して行われる再Composeは無駄な処理です。 ですので、極力排除したいところですが、表示が変わらないため、画面上からの判断が難しくなっています。 このようなとき、Layout Inspectorを利用すると、無駄な再Composeを見つけ出すことが出来ます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1 ...
Jetpack ComposeはAndroidシステムの新たなUIフレームワークです。従来のViewシステムと、アプリ画面の描画の仕組みが異なります。 このJetpack Composeによるアプリ画面の描画について、仕組みの大枠をまとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
Jetpack Composeは描画処理の軽量化(消費リソース量の削減)をするために、表示の変更されたUI要素のみを再Composeし、表示の変わらないUI要素をスキップします。これにより、高い表示パフォーマンスを維持しています。 しかし、スキップが正常に行われないとしても、アプリの画面に現れて来ません。なぜなら、同じ表示を無駄に繰り返すことになるからです。 アプリは動くけれど動作が鈍いならば、真っ先に疑うポイントです。不要な再Composeが行われている可能性が考えられます。 これは気付かないうちに蓄積し易い不具合です。ですので、再Composeとスキップについて理解し、予防に努めることをお勧めします。 今回は「再Composeとスキップ」について、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
サンプルアプリを作成して、描画処理(再Compose)の周期を観測してみました。 その結果を紹介します。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
mutableStateOfはComposable関数ではありません。 ですので、Composable関数内にある必要はなく、どこでも記述できます。 Activityから表示の更新を発行する方法として使えそうです。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
状態の保持(remenber)はアプリの画面(UI)を制御・管理するために必要な動作です。 再Composeのスケジューリング(mutableStateOf)に並び、Jetpack Composeの重要な技術の一つです。 今回は「再Composeを超えて状態の保持」について、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
再Composeのスケジューリング(mutableStateOf)はアプリのパフォーマンスに直結する動作です。 状態の保持(remember)に並び、Jetpack Composeの重要な技術の一つです。 今回は「再Composeのスケジューリング」について、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
CompositionLocalはUIツリーで発生する状態のバケツリレーを解決してくれます。 また、広く共有したい状態の定義にも適しています。例えば、「ツリーのある階層以下に対して」などと言った場合です。 CompositionLocalについて、まとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
CompositionLocalはUIツリーにローカルな変数を確保します。 その変数の参照キーはcompositionLocalOf関数(以降、Dynamic側と呼ぶ)によって返されるCompositionLocalインスタンスです。 このインスタンスを返す方法に、もう一つ、staticCompositionLocalOf関数(以降、Static側と呼ぶ)があります。 この両者の違いをまとめます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
CompositionLocalはシステムにより提供されているものがあります。 プログラミングに有益で利用頻度の高いものが用意されています。例えば、LocalContextやLocalConfigurationなどです。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
「Android Studio Giraffe」の作成するプロジェクトは、Jetpack Composeの利用が推奨されます。 今後、Viewシステムに代わり、Jetpack Composeが主流になるようです。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
「Android Studio Giraffe」の作成するプロジェクトは、Jetpack Composeの利用が推奨されます。 そして、作成されたプロジェクトは、Material Designeに準拠したテーマが指定されます。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 1     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
Android StudioにおけるJetpack Composeプロジェクトは、エディタ上でUIのプレビューが行えます。 Kotlinで記述した画面構成(UIツリー)が視覚的に確認できるので、とても便利です。 さらに、色々な表示条件の設定が行えるので、使いこなせば更に利便性が向上します。 ※この記事の執筆中にドキュメント「コンポーザブルのプレビューで UI をプレビューする」を見つけました。記事はこのドキュメントと重複する部分が多いです。ドキュメントも参考にして下さい。 ※環境:Android Studio Giraffe | 2022.3.1 Patch 3     Kotlin 1.8.10     Compose Compiler 1.4.3 ...
スポンサーリンク