你什么时候需要 Modifier.composed { ... }?
When do you need Modifier.composed { ... }?
Modifier.composed { ... }
什么时候有用?如果我可以简单地在 Modifier.composed { PaddingModifier(...) }
上执行 Modifier.padding()
,为什么我需要它?
Modifier.composed allows the creation of a composition-aware modifier-factory that's useful for materializing 实例特定,有状态 修饰符。来自文档:
Declare a just-in-time composition of a Modifier that will be composed for each element it modifies. composed may be used to implement stateful modifiers that have instance-specific state for each modified element, allowing the same Modifier instance to be safely reused for multiple elements while maintaining element-specific state.
换句话说,它允许您将提升状态注入特定于元素的 Modifier
并使用 remember
、DisposableEffect
、Ambient
等。例如:
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)
}
LazyColumnFor(items = List(size = 10) { "$it" }) {
var enabled by remember { mutableStateOf(false) }
Text(
text = "fancy modifier",
modifier = Modifier.fancyModifier(enabled) {
enabled = !enabled
}
)
}
您还可以通过使用 debugInspectorInfo
声明 InspectorInfo
来帮助调试。来自文档:
If inspectorInfo is specified this modifier will be visible to tools during development. Specify the name and arguments of the original modifier.
as well as optionally declare InspectorInfo
to help with debugging.
如果您想要跟踪的值超过了值,则可以使用 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)
}
}
这里有一些更实际的例子:
您可以使用它存储内存繁重的对象,而不是每次为特定元素调用该修饰符时都实例化。
这个具有组合的颜色会记住带有索引的颜色,因此在每次重新组合后它 returns 最初随机创建的颜色。
// 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))
}
}
)
每次重新组合可组合项时,这个都会创建一种颜色
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))
}
)
用法
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")
}
}
结果
同样在实际情况下,我使用它绘制彩色阴影,它依赖于 Paint
和 remember
块中的原生 Paint
对象,具有与 remember
的键相同的状态对象.
fun Modifier.materialShadow(badgeState: BadgeState) = composed(
inspectorInfo = {
name = "shadow"
value = badgeState.shadow
},
factory = {
if (badgeState.shadow != null) {
val shadow: MaterialShadow = badgeState.shadow!!
val isCircleShape = badgeState.isCircleShape
val paint = remember(badgeState) {
Paint()
}
val frameworkPaint = remember(badgeState) {
paint.asFrameworkPaint()
}
drawBehind {
this.drawIntoCanvas {
val color = shadow.color
val dx = shadow.offsetX.toPx()
val dy = shadow.offsetY.toPx()
val radius = shadow.shadowRadius.toPx()
val shadowColor = color
.copy(alpha = shadow.alpha)
.toArgb()
val transparent = color
.copy(alpha = 0f)
.toArgb()
frameworkPaint.color = transparent
frameworkPaint.setShadowLayer(
dx,
dy,
radius,
shadowColor
)
if (isCircleShape) {
it.drawCircle(
center = Offset(center.x, center.y),
radius = this.size.width / 2f,
paint = paint
)
} else {
it.drawRoundRect(
left = 0f,
top = 0f,
right = this.size.width,
bottom = this.size.height,
radiusX = size.height * badgeState.roundedRadiusPercent / 100,
radiusY = size.height * badgeState.roundedRadiusPercent / 100,
paint = paint
)
}
}
}
} else {
this
}
}
)
Modifier.composed { ... }
什么时候有用?如果我可以简单地在 Modifier.composed { PaddingModifier(...) }
上执行 Modifier.padding()
,为什么我需要它?
Modifier.composed allows the creation of a composition-aware modifier-factory that's useful for materializing 实例特定,有状态 修饰符。来自文档:
Declare a just-in-time composition of a Modifier that will be composed for each element it modifies. composed may be used to implement stateful modifiers that have instance-specific state for each modified element, allowing the same Modifier instance to be safely reused for multiple elements while maintaining element-specific state.
换句话说,它允许您将提升状态注入特定于元素的 Modifier
并使用 remember
、DisposableEffect
、Ambient
等。例如:
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)
}
LazyColumnFor(items = List(size = 10) { "$it" }) {
var enabled by remember { mutableStateOf(false) }
Text(
text = "fancy modifier",
modifier = Modifier.fancyModifier(enabled) {
enabled = !enabled
}
)
}
您还可以通过使用 debugInspectorInfo
声明 InspectorInfo
来帮助调试。来自文档:
If inspectorInfo is specified this modifier will be visible to tools during development. Specify the name and arguments of the original modifier. as well as optionally declare
InspectorInfo
to help with debugging.
如果您想要跟踪的值超过了值,则可以使用 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)
}
}
这里有一些更实际的例子:
您可以使用它存储内存繁重的对象,而不是每次为特定元素调用该修饰符时都实例化。
这个具有组合的颜色会记住带有索引的颜色,因此在每次重新组合后它 returns 最初随机创建的颜色。
// 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))
}
}
)
每次重新组合可组合项时,这个都会创建一种颜色
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))
}
)
用法
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")
}
}
结果
同样在实际情况下,我使用它绘制彩色阴影,它依赖于 Paint
和 remember
块中的原生 Paint
对象,具有与 remember
的键相同的状态对象.
fun Modifier.materialShadow(badgeState: BadgeState) = composed(
inspectorInfo = {
name = "shadow"
value = badgeState.shadow
},
factory = {
if (badgeState.shadow != null) {
val shadow: MaterialShadow = badgeState.shadow!!
val isCircleShape = badgeState.isCircleShape
val paint = remember(badgeState) {
Paint()
}
val frameworkPaint = remember(badgeState) {
paint.asFrameworkPaint()
}
drawBehind {
this.drawIntoCanvas {
val color = shadow.color
val dx = shadow.offsetX.toPx()
val dy = shadow.offsetY.toPx()
val radius = shadow.shadowRadius.toPx()
val shadowColor = color
.copy(alpha = shadow.alpha)
.toArgb()
val transparent = color
.copy(alpha = 0f)
.toArgb()
frameworkPaint.color = transparent
frameworkPaint.setShadowLayer(
dx,
dy,
radius,
shadowColor
)
if (isCircleShape) {
it.drawCircle(
center = Offset(center.x, center.y),
radius = this.size.width / 2f,
paint = paint
)
} else {
it.drawRoundRect(
left = 0f,
top = 0f,
right = this.size.width,
bottom = this.size.height,
radiusX = size.height * badgeState.roundedRadiusPercent / 100,
radiusY = size.height * badgeState.roundedRadiusPercent / 100,
paint = paint
)
}
}
}
} else {
this
}
}
)