kotlin 如何根据另一项调整撰写列中项的大小

jm81lzqq  于 2023-08-06  发布在  Kotlin
关注(0)|答案(2)|浏览(117)

我在Compose中有一个“简单”的布局,在Column中有两个元素:

  • 顶部图像
  • 下面有4个方格,每个方格包含一些文本

我希望布局具有以下行为:

  • 设置图像的最大高度为screenWidth
  • 设置图像的最小高度为200.dp
  • 图像应始终在一个正方形容器中(图像的裁剪很好)
  • 让网格根据需要“增长”,以包围内容,使图像根据需要收缩

这意味着如果方块中的文本很短,图像将覆盖顶部的一个大方块。但如果任何一个方形文本真的很长,我希望整个网格按比例放大并缩小图像。以下是理想的结果:
1.当文本足够短时
x1c 0d1x的数据
1.当一段文字很长的时候



我已经在Compose中用ConstraintLayout尝试过了,但是我不能正确地缩放方块。
使用Column,我无法获得随大内容增长的选项-文本只是被截断,图像仍然是一个巨大的正方形。
这些是我构建的组件:

// the screen

Column {
    Box(modifier = Modifier
        .heightIn(min = 200.dp, max = screenWidth)
        .aspectRatio(1f)
        .border(BorderStroke(1.dp, Color.Green))
        .align(Alignment.CenterHorizontally),
    ) {
        Image(
            painter = painterResource(id = R.drawable.puppy),
            contentDescription = null,
            contentScale = ContentScale.Crop
        )
    }
    OptionsGrid(choicesList, modifier = Modifier.heightIn(max = screenHeight - 200.dp))
}

@Composable
fun OptionsGrid(choicesList: List<List<String>>, modifier: Modifier = Modifier) {

    Column(
        modifier = modifier
            .border(1.dp, Color.Blue)
            .padding(top = 4.dp, bottom = 4.dp)
            .fillMaxHeight(),
        verticalArrangement = Arrangement.Center
    ) {
        choicesList.forEach { choicesPair ->
            Row(modifier = Modifier.weight(0.5f)) {
                choicesPair.forEach { choice ->
                    Box(
                        modifier = Modifier
                            .padding(4.dp)
                            .background(Color.White)
                            .weight(0.5f)
                    ) {
                        Option(choice = choice)
                    }
                }
            }
        }
    }
}

@Composable
fun Option(choice: String) {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Yellow)
            .border(BorderStroke(1.dp, Color.Red)),

        contentAlignment = Alignment.Center
    ) {
        Text(
            text = choice,
            modifier = Modifier.padding(8.dp),
            textAlign = TextAlign.Center,
        )
    }
}

字符串
我需要为此定制布局吗?我想这里发生的事情是,Column首先测量图像,让它成为它的最大高度,因为屏幕上有空间,然后在测量网格时,它给它剩余的空间,仅此而已。
所以我需要一个从底部开始测量的布局?

rmbxnbpk

rmbxnbpk1#

以下是如何在不使用自定义布局的情况下完成此操作。
1.您需要在OptionsGrid之后计算图像大小。在这种情况下,可以使用Modifier.weight(1f, fill = false):它强制所有没有Modifier.weight的视图被布置在任何加权元素之前。

  1. Modifier.weight将覆盖您的Modifier.heightIn,但我们可以从另一端限制其大小:在OptionsGrid上使用Modifier.layout。使用此修改器,我们可以覆盖应用于视图的约束。
    p.s. Modifier.heightIn(max = screenWidth)是多余的,因为视图无论如何都不会增长超过屏幕大小,除非宽度约束被覆盖,例如,使用滚动视图。
  2. .height(IntrinsicSize.Min)将阻止OptionsGrid超出所需的增长。请注意,is应该放在Modifier.layout之后,因为它会将高度限制设为无穷大。请参阅why modifiers order matters
val choicesList = listOf(
    listOf(
        LoremIpsum(if (flag) 100 else 1).values.first(),
        "Short stuff",
    ),
    listOf(
        "Medium length text",
        "Hi",
    ),
)
Column {
    Box(
        modifier = Modifier
            .weight(1f, fill = false)
            .aspectRatio(1f)
            .border(BorderStroke(1.dp, Color.Green))
            .align(Alignment.CenterHorizontally)
    ) {
        Image(
            painter = painterResource(id = R.drawable.profile),
            contentDescription = null,
            contentScale = ContentScale.Crop
        )
    }
    OptionsGrid(
        choicesList,
        modifier = Modifier
            .layout { measurable, constraints ->
                val placeable = measurable.measure(constraints.copy(
                    // left 200.dp for min image height
                    maxHeight = constraints.maxHeight - 200.dp.roundToPx(),
                    // occupy all height except full image square in case of smaller text
                    minHeight = constraints.maxHeight - constraints.maxWidth,
                ))
                layout(placeable.width, placeable.height) {
                    placeable.place(0, 0)
                }
            }
            .height(IntrinsicSize.Min)
    )
}

字符串
测试结果:


的数据

k10s72fa

k10s72fa2#

我想这里发生的事情是,柱子首先测量图像,让它成为它的最大高度,因为屏幕上有空间,然后当测量网格时,它给它剩余的空间,仅此而已。
这是正确的,它沿着UI树向下,测量列的第一个子级(具有图像的框),并且由于图像没有任何子级,因此它将其大小返回给父级Column。(参见documentation
我很确定这需要一个自定义布局,所以这是我想到的:
首先,为了测试的目的,修改了一些可组合项(调整了一些修饰符,并将Texts替换为TextFields,以便能够看到UI的React)

@ExperimentalComposeUiApi
@Composable
fun theImage() {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .aspectRatio(1f)
            .border(BorderStroke(1.dp, Color.Green))
            .background(Color.Blue)

    ) {
        Image(
            painter = painterResource(id = R.drawable.ic_launcher_foreground),
            contentDescription = null,
            contentScale = ContentScale.Crop,
            modifier = Modifier
                .border(BorderStroke(2.dp, Color.Cyan))
        )
    }
}

@Composable
fun OptionsGrid(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier
            .border(1.dp, Color.Blue)
            .padding(top = 4.dp, bottom = 4.dp)
            .height(IntrinsicSize.Min),
        verticalArrangement = Arrangement.Center
    ) {
        repeat(2){
            Row(modifier = Modifier.weight(0.5f)) {
                repeat(2){
                    Box(
                        modifier = Modifier
                            .padding(4.dp)
                            .background(Color.White)
                            .weight(0.5f)
                            .wrapContentHeight()
                    ) {
                        Option()
                    }
                }
            }
        }
    }
}

@Composable
fun Option() {
    var theText by rememberSaveable { mutableStateOf("a")}

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Yellow)
            .border(BorderStroke(1.dp, Color.Red)),

        contentAlignment = Alignment.Center
    ) {
        OutlinedTextField(value = theText, onValueChange = {theText = it})
    }
}

字符串

现在,自定义布局

由于subcompose需要一个slotId,而您只需要图像和网格的ID,因此您可以创建一个具有两个ID的Enum类。

enum class SlotsEnum {Main, Dependent}


slotID:一个唯一的id,它代表我们正在合成的插槽。如果你有固定的数量或插槽,你可以使用枚举作为插槽ID,或者如果你有一个项目列表,也许列表中的索引或其他一些唯一的键可以工作。为了能够正确匹配两次重新测量之间的内容,您应该提供与上次测量期间使用的对象相同的对象。内容-定义插槽的可组合内容。它可以发出多个布局,在这种情况下,返回的Measurables列表将具有多个元素。
然后,用这个Composable,它接收一个屏幕宽度、高度、一个可选的修饰符和图像,以及网格

@Composable
fun DynamicColumn(
    screenWidth: Int,
    screenHeight: Int,
    modifier: Modifier = Modifier,
    img: @Composable () -> Unit,
    squares: @Composable () -> Unit
)


您可以测量网格的总高度,并使用它来计算图像的高度(当缩放到200 dp以下时,仍然没有管理到适当的UI,但应该不难)。

SubcomposeLayout { constraints ->
    val placeableSquares = subcompose(SlotsEnum.Main, squares).map {
        it.measure(constraints)
    }
    val squaresHeight = placeableSquares.sumOf { it.height }
    val remainingHeight = screenHeight - squaresHeight

    val imgMaxHeight = if (remainingHeight > screenWidth) screenWidth else remainingHeight

    val placeableImage = subcompose(SlotsEnum.Dependent, img).map{
        it.measure(Constraints(200, screenWidth, imgMaxHeight, imgMaxHeight))
    }


然后,将约束应用于图像,最后放置项目。

layout(constraints.maxWidth, constraints.maxHeight) {
    var yPos = 0
    
    placeableImage.forEach{
        it.place(x= screenWidth / 2 - it.width / 2, y= yPos)
        yPos += it.height
    }
    
    placeableSquares.forEach{
        it.place(x=0, y=yPos)
    }
}


最后,调用前面的可组合函数DynamicColumn

@ExperimentalComposeUiApi
@Composable
fun ImageAndSquaresLayout(screenWidth: Int, screenHeight: Int) {

    DynamicColumn(screenWidth = screenWidth, screenHeight = screenHeight,
        img = { theImage() },
        squares = { OptionsGrid() })
}


PS:可能会更新这个明天,如果我能解决最小宽度的问题

相关问题