【问题标题】:Jetpack Compose how does SubcomposeLayout work?Jetpack Compose SubcomposeLayout 是如何工作的?
【发布时间】:2021-10-21 13:48:29
【问题描述】:

official documents 中可以看到有一个名为 SubcomposeLayout 的布局定义为

布局的模拟,允许子组合实际内容 在测量阶段,例如使用计算的值 在测量过程中作为孩子组成的参数。

可能的用例:

你需要知道父进程在 组合并且不能仅使用自定义布局来解决您的用例或 布局修改器。看 androidx.compose.foundation.layout.BoxWithConstraints。

您想在构图过程中使用一个孩子的大小 第二个孩子。

您想根据可用尺寸懒惰地组合您的项目。为了 例如,您有一个包含 100 个项目的列表,而不是组成所有 他们你只组成当前可见的那些(比如 5 个 它们)并在组件滚动时组成下一个项目。

我用SubcomposeLayout关键字搜索了Stackoverflow,但找不到任何相关信息,创建了这个示例代码,从官方文档中复制了大部分内容,以测试和了解它是如何工作的

@Composable
private fun SampleContent() {

    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
    ) {
        SubComponent(
            mainContent = {
                Text(
                    "MainContent",
                    modifier = Modifier
                        .background(Color(0xffF44336))
                        .height(60.dp),
                    color = Color.White
                )
            },
            dependentContent = {
                val size = it

                println("???? Dependent size: $size")
                Column() {

                    Text(
                        "Dependent Content",
                        modifier = Modifier
                            .background(Color(0xff9C27B0)),
                        color = Color.White
                    )
                }
            }
        )

    }
}

@Composable
private fun SubComponent(
    mainContent: @Composable () -> Unit,
    dependentContent: @Composable (IntSize) -> Unit
) {

    SubcomposeLayout { constraints ->

        val mainPlaceables = subcompose(SlotsEnum.Main, mainContent).map {
            it.measure(constraints)

        }

        val maxSize = mainPlaceables.fold(IntSize.Zero) { currentMax, placeable ->
            IntSize(
                width = maxOf(currentMax.width, placeable.width),
                height = maxOf(currentMax.height, placeable.height)
            )
        }

        layout(maxSize.width, maxSize.height) {

            mainPlaceables.forEach { it.placeRelative(0, 0) }

            subcompose(SlotsEnum.Dependent) {
                dependentContent(maxSize)
            }.forEach {
                it.measure(constraints).placeRelative(0, 0)
            }

        }
    }
}

enum class SlotsEnum { Main, Dependent }

它应该根据另一个组件的大小重新测量一个组件,但这段代码的实际作用对我来说是个谜。

  1. subcompose 函数是如何工作的?
  2. slotId 的意义何在?我们能否以某种方式获取 slotId?

subCompose函数说明

使用给定的 slotId 执行提供的内容的子组合。 参数: slotId - 代表我们正在组成的插槽的唯一 ID 进入。如果您有固定数量或插槽,您可以使用枚举作为插槽 id, 或者如果您有一个项目列表,可能是列表中的索引或一些 其他唯一键可以工作。为了能够正确匹配内容 在重新测量之间,您应该提供等于的对象 您在上一次测量中使用的那个。内容—— 定义插槽的可组合内容。它可以发出多个 布局,在这种情况下,返回的 Measurables 列表将具有 多个元素。

有人可以解释它的含义或/并为SubcomposeLayout 提供工作示例吗?

【问题讨论】:

    标签: android android-jetpack-compose


    【解决方案1】:

    它应该根据另一个组件尺寸重新测量一个组件...

    SubcomposeLayout 不会重新测量。它允许推迟内容的组成和测量,直到其父项的约束已知并且可以测量其内容,从中可以将结果作为参数传递给延迟的内容。上面的例子计算了mainContent生成的内容的最大尺寸,并将其作为参数传递给deferredContent。然后它测量deferredContent 并将mainContentdeferredContent 放在一起。

    如何使用SubcomposeLayout 的最简单示例是BoxWithConstraints,它只是将其从父级接收到的约束直接传递给其内容。在布局期间发生的父级测量框的兄弟姐妹之前,框的约束是未知的,因此content 的组合被推迟到布局。

    同样,对于上面的示例,mainContentmaxSize 在布局之前是未知的,因此一旦计算了 maxSize,就会在布局中调用 deferredContent。它总是将deferredContent 放在mainContent 之上,因此假定deferredContent 以某种方式使用maxSize 以避免混淆mainContent 生成的内容。可能不是可组合的最佳设计,但可组合的目的是说明性的,本身没有用处。

    请注意,subcompose 可以在 layout 块中多次调用。例如,LazyRow 中发生的情况。 slotId 允许SubcomposeLayout 跟踪和管理通过调用subcompose 创建的作品。例如,如果您从数组生成内容,您可能希望使用数组的索引作为其slotId,从而允许SubcomposeLayout 确定上次生成的subcompose 应该在重组期间使用。另外,如果 slotid 不再使用,SubcomposeLayout 将处理其对应的组合。

    至于slotId去哪里,这取决于SubcomposeLayout的调用者。如果内容需要它,请将其作为参数传递。上面的例子不需要它,因为slotId 对于deferredContent 总是一样的,所以它不需要去任何地方。

    【讨论】:

    • 如果我想使用不是mainComponent 的maxSize 而是使用这两个composables 的最大值怎么办?我需要一个示例来获得两个可组合物中最长的一个,然后将短的宽度设置为与长的相同。我如何使用SubcomposeLayout 实现这一目标?
    • 注意subcompose可以在布局块中多次调用。这个怎么调用呢?当我调用它时,它会抛出一个异常并要求一个新的 id。而且只能在layoutblock 内部调用吗? mainComponentdependentComponentlayout 块内部或外部调用 subcompose 有什么不同吗?
    • 每次调用 subcompose 都必须有一个唯一的 id。它只能在布局块内部调用,因为它是对 LayoutScope 接收器的调用。
    【解决方案2】:

    我根据官方文档提供的示例和@chuckj的回答做了一个示例,但仍然不确定这种实施方式是否有效或正确。

    它基本上测量最长的组件集父宽度并使用minimumWidthConstraint 重新测量较短的组件集,并调整短组件集的大小,如此 gif 所示。这就是 whatsapp 基本上是如何缩放报价和消息长度的。

    橙色和粉红色的容器是 Columns,它们指导 DynamicWidthLayout 的子级,使用 SubcomposeLayout 重新测量。

    @Composable
    private fun DynamicWidthLayout(
        modifier: Modifier = Modifier,
        mainContent: @Composable () -> Unit,
        dependentContent: @Composable (IntSize) -> Unit
    ) {
    
        SubcomposeLayout(modifier = modifier) { constraints ->
    
    
            var mainPlaceables: List<Placeable> = subcompose(SlotsEnum.Main, mainContent).map {
                it.measure(constraints)
            }
    
            var maxSize =
                mainPlaceables.fold(IntSize.Zero) { currentMax: IntSize, placeable: Placeable ->
                    IntSize(
                        width = maxOf(currentMax.width, placeable.width),
                        height = maxOf(currentMax.height, placeable.height)
                    )
                }
    
            val dependentMeasurables: List<Measurable> = subcompose(SlotsEnum.Dependent) {
                // ?? Send maxSize of mainComponent to
                // dependent composable in case it might be used
                dependentContent(maxSize)
            }
    
            val dependentPlaceables: List<Placeable> = dependentMeasurables
                .map { measurable: Measurable ->
                    measurable.measure(Constraints(maxSize.width, constraints.maxWidth))
                }
    
            // Get maximum width of dependent composable
            val maxWidth = dependentPlaceables.maxOf { it.width }
    
    
            println("? DynamicWidthLayout-> maxSize width: ${maxSize.width}, height: ${maxSize.height}")
    
            // If width of dependent composable is longer than main one, remeasure main one
            // with dependent composable's width using it as minimumWidthConstraint
            if (maxWidth > maxSize.width) {
    
                println("? DynamicWidthLayout REMEASURE MAIN COMPONENT")
    
                // !!! ?? CANNOT use SlotsEnum.Main here why?
                mainPlaceables = subcompose(2, mainContent).map {
                    it.measure(Constraints(maxWidth, constraints.maxWidth))
                }
            }
    
            // Our final maxSize is longest width and total height of main and dependent composables
            maxSize = IntSize(
                maxSize.width.coerceAtLeast(maxWidth),
                maxSize.height + dependentPlaceables.maxOf { it.height }
            )
    
    
            layout(maxSize.width, maxSize.height) {
    
                // Place layouts
                mainPlaceables.forEach { it.placeRelative(0, 0) }
                dependentPlaceables.forEach {
                    it.placeRelative(0, mainPlaceables.maxOf { it.height })
                }
            }
        }
    }
    
    
    enum class SlotsEnum { Main, Dependent }
    

    用法

    @Composable
    private fun TutorialContent() {
    
        val density = LocalDensity.current.density
    
        Column(
            modifier = Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState())
        ) {
    
    
            var mainText by remember { mutableStateOf(TextFieldValue("Main Component")) }
            var dependentText by remember { mutableStateOf(TextFieldValue("Dependent Component")) }
    
    
            OutlinedTextField(
                modifier = Modifier
                    .padding(horizontal = 8.dp)
                    .fillMaxWidth(),
                value = mainText,
                label = { Text("Main") },
                placeholder = { Text("Set text to change main width") },
                onValueChange = { newValue: TextFieldValue ->
                    mainText = newValue
                }
            )
    
            OutlinedTextField(
                modifier = Modifier
                    .padding(horizontal = 8.dp)
                    .fillMaxWidth(),
                value = dependentText,
                label = { Text("Dependent") },
                placeholder = { Text("Set text to change dependent width") },
                onValueChange = { newValue ->
                    dependentText = newValue
                }
            )
    
            DynamicWidthLayout(
                modifier = Modifier
                    .padding(8.dp)
                    .background(Color.LightGray)
                    .padding(8.dp),
                mainContent = {
    
                    println("? DynamicWidthLayout-> MainContent {} composed")
    
                    Column(
                        modifier = Modifier
                            .background(orange400)
                            .padding(4.dp)
                    ) {
                        Text(
                            text = mainText.text,
                            modifier = Modifier
                                .background(blue400)
                                .height(40.dp),
                            color = Color.White
                        )
                    }
                },
                dependentContent = { size: IntSize ->
    
    
                    // ? Measure max width of main component in dp  retrieved
                    // by subCompose of dependent component from IntSize
                    val maxWidth = with(density) {
                        size.width / this
                    }.dp
    
                    println(
                        "? DynamicWidthLayout-> DependentContent composed " +
                                "Dependent size: $size, "
                                + "maxWidth: $maxWidth"
                    )
    
                    Column(
                        modifier = Modifier
                            .background(pink400)
                            .padding(4.dp)
                    ) {
    
                        Text(
                            text = dependentText.text,
                            modifier = Modifier
                                .background(green400),
                            color = Color.White
                        )
                    }
                }
            )
        }
    }
    

    完整的源代码是here

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-19
      • 2023-01-20
      • 2022-09-23
      • 2022-01-18
      • 1970-01-01
      • 2021-10-05
      • 2021-07-17
      相关资源
      最近更新 更多