Compose 为常见行为提供了许多开箱即用的修饰符, 但您也可以自行创建自定义修饰符
修饰符包含多个部分:
- 修饰符工厂
- 这是
Modifier
的扩展函数,提供了一个惯用的 API 并且可以轻松将修饰符串联在一起。通过 修饰符工厂生成了 Compose 用于修改的修饰符元素 界面
- 这是
- 修饰符元素
- 您可以在这里实现修饰符的行为。
您可以通过多种方式实现自定义修饰符,具体取决于
所需的全部功能通常,实现自定义修饰符的最简单方法是
实现一个自定义修饰符工厂,将其他已定义的修饰符
修饰符工厂组合在一起。如果您需要更多自定义行为,请实现
修饰符元素使用 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。例如,以下代码段显示了一个
修饰符,在启用/停用时为 alpha 变化添加动画效果:
@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
,因为 CompositionLocal 将是
可在使用现场正确解决,并且可以安全提升。
绝不会跳过可组合函数修饰符
从不跳过可组合工厂修饰符,因为可组合函数 返回的值无法跳过。这意味着您的修饰符函数 每次重组时都会调用,如果重组,费用可能会很高 。
可组合函数修饰符必须在可组合函数中调用
与所有可组合函数一样,必须从 。这会限制修饰符可以提升到的位置,因为它可以 绝不会从组合中提升。相比之下,不可组合修饰符 工厂可以从可组合函数中提升出来,以便更轻松地重复使用, 提升效果:
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 相同,并且是
创建自定义修饰符的高效方法。
使用 Modifier.Node
实现自定义修饰符
使用 Modifier.Node 实现自定义修饰符的过程分为三个部分:
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
以及零个或多个节点类型。还有
选择不同的节点类型。通过
上面的示例都需要能够绘制,因此它实现了 DrawModifierNode
,
允许其覆盖绘制方法。
可用的类型如下:
节点 |
用法 |
示例链接 |
一个 |
||
绘制到布局空间中的 |
||
实现此接口可让 |
||
一个 |
||
一个 |
||
为父布局提供数据的 |
||
一个 |
||
一个 |
||
实现 |
||
一个能够将工作委托给其他 这有助于将多个节点实现组合为一个。 |
||
允许 |
当对节点的相应节点调用更新时,节点会自动失效
元素。由于我们的示例是 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
方法中创建新节点。在我们的 圆圈示例中,节点的颜色已更新。
此外,ModifierNodeElement
实现还需要实现 equals
和 hashCode
。update
只有在与
返回 false。
上面的示例使用数据类来实现这一点。这些方法用于
检查节点是否需要更新。如果元素的属性
不会影响节点是否需要更新,
类,那么您可以手动实现 equals
和 hashCode
,例如内边距修饰符元素。
修饰符工厂
这是修饰符的公共 API Surface。大部分植入方式 创建修饰符元素并将其添加到修饰符链中:
// Modifier factory fun Modifier.circle(color: Color) = this then CircleElement(color)
完整示例
这三个部分结合在一起,创建了用于绘制圆形的自定义修饰符
使用 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) } } }
引用 CompositionLocal
Modifier.Node
修饰符不会自动观察 Compose 状态的变化
对象,例如 CompositionLocal
。Modifier.Node
修饰符的优势
使用可组合工厂创建的修饰符的优点是,它们可以读取
在界面中使用修饰符的 CompositionLocal 的值
而不是在分配修饰符的位置,使用 currentValueOf
。
不过,修饰符节点实例不会自动观察状态变化。接收者 自动响应 CompositionLocal 的变化,您可以读取其当前 某个范围内的值:
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
修饰符可以委托给其他节点。应用场景有很多
例如,跨不同修饰符提取常见实现,
但它也可以用于在修饰符之间共享通用状态。
例如,可点击的修饰符节点的基本实现 互动数据:
class ClickableNode : DelegatingNode() { val interactionData = InteractionData() val focusableNode = delegate( FocusableNode(interactionData) ) val indicationNode = delegate( IndicationNode(interactionData) ) }
选择停用节点自动失效功能
Modifier.Node
个节点会在其对应的节点
更新了 ModifierNodeElement
项调用。有时,在更复杂的修饰符中,
您希望停用此行为,以便更精细地控制
您的修饰符会使阶段失效。
如果您的自定义修饰符同时修改布局和
平局。通过选择停用自动失效功能,
仅指定与绘制相关的属性,如 color
、更改,而不会使布局失效。
这可以提升修饰符的性能。
下面显示了一个假设示例,其中使用具有 color
的修饰符,
size
和 onClick
lambda 作为属性。此修饰符只会让
并跳过任何不是以下项的失效操作:
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) } } }