Jetpack Compose 使用 SubcomposeLayout 动态地将兄弟 Composables 的宽度设置为最长的宽度
Posted
技术标签:
【中文标题】Jetpack Compose 使用 SubcomposeLayout 动态地将兄弟 Composables 的宽度设置为最长的宽度【英文标题】:Jetpack Compose set sibling Composables' width to longest one dynamically with SubcomposeLayout 【发布时间】:2022-01-18 11:40:08 【问题描述】:如何设置一组组合的宽度,从上到下的兄弟布局,最长的宽度?
我尝试构建的内容与上面的图片完全相同。为简单起见,假设 quote 顶部的组件和 message box 包含消息和另一个存储日期和消息状态的容器。
quote 和 message box 中最长的一个必须设置为父宽度,另一个必须设置为与最长的宽度相同的宽度,这需要重新测量我假设的短的。
此外,如果 message box
调整大小,则需要一个内部参数传递此宽度以设置存储日期和状态的容器的位置。可以清楚地看到,当 quote 长于 message box 时,消息文本移动到开始,而状态结束。当消息有多于一行时,消息框的宽度和高度是通过电报或whatsapp的计算来设置的。
最初使用Layout
构建它
@Composable
private fun DynamicLayout(
modifier: Modifier = Modifier,
quote: @Composable () -> Unit,
message: @Composable () -> Unit
)
val content = @Composable
quote()
message()
Layout(content = content, modifier = modifier) measurables, constraints ->
val placeableQuote = measurables.first().measure(constraints)
val quoteWidth = placeableQuote.width
val placeableMessage =
measurables.last()
.measure(Constraints(minWidth = quoteWidth, maxWidth = constraints.maxWidth))
val messageWidth = placeableMessage.width
val maxWidth = quoteWidth.coerceAtLeast(messageWidth)
val totalHeight = placeableQuote.height + placeableMessage.height
layout(maxWidth, totalHeight)
placeableQuote.placeRelative(x = 0, y = 0)
placeableMessage.placeRelative(x = 0, y = placeableQuote.height)
在我使用 quote 约束的宽度测量 message box 的情况下,它可以工作,但仅在quote 较长时才有效。
DynamicLayout(
quote =
Text(
"QUOTE with a very long text",
modifier = Modifier
.background(Color(0xffF44336))
.height(60.dp),
color = Color.White
)
,
message =
Text(
"MESSAGE Content",
modifier = Modifier
.background(Color(0xff9C27B0)),
color = Color.White
)
)
DynamicLayout(
quote =
Text(
"QUOTE",
modifier = Modifier
.background(Color(0xffF44336))
.height(60.dp),
color = Color.White
)
,
message =
Text(
"MESSAGE with very long Content",
modifier = Modifier
.background(Color(0xff9C27B0)),
color = Color.White
)
)
由于必须重新测量,我认为应该使用SubComposeLayout
解决这个问题,但无法弄清楚如何将其用于此设置?
@Composable
private fun SubComponentLayout(
modifier: Modifier = Modifier,
mainContent: @Composable () -> Unit,
dependentContent: @Composable (Int) -> Unit
)
SubcomposeLayout(modifier = modifier) constraints ->
val mainMeasurables: List<Measurable> = subcompose(SlotsEnum.Main, mainContent)
val mainPlaceables: List<Placeable> = mainMeasurables.map
it.measure(constraints)
val maxSize =
mainPlaceables.fold(IntSize.Zero) currentMax: IntSize, placeable: Placeable ->
IntSize(
width = maxOf(currentMax.width, placeable.width),
height = maxOf(currentMax.height, placeable.height)
)
var maxWidth =
mainPlaceables.maxOf it.width
layout(maxSize.width, maxSize.height)
println("???? SubcomposeLayout-> layout() maxSize width: $maxSize.width, height: $maxSize.height")
val dependentMeasurables: List<Measurable> = subcompose(
slotId = SlotsEnum.Dependent,
content =
println("???? SubcomposeLayout-> layout()->subcompose() mainWidth ZERO")
dependentContent(0)
)
val dependentPlaceables: List<Placeable> = dependentMeasurables.map
it.measure(constraints)
maxWidth = maxWidth.coerceAtLeast(
dependentPlaceables.maxOf it.width
)
subcompose(SlotsEnum.NEW)
println("???? SubcomposeLayout-> layout()->subcompose() maxWidth: $maxWidth")
dependentContent(maxWidth)
mainPlaceables.forEach it.placeRelative(0, 0)
dependentPlaceables.forEach it.placeRelative(0, 150)
为什么不能用相同的 id 第二次重新测量相同的组件?当我尝试使用 SlotsEnum.Dependent 调用 subCompose
时,它会引发异常
subcompose(SlotsEnum.NEW)
println("???? SubcomposeLayout-> layout()->subcompose() maxWidth: $maxWidth")
dependentContent(maxWidth)
调用后仍然无法正确重新测量? SubcomposeLayout
如何解决设置兄弟的问题?
【问题讨论】:
【参考方案1】:我根据官方文档提供的样本和@chuckj的回答here做了一个样本。
橙色和粉红色的容器是 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。
【讨论】:
以上是关于Jetpack Compose 使用 SubcomposeLayout 动态地将兄弟 Composables 的宽度设置为最长的宽度的主要内容,如果未能解决你的问题,请参考以下文章
Android Jetpack Compose学习—— Jetpack compose基础布局
Android Jetpack Compose学习—— Jetpack compose基础布局
Android Jetpack Compose学习—— Jetpack compose基础布局
什么是Jetpack Compose?带你走进Jetpack Compose~