Как использовать Jetpack Compose Canvas с макетом с динамическим размером

я хочу установить androidx.compose.foundation.Canvas размер динамически после измерения измеримых величин и размещения размещаемых объектов с помощью функции макета, но для холста требуется модификатор с определенным размером, как можно добиться этого с помощью макета и холста и эффективным способом.

Чего я хочу добиться, так это, как на изображении, чтобы установить содержимое, а также добавить немного места и нарисовать стрелку в красной области, чтобы создать речевой пузырь, как можно увидеть здесь.

Первый контейнер на изображении является правильным с Modifier.layout и Modifier.drawbackground как

      @Composable
private fun MyComposable(modifier: Modifier = Modifier) {

    Row(
        modifier = Modifier

    ) {
        var totalSize = Size.Zero

        val layoutModifier = modifier
            .layout { measurable, constraints ->

                val placeable = measurable.measure(constraints)
                totalSize = Size(placeable.width + 50f, placeable.height.toFloat())

                println("MyComposable() LAYOUT placeable: $placeable")

                layout(totalSize.width.roundToInt(), placeable.height) {
                    placeable.place(0, 0)
                }

            }
            .drawBehind {
                println("MyComposable() drawBehind size: $size")
                drawRect(Color.Red, size = totalSize, style = Stroke(2f))
                drawRect(Color.Blue, size = size, style = Stroke(2f))
            }


        Column(modifier = layoutModifier) {
            Text("First Text")
            Text("Second Text")
        }

    }
}

И журналы

      I: MyComposable() LAYOUT placeable width: 250, height: 124
I: MyComposable() drawBehind size: Size(250.0, 124.0)

С макетом и холстом

      @Composable
private fun CanvasLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {

    var widthInDp by remember { mutableStateOf(0.dp) }
    var heightInDp by remember { mutableStateOf(0.dp) }

    Layout(content = content, modifier = modifier) { measurables, constraints ->

        val placeables = measurables.map { measurable ->
            measurable.measure(constraints)
        }

        val maxWidth = placeables.maxOf { it.width } + 50
        val height = placeables.sumOf { it.height }

        widthInDp = (maxWidth / density).toDp()
        heightInDp = (height / density).toDp()
        println("🚗 CanvasLayout() LAYOUT maxWidth: $maxWidth, height: $height placeables: ${placeables.size}")

        var yPos = 0
        layout(maxWidth, height) {
            placeables.forEach { placeable: Placeable ->
                placeable.placeRelative(0, yPos)
                yPos += placeable.height
            }
        }
    }

    androidx.compose.foundation.Canvas(
        modifier = Modifier.size(widthInDp, heightInDp),
        onDraw = {
            println("🚙 CanvasLayout() CANVAS size: $size")
            drawRect(Color.Blue, size = size, style = Stroke(2f))
        }
    )
}

Как видно на изображении, нижний прямоугольник рисуется под текстовыми компоновками, а не вокруг них и вызывает layout дважды

      I: 🚗 CanvasLayout() LAYOUT maxWidth: 300, height: 124 placeables: 2
I: 🚙 CanvasLayout() CANVAS size: Size(0.0, 0.0)
I: 🚗 CanvasLayout() LAYOUT maxWidth: 300, height: 124 placeables: 2
I: 🚙 CanvasLayout() CANVAS size: Size(114.0, 47.0)

Как правильно и эффективно использовать Layout и Canvas вместе?

1 ответ

Иногда требуется динамический размер, который требует, чтобы вы изменили размер холста, как в этом вопросе.

СCanvasэтоSpacer

      @Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw))

ничья позади работает нормально.

Но иногда вы создаете свой макет с помощью Canvas таким образом, что Canvas находится в компоновке без статистики в нижнем слое, например, в исходном коде слайдера по умолчанию.

      fun Slider() {
   // States rememberables
  SliderImpl()
}

fun SliderImpl() {
   Canvas()
   // Other Composables like thumb
}

Когда вам нужно передать динамический размер в Canvas, есть несколько вариантов

1- Используя Modifier.onSizeChanged{} в своем макете, вы хотите получить свое измерение, но это не рекомендуется, и это может вызвать бесконечный цикл, который вы устанавливаете размер и читаете снова

2- Использование, которое возвращает min/maxWidth, min/maxHeight, но максимальные ограничения, не всегда соответствует границам вашего макета. Это работает, когда ваш Composable заполняет весь макет, когда используется Modifier.fillMax или у вас есть фиксированный размер.

3- Другой вариант - использоватьSubcomposeLayoutгде вы создаете основной (вы хотите измерить) и зависимый (вы хотите передать измерение) Composables. Основной компонент становится составным и передает измерение зависимому, и вы устанавливаете его.

Эта логика используетсяScaffold,TabRowдля измерения и размещения Composables на основе других. Вы можете проверить эту ссылку о SubcomposeLayout для получения более подробной информации.BoxWithConstraints, иLazyListтакже являются SubcomposeLayouts

4-LookAheadLayout, это тоже может сработать, я еще не пробовал, когда я это сделаю, я отредактирую этот ответ с образцом

Другие вопросы по тегам