什么时候需要 Modifier.composed { ... }?

Arc*_*nes 23 android android-jetpack-compose

什么时候Modifier.composed { ... }有用?Modifier.padding()如果我可以简单地重来一遍,为什么我需要它Modifier.composed { PaddingModifier(...) }

adn*_*eal 24

注意 -composed不再是创建 Composition-aware 的推荐方法Modifier。现在建议Modifier像任何其他可组合项一样创建组合感知:

@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()
}
Run Code Online (Sandbox Code Playgroud)

查看有关创建自定义的最新文档Modifier以获取更多信息: https ://developer.android.com/jetpack/compose/custom-modifiers


Modifier.composed允许创建一个组合感知的修改器工厂,这对于具体化特定于实例的有状态修改器非常有用。来自文档:

声明一个 Modifier 的即时组合,该组合将针对它修改的每个元素进行组合。composed 可用于实现有状态修饰符,这些修饰符为每个被修改的元素具有特定于实例的状态,允许相同的修饰符实例安全地重用于多个元素,同时维护特定于元素的状态。

换句话说,它允许您将提升的状态注入特定于元素的状态Modifier并使用rememberDisposableEffectAmbient等。例如:

fun Modifier.fancyModifier(
    enabled: Boolean = false,
    onClick: () -> Unit = {}
) = composed(inspectorInfo = debugInspectorInfo {
    name = "fancyModifier"
    value = enabled
}) {
    var paddingValue by remember { mutableStateOf(0.dp) }
    onCommit(enabled) {
        paddingValue = if (enabled) 16.dp else 0.dp
    }
    fillMaxWidth()
        .clickable { onClick() }
        .padding(paddingValue)
}
Run Code Online (Sandbox Code Playgroud)
LazyColumnFor(items = List(size = 10) { "$it" }) {
    var enabled by remember { mutableStateOf(false) }
    Text(
        text = "fancy modifier",
        modifier = Modifier.fancyModifier(enabled) {
            enabled = !enabled
        }
    )
}
Run Code Online (Sandbox Code Playgroud)

您还可以InspectorInfo使用 来声明以帮助调试debugInspectorInfo。来自文档:

如果指定了spectorInfo,则该修饰符将在开发过程中对工具可见。指定原始修饰符的名称和参数。以及可选地声明InspectorInfo以帮助调试。

如果您有超过您想要跟踪的值,properties则可以使用该字段。value

注意:debugInspectorInfo lambda 已从release构建中删除。

class FancyModifierTest {

    @Before
    fun setup() {
        isDebugInspectorInfoEnabled = true
    }

    @After
    fun teardown() {
        isDebugInspectorInfoEnabled = false
    }

    @Test
    fun testFancyModifierInspectableValue() {
        val modifier = Modifier.fancyModifier() as InspectableValue
        assertEquals(modifier.nameFallback, "fancyModifier")
        assertEquals(modifier.valueOverride, false)
        assertEquals(modifier.inspectableElements.toList().size, 0)
    }

}
Run Code Online (Sandbox Code Playgroud)

下面是一些更实际的例子:

  • 是的,差不多。如果您发现自己需要一个组合软件“Modifier”,那么您正在寻找“composed”,而不是将其标记为“@Composable”。 (2认同)
  • 为什么提示“不必要使用Modifier.composed”? (2认同)

Thr*_*ian 8

您可以使用它存储内存繁重的对象,这样每次为特定元素调用该修饰符时就不会实例化。

这个组合会记住带有索引的颜色,因此每次重新组合后它都会返回最初随机创建的颜色。

// Creates stateful modifier with multiple arguments
fun Modifier.composedBackground(width: Dp, height: Dp, index: Int) = composed(
    // pass inspector information for debug
    inspectorInfo = debugInspectorInfo {
        // name should match the name of the modifier
        name = "myModifier"
        // add name and value of each argument
        properties["width"] = width
        properties["height"] = height
        properties["index"] = index
    },
    // pass your modifier implementation that resolved per modified element

    factory = {

        val density = LocalDensity.current

        val color: Color = remember(index) {
            Color(
                red = Random.nextInt(256),
                green = Random.nextInt(256),
                blue = Random.nextInt(256),
                alpha = 255
            )
        }

        // add your modifier implementation here
        Modifier.drawBehind {

            val widthInPx = with(density) { width.toPx() }
            val heightInPx = with(density) { height.toPx() }

            drawRect(color = color, topLeft = Offset.Zero, size = Size(widthInPx, heightInPx))
        }
    }
)
Run Code Online (Sandbox Code Playgroud)

每次可组合项重新组合时都会创建一种颜色

fun Modifier.nonComposedBackground(width: Dp, height: Dp) = this.then(

    // add your modifier implementation here
    Modifier.drawBehind {

        //  Without remember this color is created every time item using this modifier composed
        val color: Color = Color(
            red = Random.nextInt(256),
            green = Random.nextInt(256),
            blue = Random.nextInt(256),
            alpha = 255
        )

        val widthInPx = width.toPx()
        val heightInPx = height.toPx()

        drawRect(color = color, topLeft = Offset.Zero, size = Size(widthInPx, heightInPx))
    }
)
Run Code Online (Sandbox Code Playgroud)

用法

        var counter by remember { mutableStateOf(0) }

        Button(
            onClick = { counter++ },
            modifier = Modifier.fillMaxWidth()
        ) {
            Text(text = "Increase $counter")
        }

        TutorialText2(text = "Modifier.composed")
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {

            Box(
                modifier = Modifier
                    .composedBackground(150.dp, 20.dp, 0)
                    .width(150.dp)
            ) {
                Text(text = "Recomposed $counter")
            }

            Box(
                modifier = Modifier
                    .composedBackground(150.dp, 20.dp, 1)
                    .width(150.dp)
            ) {
                Text(text = "Recomposed $counter")
            }
        }

        TutorialText2(text = "Modifier that is not composed")
        Row(
            modifier = Modifier.fillMaxWidth(),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {

            Box(
                modifier = Modifier
                    .nonComposedBackground(150.dp, 20.dp)
                    .width(150.dp)
            ) {
                Text(text = "Recomposed $counter")
            }

            Box(
                modifier = Modifier
                    .nonComposedBackground(150.dp, 20.dp)
                    .width(150.dp)
            ) {
                Text(text = "Recomposed $counter")
            }
        }
Run Code Online (Sandbox Code Playgroud)

结果

在此输入图像描述

另外,在实践中,您可以创建一个动画抖动修改器,例如

fun Modifier.shake(enabled: Boolean) = composed(

    factory = {
        
        val scale by animateFloatAsState(
            targetValue = if (enabled) .9f else 1f,
            animationSpec = repeatable(
                iterations = 5,
                animation = tween(durationMillis = 50, easing = LinearEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

        Modifier.graphicsLayer {
            scaleX = if (enabled) scale else 1f
            scaleY = if (enabled) scale else 1f
        }
    },
    inspectorInfo = debugInspectorInfo {
        name = "shake"
        properties["enabled"] = enabled
    }
)
Run Code Online (Sandbox Code Playgroud)

用法

Icon(
    imageVector = Icons.Default.NotificationsActive,
    contentDescription = null,
    tint = Color.White,
    modifier = Modifier
        .shake(enabled)
        .background(Color.Red, CircleShape)
        .size(50.dp)
        .padding(10.dp)
)
Run Code Online (Sandbox Code Playgroud)