Compose には、一般的な動作のための多くの修飾子が最初から用意されています。 独自の修飾子を作成することもできます
修飾子は複数の要素で構成されます。
- 修飾子ファクトリ
<ph type="x-smartling-placeholder">
- </ph>
- これは、慣用的な API を提供する
Modifier
の拡張関数です。 複数の修飾子を簡単につなぐことができます。「 修飾子ファクトリは、Compose が変更に使用する修飾子要素を生成します 作成します。
- これは、慣用的な API を提供する
- 修飾子要素
<ph type="x-smartling-placeholder">
- </ph>
- ここで、修飾子の動作を実装できます。
カスタム修飾子を実装する方法は、
必要があります。多くの場合、カスタムの修飾子を実装する最も簡単な方法は
これは、すでに定義されている他の修飾子を組み合わせ、
組み合わせることもできます。さらにカスタム動作が必要な場合は、
Modifier.Node
API を使用する修飾子要素を使用します。この API は下位レベルですが、
柔軟性が高まります
既存の修飾子を連結する
多くの場合、既存の修飾子を使用するだけで、カスタム修飾子を作成できます。
使用します。たとえば、Modifier.clip()
は
graphicsLayer
修飾子。この戦略では、既存の修飾子要素を使用します。
独自の修飾子ファクトリを指定できます
独自のカスタム修飾子を実装する前に、 説明します。
fun Modifier.clip(shape: Shape) = graphicsLayer(shape = shape, clip = true)
同じ修飾子のグループを頻繁に繰り返す場合は、 これを独自の修飾子でラップします。
fun Modifier.myBackground(color: Color) = padding(16.dp) .clip(RoundedCornerShape(8.dp)) .background(color)
コンポーザブルの修飾子ファクトリを使用してカスタム修飾子を作成する
コンポーズ可能な関数を使用して値を渡すカスタム修飾子を作成することもできます 追加します。これは、コンポーザブル修飾子ファクトリと呼ばれます。
コンポーザブルの修飾子ファクトリを使用して修飾子を作成すると、
上位レベルの Compose API(animate*AsState
や、他の Compose での使用など)
状態ベースのアニメーション API について説明します。たとえば、次のスニペットは、
有効化/無効化時にアルファの変化をアニメーション化する修飾子です。
@Composable fun Modifier.fade(enable: Boolean): Modifier { val alpha by animateFloatAsState(if (enable) 0.5f else 1.0f) return this then Modifier.graphicsLayer { this.alpha = alpha } }
カスタム修飾子が、
CompositionLocal
。これを実装する最も簡単な方法は、コンポーザブルを使用することです。
修飾子ファクトリ:
@Composable fun Modifier.fadedBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) }
この方法には、以下の注意点があります。
CompositionLocal
値は、修飾子ファクトリの呼び出しサイトで解決されます。
コンポーズ可能な修飾子ファクトリを使用してカスタム修飾子を作成する場合、コンポジション ローカル変数は、作成元のコンポジション ツリーから値を取得します。 分析できますこれは予期しない結果につながる可能性があります。たとえば ローカル修飾子の例で、 コンポーズ可能な関数:
@Composable fun Modifier.myBackground(): Modifier { val color = LocalContentColor.current return this then Modifier.background(color.copy(alpha = 0.5f)) } @Composable fun MyScreen() { CompositionLocalProvider(LocalContentColor provides Color.Green) { // Background modifier created with green background val backgroundModifier = Modifier.myBackground() // LocalContentColor updated to red CompositionLocalProvider(LocalContentColor provides Color.Red) { // Box will have green background, not red as expected. Box(modifier = backgroundModifier) } } }
修飾子が想定した動作と異なる場合は、カスタム
Modifier.Node
。コンポジション ローカルが
使用サイトで正しく解決され、安全にホイスティングできる。
コンポーズ可能な関数の修飾子がスキップされない
コンポーズ可能な関数は、コンポーズ可能な関数によってスキップされることはありません。 スキップできません。これは修飾子関数です は再コンポーズのたびに呼び出されるため、再コンポーズの際にコストが高くなる可能性があります。 できます。
コンポーズ可能な関数の修飾子はコンポーズ可能な関数内で呼び出す必要がある
すべてのコンポーズ可能な関数と同様に、コンポーズ可能なファクトリ修飾子は 必要があります。これにより、修飾子をホイスティングできる場所が制限されます。 コンポジションからホイスティングされることはありません。比較すると、コンポーズ可能な修飾子は ファクトリをコンポーズ可能な関数からホイスティングして、再利用と パフォーマンスの向上:
val extractedModifier = Modifier.background(Color.Red) // Hoisted to save allocations @Composable fun Modifier.composableModifier(): Modifier { val color = LocalContentColor.current.copy(alpha = 0.5f) return this then Modifier.background(color) } @Composable fun MyComposable() { val composedModifier = Modifier.composableModifier() // Cannot be extracted any higher }
Modifier.Node
を使用してカスタムの修飾子の動作を実装する
Modifier.Node
は、Compose で修飾子を作成するための下位レベルの API です。これは、
これは、Compose が独自の修飾子を実装している API と同じ API であり、
カスタム修飾子を効率的に作成できます
Modifier.Node
を使用してカスタム修飾子を実装する
Modifier.Node を使用してカスタム修飾子を実装する手順は、次の 3 つの部分に分かれます。
- ロジックとメタデータを保持する
Modifier.Node
実装。 調整する必要があります。 - 修飾子を作成および更新する
ModifierNodeElement
ノードインスタンス。 - 前述のオプションの修飾子ファクトリ。
ModifierNodeElement
クラスはステートレスであり、それぞれに新しいインスタンスが割り当てられます。
再コンポーズされますが、Modifier.Node
クラスはステートフルにでき、存続します
複数の再コンポーズで使用できます。また、再利用もできます。
次のセクションでは、各要素について説明し、 カスタム修飾子を使用して円を描画します。
Modifier.Node
Modifier.Node
の実装(この例では CircleNode
)は、
変更することもできます。
// Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
この例では、修飾子に渡された色で円を描画します 使用します。
ノードは Modifier.Node
と 0 個以上のノードタイプを実装します。他にも
異なるノードタイプを指定します。「
上記の例では描画が可能である必要があるため、DrawModifierNode
を実装します。
描画メソッドをオーバーライドできます。
使用可能なタイプは次のとおりです。
ノード |
使用目的 |
リンクの例 |
ラップされたコンテンツの測定方法と配置方法を変更する |
||
レイアウトのスペースに描画される |
||
このインターフェースを実装すると、 |
||
テストやユーザー補助などのユースケースで使用するセマンティクスの Key-Value を追加する |
||
PointerInputChanges を受け取る |
||
親レイアウトにデータを提供する |
||
|
||
コンテンツのグローバル位置が変更された可能性があるときに、レイアウトの最終 |
||
|
||
他の これは、複数のノード実装を 1 つにまとめる場合に便利です。 |
||
|
対応するノードで update が呼び出されると、ノードは自動的に無効化される
要素です。この例では DrawModifierNode
であるため、更新が呼び出されるたびに
要素を更新すると、ノードは再描画をトリガーし、色が正しく更新されます。内容
自動無効化を無効にすることができます。詳しくは後述をご覧ください。
ModifierNodeElement
ModifierNodeElement
は、作成または作成するデータを保持する不変のクラスです。
カスタム修飾子を更新します。
// ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } }
ModifierNodeElement
実装では、次のメソッドをオーバーライドする必要があります。
create
: 修飾子ノードをインスタンス化する関数です。これにより、 ノードを作成するために呼び出されます。通常、この ノードを構築し、そのノードで使用するパラメータを 修飾子ファクトリに渡されましたupdate
: この関数は、この修飾子が このノードはすでに存在しますが、プロパティが変更されています。これは、 クラスのequals
メソッドによって決まります。変更前の修飾子ノードはupdate
呼び出しにパラメータとして送信されます。この時点で ノードの既存のインスタンスを対応する各プロパティを あります。このようにノードを再利用できることは、Modifier.Node
によるパフォーマンス向上そのため、既存のルールの 新しいノードを作成するのではなく、update
メソッドで既存のノードを使用します。Google の 場合、ノードの色が更新されます。
また、ModifierNodeElement
の実装には equals
も実装する必要があります。
および hashCode
。update
は、次と等しい比較が行われた場合にのみ呼び出されます。
前の要素は false を返します。
上記の例では、データクラスを使用してこれを実現しています。これらのメソッドは、
ノードの更新が必要かどうかを確認します要素に
ノードの更新の必要性に寄与しない、または
使用する必要がある場合は、equals
を手動で実装できます。
と hashCode
。たとえば、パディング修飾子要素。
修飾子ファクトリ
これは修飾子の公開 API サーフェスです。ほとんどの実装は単純に 修飾子要素を作成して修飾子チェーンに追加します。
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
完全な例
これら 3 つの要素を組み合わせて、円を描くカスタム修飾子が作成されます
Modifier.Node
API を使用します。
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color) // ModifierNodeElement private data class CircleElement(val color: Color) : ModifierNodeElement<CircleNode>() { override fun create() = CircleNode(color) override fun update(node: CircleNode) { node.color = color } } // Modifier.Node private class CircleNode(var color: Color) : DrawModifierNode, Modifier.Node() { override fun ContentDrawScope.draw() { drawCircle(color) } }
Modifier.Node
を使用する一般的な状況
Modifier.Node
を使用してカスタム修飾子を作成する場合、次のような状況が考えられます。
あります。
パラメータなし
修飾子にパラメータがない場合は、更新の必要はなく、 また、データクラスである必要はありません。この実装例を見てみましょう コンポーザブルに一定量のパディングを適用する修飾子の例です。
fun Modifier.fixedPadding() = this then FixedPaddingElement data object FixedPaddingElement : ModifierNodeElement<FixedPaddingNode>() { override fun create() = FixedPaddingNode() override fun update(node: FixedPaddingNode) {} } class FixedPaddingNode : LayoutModifierNode, Modifier.Node() { private val PADDING = 16.dp override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = PADDING.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } }
コンポジション ローカルの参照
Modifier.Node
修飾子が Compose の状態の変更を自動的に監視しない
オブジェクト(例: CompositionLocal
)Modifier.Node
修飾子が
コンポーズ可能なファクトリで作成された修飾子は、
UI で修飾子が使用されているコンポジション ローカルの値
修飾子が割り当てられた場所ではなく、currentValueOf
を使用してツリー内に移動します。
ただし、修飾子ノード インスタンスは状態変化を自動的に監視しません。宛先 コンポジションのローカルな変化に自動的に反応するため、その現在の あります。
DrawModifierNode
:ContentDrawScope
LayoutModifierNode
:MeasureScope
、IntrinsicMeasureScope
SemanticsModifierNode
:SemanticsPropertyReceiver
この例では、LocalContentColor
の値を監視して背景を描画します。
表示されます。ContentDrawScope
はスナップショットの変更を監視するため、
LocalContentColor
の値が変わると自動的に再描画します。
class BackgroundColorConsumerNode : Modifier.Node(), DrawModifierNode, CompositionLocalConsumerModifierNode { override fun ContentDrawScope.draw() { val currentColor = currentValueOf(LocalContentColor) drawRect(color = currentColor) drawContent() } }
スコープ外の状態の変化に対応し、
ObserverModifierNode
を使用します。
たとえば、Modifier.scrollable
はこの手法を使用して次のことを行います。
LocalDensity
の変化を確認します。以下に簡単な例を示します。
class ScrollableNode : Modifier.Node(), ObserverModifierNode, CompositionLocalConsumerModifierNode { // Place holder fling behavior, we'll initialize it when the density is available. val defaultFlingBehavior = DefaultFlingBehavior(splineBasedDecay(UnityDensity)) override fun onAttach() { updateDefaultFlingBehavior() observeReads { currentValueOf(LocalDensity) } // monitor change in Density } override fun onObservedReadsChanged() { // if density changes, update the default fling behavior. updateDefaultFlingBehavior() } private fun updateDefaultFlingBehavior() { val density = currentValueOf(LocalDensity) defaultFlingBehavior.flingDecay = splineBasedDecay(density) } }
アニメーション修飾子
Modifier.Node
の実装では coroutineScope
にアクセスできます。これにより
Compose Animatable API を使用する。たとえば、このスニペットは
フェードインとフェードアウトを繰り返すには、上から CircleNode
します。
class CircleNode(var color: Color) : Modifier.Node(), DrawModifierNode { private val alpha = Animatable(1f) override fun ContentDrawScope.draw() { drawCircle(color = color, alpha = alpha.value) drawContent() } override fun onAttach() { coroutineScope.launch { alpha.animateTo( 0f, infiniteRepeatable(tween(1000), RepeatMode.Reverse) ) { } } } }
委任を使用した修飾子間での状態の共有
Modifier.Node
修飾子は他のノードに委任できます。さまざまなユースケースで
たとえば、異なる修飾子にまたがって共通の実装を抽出する、
修飾子間で共通の状態を共有するためにも使用できます。
たとえば、この 2 つのノードを共有するクリック可能な修飾子ノードの基本的な実装例は、 インタラクション データ:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
ノードの自動無効化のオプトアウト
Modifier.Node
個のノードは、対応する
ModifierNodeElement
の呼び出しが更新されました。場合によっては、より複雑な修飾子を使用して、
このような動作をオプトアウトして
フェーズを無効化します。
これは、カスタムの修飾子によってレイアウトと
描画します。自動無効化を無効にすると、描画を無効化するだけで
描画関連のプロパティ(color
など)のみを使用して変更し、レイアウトを無効にしない。
これにより、修飾子のパフォーマンスが向上します。
以下は、color
を持つ修飾子を使用した架空の例です。
プロパティとして size
および onClick
ラムダを追加します。この修飾子は、定義されている対象のみを
必須になり、次の条件に該当しない無効化はスキップされます。
class SampleInvalidatingNode( var color: Color, var size: IntSize, var onClick: () -> Unit ) : DelegatingNode(), LayoutModifierNode, DrawModifierNode { override val shouldAutoInvalidate: Boolean get() = false private val clickableNode = delegate( ClickablePointerInputNode(onClick) ) fun update(color: Color, size: IntSize, onClick: () -> Unit) { if (this.color != color) { this.color = color // Only invalidate draw when color changes invalidateDraw() } if (this.size != size) { this.size = size // Only invalidate layout when size changes invalidateMeasurement() } // If only onClick changes, we don't need to invalidate anything clickableNode.update(onClick) } override fun ContentDrawScope.draw() { drawRect(color) } override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val size = constraints.constrain(size) val placeable = measurable.measure(constraints) return layout(size.width, size.height) { placeable.place(0, 0) } } }