首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Jetpack编写SubcomposeLayout是如何工作的?

Jetpack编写SubcomposeLayout是如何工作的?
EN

Stack Overflow用户
提问于 2021-10-21 13:48:29
回答 3查看 6K关注 0票数 10

正如在正式文件中所看到的,有一个名为SubcomposeLayout的布局被定义为

布局的模拟,允许在测量阶段细分实际内容,例如,使用测量期间计算的值作为儿童组合的参数。 可能的用例: 您需要知道在组合过程中父级传递的约束,并且不能用自定义布局或LayoutModifier来解决您的用例。请参阅androidx.compose.foundation.layout.BoxWithConstraints. 您希望在第二个子级的组合中使用一个子级的大小。 您希望根据可用的大小懒洋洋地撰写项目。例如,您有一个包含100项的列表,而不是将它们全部组合起来,而是只编写当前可见的项(比如其中的5项),并在组件滚动时组成下一项。

我用SubcomposeLayout关键字搜索Stackoverflow,但是找不到它的任何内容,创建了这个示例代码,从正式文档中复制了其中的大部分代码,以测试和学习它的工作原理。

代码语言:javascript
运行
复制
@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执行所提供内容的子组合。Params: slotId -唯一的id,它表示我们要组合到的槽。如果您有固定的数量或插槽,您可以使用枚举作为槽in,或者如果您有一个项目列表,可能是列表中的索引或其他一些唯一的键可以工作。为了能够正确匹配重测量之间的内容,您应该提供与您在上一次度量中使用的对象相等的对象。content -定义槽的可组合内容。它可以发出多个布局,在这种情况下,返回的可度量列表将包含多个元素。

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-10-21 16:52:18

它应该根据另一个组件的大小来重新测量一个组件.

SubcomposeLayout不重测。它允许将内容的组成和度量推迟到它的父约束已知,并且它的一些内容可以被测量,结果可以作为参数传递给延迟的内容。上面的示例计算mainContent生成的内容的最大大小,并将其作为参数传递给deferredContent。然后,它度量deferredContent,并将mainContentdeferredContent放在一起。

如何使用SubcomposeLayout的最简单的例子是BoxWithConstraints,它只是将从父服务器接收到的约束直接传递给它的内容。在布局过程中发生的父级度量了盒的兄弟节点之后,才知道盒的约束,因此content的组合被推迟到布局。

类似地,对于上面的示例,只有在布局中计算出maxSize mainContent之后,才知道deferredContent在布局中被调用。它总是将deferredContent放在mainContent之上,因此假定deferredContent以某种方式使用maxSize以避免掩盖mainContent生成的内容。可能不是一个可合成的最好的设计,但可合成的意图是说明性,而不是有用的本身。

注意,subcompose可以在layout块中多次调用。例如,这就是在LazyRow中发生的事情。slotId允许SubcomposeLayout跟踪和管理通过调用subcompose创建的组合。例如,如果要从数组生成内容,则可能需要使用数组的索引作为其slotId,允许SubcomposeLayout确定在重新组合期间应该使用上次生成的subcompose。另外,如果不再使用slotidSubcomposeLayout将释放其相应的组合。

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

票数 15
EN

Stack Overflow用户

发布于 2021-12-16 18:04:33

我根据官方文件提供的样本和@chuckj的答复制作了一个示例,但仍然不确定这种实现方法是否有效或正确。

它基本上测量最长的组件集,父本宽度,用minimumWidth of Constraint重测量较短的组件集,并调整短组件集的大小,如本文所示。这就是whatsapp如何缩放引用和消息长度的基本方法。

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

代码语言:javascript
运行
复制
@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 }

用法

代码语言:javascript
运行
复制
@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
                    )
                }
            }
        )
    }
}

完整的源代码是这里

票数 2
EN

Stack Overflow用户

发布于 2022-04-08 06:52:28

最近,我需要使用几乎相同的SubcomposeLayout。我需要一个滑翔机,我需要一个可组合的拇指,我需要得到它的宽度,以便我可以设置开始和结束的轨道和完全宽度的滑雪板,我从BoxWithConstraints得到。

代码语言:javascript
运行
复制
enum class SlotsEnum {
    Slider, Thumb
}

/**
 * [SubcomposeLayout] that measure [thumb] size to set Slider's track start and track width.
 * @param thumb thumb Composable
 * @param slider Slider composable that contains **thumb** and **track** of this Slider.
 */
@Composable
private fun SliderComposeLayout(
    modifier: Modifier = Modifier,
    thumb: @Composable () -> Unit,
    slider: @Composable (IntSize, Constraints) -> Unit
) {
    SubcomposeLayout(modifier = modifier) { constraints: Constraints ->

        // Subcompose(compose only a section) main content and get Placeable
        val thumbPlaceable: Placeable = subcompose(SlotsEnum.Thumb, thumb).map {
            it.measure(constraints)
        }.first()

        // Width and height of the thumb Composable
        val thumbSize = IntSize(thumbPlaceable.width, thumbPlaceable.height)

        // Whole Slider Composable
        val sliderPlaceable: Placeable = subcompose(SlotsEnum.Slider) {
            slider(thumbSize, constraints)
        }.map {
            it.measure(constraints)
        }.first()


        val sliderWidth = sliderPlaceable.width
        val sliderHeight = sliderPlaceable.height

        layout(sliderWidth, sliderHeight) {
            sliderPlaceable.placeRelative(0, 0)
        }
    }
}

测量拇指,并将其尺寸作为IntSize和约束发送给滑翔机,并且只放置滑翔机,因为拇指已经被放置在内部滑翔机,放置在这里会创建两个拇指。

并把它当作

代码语言:javascript
运行
复制
    SliderComposeLayout(
        modifier = modifier
            .minimumTouchTargetSize()
            .requiredSizeIn(
                minWidth = ThumbRadius * 2,
                minHeight = ThumbRadius * 2,
            ),
        thumb = { thumb() }
    ) { thumbSize: IntSize, constraints: Constraints ->

        val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl

        val width = constraints.maxWidth.toFloat()
        val thumbRadiusInPx = (thumbSize.width / 2).toFloat()

        // Start of the track used for measuring progress,
        // it's line + radius of cap which is half of height of track
        // to draw this on canvas starting point of line
        // should be at trackStart + trackHeightInPx / 2 while drawing
        val trackStart: Float
        // End of the track that is used for measuring progress
        val trackEnd: Float
        val strokeRadius: Float
        with(LocalDensity.current) {

            strokeRadius = trackHeight.toPx() / 2
            trackStart = thumbRadiusInPx.coerceAtLeast(strokeRadius)
            trackEnd = width - trackStart
        }
    // Rest of the code
}

结果

代码的Github链接

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69663194

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档