Как использовать 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
, это тоже может сработать, я еще не пробовал, когда я это сделаю, я отредактирую этот ответ с образцом