如何在 Jetpack Compose 中管理二维网格 UI 的状态?
How to manage state for a 2D-grid UI in Jetpack Compose?
我使用 LazyVerticalGrid (Snake Game App and Path Finding Visualizer).
创建了 2 个 animated 网格应用程序
该程序按预期工作,但对于 Path Finding 应用程序,正在进行非常复杂的动画和计算(与 Snake 应用程序相比),我注意到动画略有滞后或缓慢,我在想这可能是关于如何在二维网格中管理状态。
从当前版本 (1.0.0-rc01) 开始,我们可以使用以下代码组成二维网格 UI:
@Composable
fun GridUI(gridData: List<GridData>) {
LazyVerticalGrid(cells = GridCells.Fixed(40)) {
items(gridData) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
// Cell Ui here
}
}
}
}
请注意,可组合项接受二维网格数据列表,转换为线性列表(在本例中为 40 行)。对于我的寻路应用程序,二维网格大约为 20 x 40,这将产生大约 800 个单元格或 800 个线性网格大小。
对于动画中的每一帧(每帧延迟20毫秒),整个网格状态都会更新,根据Compose的状态管理思想,它会计算数据的前一个状态和当前状态之间的差异。
所以问题; Compose 是否足够智能以计算 2d 网格(本例中大约 800 个单元格)之间的差异并每 20 毫秒重新组合一次,或者是否有任何其他方法可以改进此用例中网格的状态管理方法?
例如在蛇应用程序中,在每一帧中,蛇只向一个方向移动,而 UI 的其余部分保持不变,因此网格状态的变化非常小。 Compose 中有没有办法利用这种情况来改进重组?
你是 Compose 方面的相对专家,所以我会先给出其他人的答案,然后再循环回到你的问题。
一般来说,您不应该试图推断 Compose 编译器会做什么或不会做什么,因为这些优化是实现细节,随时可能更改,恕不另行通知。特别是,如果用户试图避免重新组合特定小部件的问题表明用户对问题的思考是错误的。正确性决定了您既不知道也不关心您的可组合项何时或多久重新组合一次,并且如果需要,Compose 应该可以自由地在每一帧上重新 运行 您的可组合项。撇开正确性不谈,用户询问的第二个最常见的原因是他们正在进行过早的性能优化;仅在测量问题后进行优化。
现在针对您的特定用例:您已清楚地完成测量,并正在寻求优化...
(1) 我的第一个问题是,您的GridData
class 是否被注释为androidx/compose/runtime/Immutable
?如果没有,那是我要做的第一件事。将 class 注释为不可变将导致 Compose 使用 .equals
调用来确定值是否已更改,因此可能会表现得更好。
(2) 尝试定义您自己的名为 GridCell
的可组合项,它只接受一个 GridData
并渲染单元格。即使此 GridCell
调用一个列,它也不会内联,除非明确标记为内联。不要将其标记为内联。我不确定这是否相关,但可能是因为 Column 是一个内联函数,每次任何子级更改时,父级范围都会被完全重新组合,这可能很昂贵。使用非内联 GridCell 可组合项应该有助于消除这种可能性。
(3) 不是每次都传入一个新列表,而是创建一个可变 GridData 对象列表,其中 GridData 是一个 class,具有一个或多个字段,由 mutableStateOf (val cellColor by mutableStateOf(...)
) 而不是重新创建您的列表和 GridData,只需更新现有的网格。这对于大多数网格在帧与帧之间相同但少数元素发生变化的情况特别有用。
我使用 LazyVerticalGrid (Snake Game App and Path Finding Visualizer).
创建了 2 个 animated 网格应用程序该程序按预期工作,但对于 Path Finding 应用程序,正在进行非常复杂的动画和计算(与 Snake 应用程序相比),我注意到动画略有滞后或缓慢,我在想这可能是关于如何在二维网格中管理状态。
从当前版本 (1.0.0-rc01) 开始,我们可以使用以下代码组成二维网格 UI:
@Composable
fun GridUI(gridData: List<GridData>) {
LazyVerticalGrid(cells = GridCells.Fixed(40)) {
items(gridData) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
// Cell Ui here
}
}
}
}
请注意,可组合项接受二维网格数据列表,转换为线性列表(在本例中为 40 行)。对于我的寻路应用程序,二维网格大约为 20 x 40,这将产生大约 800 个单元格或 800 个线性网格大小。
对于动画中的每一帧(每帧延迟20毫秒),整个网格状态都会更新,根据Compose的状态管理思想,它会计算数据的前一个状态和当前状态之间的差异。
所以问题; Compose 是否足够智能以计算 2d 网格(本例中大约 800 个单元格)之间的差异并每 20 毫秒重新组合一次,或者是否有任何其他方法可以改进此用例中网格的状态管理方法?
例如在蛇应用程序中,在每一帧中,蛇只向一个方向移动,而 UI 的其余部分保持不变,因此网格状态的变化非常小。 Compose 中有没有办法利用这种情况来改进重组?
你是 Compose 方面的相对专家,所以我会先给出其他人的答案,然后再循环回到你的问题。
一般来说,您不应该试图推断 Compose 编译器会做什么或不会做什么,因为这些优化是实现细节,随时可能更改,恕不另行通知。特别是,如果用户试图避免重新组合特定小部件的问题表明用户对问题的思考是错误的。正确性决定了您既不知道也不关心您的可组合项何时或多久重新组合一次,并且如果需要,Compose 应该可以自由地在每一帧上重新 运行 您的可组合项。撇开正确性不谈,用户询问的第二个最常见的原因是他们正在进行过早的性能优化;仅在测量问题后进行优化。
现在针对您的特定用例:您已清楚地完成测量,并正在寻求优化...
(1) 我的第一个问题是,您的GridData
class 是否被注释为androidx/compose/runtime/Immutable
?如果没有,那是我要做的第一件事。将 class 注释为不可变将导致 Compose 使用 .equals
调用来确定值是否已更改,因此可能会表现得更好。
(2) 尝试定义您自己的名为 GridCell
的可组合项,它只接受一个 GridData
并渲染单元格。即使此 GridCell
调用一个列,它也不会内联,除非明确标记为内联。不要将其标记为内联。我不确定这是否相关,但可能是因为 Column 是一个内联函数,每次任何子级更改时,父级范围都会被完全重新组合,这可能很昂贵。使用非内联 GridCell 可组合项应该有助于消除这种可能性。
(3) 不是每次都传入一个新列表,而是创建一个可变 GridData 对象列表,其中 GridData 是一个 class,具有一个或多个字段,由 mutableStateOf (val cellColor by mutableStateOf(...)
) 而不是重新创建您的列表和 GridData,只需更新现有的网格。这对于大多数网格在帧与帧之间相同但少数元素发生变化的情况特别有用。