Jetpack Compose - 避免不必要的重组

use*_*421 3 android-jetpack-compose compose-recomposition

我正在为我的应用程序创建一个自定义滑块控件,但如果不添加一些丑陋的黑客,我无法避免不必要的重组......

CustomSlider1是一个当值改变时重组其所有子组件的组件;CustomSlider2是我想出的,但代码似乎不正确,所以有人可以告诉我我做错了什么CustomSlider1以及是否CustomSlider2确实正确?

这两个组件之间的区别基本上是我通过 lambda 读取值,并将组件添加到ScopedSlider可组合项中。

我正在使用recomposeHighlighter来显示重组。

这是一个 gif,显示了当我更改其值时两者的行为: 在此输入图像描述

这是代码:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            TestTheme {
                Column {
                    var value by remember {
                        mutableStateOf(50f)
                    }

                    CustomSlider1("Custom Slider", value, 50f, true, { value = it }, 0f..100f, 5)
                    Spacer(modifier = Modifier.padding(10.dp))
                    CustomSlider2("Custom Slider 2", { value }, 50f, true, { value = it }, 0f..100f, 5)
                }
            }
        }
    }
}

@Composable
fun CustomSlider1(
    label: String,
    value: Float,
    defaultValue: Float,
    enabled: Boolean = true,
    onValueChange: (Float) -> Unit,
    valueRange: ClosedFloatingPointRange<Float>,
    steps: Int = 0,
) {
    Column(
        modifier = Modifier.recomposeHighlighter()
    ) {
        Text(
            text = label,
            color = if (enabled) Color.Unspecified else LocalContentColor.current.copy(alpha = 0.5f),
            style = MaterialTheme.typography.bodyMedium,
            modifier = Modifier.recomposeHighlighter()
        )
        Row {
            Slider(
                value = value,
                valueRange = valueRange,
                steps = steps,
                enabled = enabled,
                onValueChange = onValueChange,
                modifier = Modifier
                    .recomposeHighlighter()
                    .weight(1f)
            )

            IconButton(
                onClick = { onValueChange(defaultValue) },
                enabled = enabled,
                colors = IconButtonDefaults.iconButtonColors(contentColor = MaterialTheme.colorScheme.primary),
                modifier = Modifier.recomposeHighlighter()
            ) {
                Icon(
                    imageVector = Icons.Filled.Refresh,
                    contentDescription = null,
                    modifier = Modifier.recomposeHighlighter()
                )
            }
        }
    }
}


@Composable
fun CustomSlider2(
    label: String,
    value: () -> Float,
    defaultValue: Float,
    enabled: Boolean = true,
    onValueChange: (Float) -> Unit,
    valueRange: ClosedFloatingPointRange<Float>,
    steps: Int = 0,
) {
    Column(
        modifier = Modifier.recomposeHighlighter()
    ) {
        Text(
            text = label,
            color = if (enabled) Color.Unspecified else LocalContentColor.current.copy(alpha = 0.5f),
            style = MaterialTheme.typography.bodyMedium,
            modifier = Modifier.recomposeHighlighter()
        )
        Row {
            Scoped { //had to do this to avoid recompositions...
                Slider(
                    value = value.invoke(),
                    valueRange = valueRange,
                    steps = steps,
                    enabled = enabled,
                    onValueChange = onValueChange,
                    modifier = Modifier
                        .recomposeHighlighter()
                        .weight(1f)
                )
            }

            IconButton(
                onClick = { onValueChange(defaultValue) },
                enabled = enabled,
                colors = IconButtonDefaults.iconButtonColors(contentColor = MaterialTheme.colorScheme.primary),
                modifier = Modifier.recomposeHighlighter()
            ) {
                Icon(
                    imageVector = Icons.Filled.Refresh,
                    contentDescription = null,
                    modifier = Modifier.recomposeHighlighter()
                )
            }
        }
    }
}

@Composable
fun Scoped(content: @Composable () -> Unit) = content()
Run Code Online (Sandbox Code Playgroud)

Thr*_*ian 6

为了防止重组,您要做的第一件事是创建范围来创建重组范围以限制重组,因为 Column 和 Row 是不创建范围的内联函数。

\n

关于 lambda 的第二件事。在组合中,lambda 是独一无二的,它们将状态读取从框架的组合阶段推迟到布局或绘制阶段,这就是您不需要重新组合的原因。

\n

Composition->Layout(measure 和 Layout)->Draw 是使用 lambda 触发(重新)组合的阶段,您不调用组合阶段。

\n

对于 lambda 和状态延迟,您可以查看下面的官方文档或问题

\n
// Here, assume animateColorBetween() is a function that swaps between\n// two colors\nval color by animateColorBetween(Color.Cyan, Color.Magenta)\n\nBox(Modifier.fillMaxSize().background(color))\n
Run Code Online (Sandbox Code Playgroud)\n
\n

在这里,框的背景颜色在两种颜色之间快速切换。因此,这种状态变化非常频繁。然后可组合项在后台修改器中读取此状态。因此,框\n必须在每一帧上重新组合,因为颜色在每一帧上\n都在变化。

\n

为了改善这一点,我们可以使用基于 lambda 的修饰符\xe2\x80\x93(在本例中为\ndrawBehind)。这意味着颜色状态仅在绘制阶段读取。因此,Compose 可以完全跳过合成和布局阶段\n\n\xe2\x80\x93,当颜色发生变化时,Compose 直接进入绘制\n阶段。

\n
\n
val color by animateColorBetween(Color.Cyan, Color.Magenta) Box(    Modifier\n      .fillMaxSize()\n      .drawBehind {\n         drawRect(color)\n      } )\n
Run Code Online (Sandbox Code Playgroud)\n

Android Jetpack Compose - 每次文本字段值更改时可组合函数都会重新组合

\n

对于范围合成,您可以查看此问题或与其链接的其他答案

\n