I'm working on chart layout and want all my "dots" on the chart have the same specified size. But the layout inspector shows incorrect value:
Here is my chart composable:
#Composable
private fun Chart() {
val data = DoubleArray(100) { it.toDouble() }
val state = LineChartScaffoldState(
LineChartState(
data = data,
gap = 0.dp,//Dp.Unspecified,
scrollState = rememberScrollState()
)
)
LineChartScaffold(
modifier = Modifier.fillMaxSize(),
state = state
) {
Box {
Column(
Modifier
.fillMaxSize()
.padding(start = 0.dp)
) {
LineChart(
modifier = Modifier
.weight(weight = 1f, fill = false)
.horizontalScroll(state.lineChartState.scrollState),
state = it.lineChartState
) {
Dot(
modifier = Modifier.size(10.dp), // BUT I HAVE SPECIFIED 10 DP SIZE
data = data[it]
)
}
}
}
}
}
Here is my line chart scaffold that is using for sharing the state object.
#Composable
fun LineChartScaffold(
modifier: Modifier = Modifier,
state: LineChartScaffoldState,
content: #Composable (state: LineChartScaffoldState) -> Unit
) {
Box(modifier = modifier) {
content(state)
}
}
My line chart layout:
#Composable
fun LineChart(
modifier: Modifier = Modifier,
state: LineChartState,
item: #Composable (index: Int) -> Unit
) {
Layout(
modifier = modifier,
content = {
for (i in state.data.indices) {
item(i)
}
}
) { measurables, constraints ->
val gap = if (state.gap.isUnspecified) {
0
} else {
state.gap.roundToPx()
}
val placeables = measurables.map { it.measure(constraints) }
layout(constraints.maxWidth, constraints.maxHeight) {
placeables.forEachIndexed { i, it ->
val x = (it.width + gap) * i
val y = (constraints.maxHeight - it.height) * 0.01 * i // TODO: Remove debug percentage calculation
Log.d("debug","x=$x, y=$y")
it.place(x.toInt(), y.toInt())
}
}
}
}
And the dot composable:
#Composable
fun Dot(
modifier: Modifier = Modifier,
data: Double,
) {
Image(
modifier = modifier,
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = null,
alignment = Alignment.TopCenter,
)
}
Why I get dots width 9 dp instead specified 10 dp?
Related
What I want
Top fixed item with status bar padding and adaptive radius
Bottom fixed item with navigation bar padding
Adaptive center item, have enough room => Not scrollable, if not => scrollable
Current status
For full demo click this link
Only problem here is when there is no extra space, and we are dragging the scrollable list.
I think it's a bug, because everything is fine except the scrollable column.
window.height - sheetContent.height >= statusBarHeight
Everything is fine.
window.height - sheetContent.height < statusBarHeight
Dragging top fixed item or bottom fixed item, scroll still acting well.
Dragging the scrollable list, sheet pops back to top when the sheetState.offset is approaching statusBarHeight
Test youself
You can test it with these 3 functions, for me, I'm using Pixel 2 Emulator, itemCount at 18,19 could tell the difference.
#Composable
fun CEModalBottomSheet(
sheetState: ModalBottomSheetState,
onCloseDialogClicked: () -> Unit,
title: String = "BottomSheet Title",
toolbarElevation: Dp = 0.dp,
sheetContent: #Composable ColumnScope.() -> Unit,
) {
val density = LocalDensity.current
val sheetOffset = sheetState.offset
val statusBar = WindowInsets.statusBars.asPaddingValues()
val shapeRadius by animateDpAsState(
if (sheetOffset.value < with(density) { statusBar.calculateTopPadding().toPx() }) {
0.dp
} else 12.dp
)
val dynamicStatusBarPadding by remember {
derivedStateOf {
val statusBarHeightPx2 = with(density) { statusBar.calculateTopPadding().toPx() }
val offsetValuePx = sheetOffset.value
if (offsetValuePx >= statusBarHeightPx2) {
0.dp
} else {
with(density) { (statusBarHeightPx2 - offsetValuePx).toDp() }
}
}
}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetShape = RoundedCornerShape(topStart = shapeRadius, topEnd = shapeRadius),
content = {},
sheetContent = {
Column(modifier = Modifier.fillMaxWidth()) {
TopTitleItemForDialog(
title = title,
elevation = toolbarElevation,
topPadding = dynamicStatusBarPadding,
onClick = onCloseDialogClicked
)
sheetContent()
}
})
}
#Composable
fun TopTitleItemForDialog(
title: String,
elevation: Dp = 0.dp,
topPadding: Dp = 0.dp,
onClick: () -> Unit
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color.LightGray,
elevation = elevation
) {
Row(
modifier = Modifier.padding(top = topPadding),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.size(16.dp))
Text(
text = title,
maxLines = 1,
modifier = Modifier.weight(1f)
)
IconButton(onClick = onClick) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.cancel),
tint = Color.Gray,
modifier = Modifier.size(24.dp)
)
}
}
}
}
class SheetPaddingTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.statusBarColor = android.graphics.Color.TRANSPARENT
window.navigationBarColor = android.graphics.Color.TRANSPARENT
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
Box(
Modifier
.fillMaxSize()
.background(Color.Green), contentAlignment = Alignment.Center
) {
var itemCount by remember { mutableStateOf(20) }
val state = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden) { false }
val scope = rememberCoroutineScope()
Text("ζΎη€ΊεΌΉηͺ", modifier = Modifier.clickable {
scope.launch { state.animateTo(ModalBottomSheetValue.Expanded) }
})
CEModalBottomSheet(sheetState = state,
onCloseDialogClicked = {
scope.launch {
state.hide()
}
}, sheetContent = {
Column(
Modifier
.verticalScroll(rememberScrollState())
.weight(1f, fill = false)
.padding(horizontal = 16.dp),
) {
repeat(itemCount) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(30.dp)
.background(Color.Blue.copy(alpha = 1f - it * 0.04f))
)
}
}
CompositionLocalProvider(
LocalContentColor.provides(Color.White)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colors.primary)
.padding(vertical = 12.dp)
.navigationBarsPadding(),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(modifier = Modifier.weight(1f),
onClick = {
itemCount = max(itemCount - 1, 0)
}) {
Icon(Icons.Default.KeyboardArrowLeft, "")
}
Text(
modifier = Modifier.weight(1f), text = "$itemCount",
textAlign = TextAlign.Center
)
IconButton(modifier = Modifier.weight(1f),
onClick = {
itemCount++
}) {
Icon(Icons.Default.KeyboardArrowRight, "")
}
}
}
}
)
}
}
}
}
I have a Compose component with a box and 2 components inside it, but they are never visible both at the same time. I want to adjust size of this box to the first component and stay unchanged when this component will be not visible.
Box(
modifier = modifier.then(Modifier.background(bgColor)),
contentAlignment = Alignment.Center
) {
if (componentVisible1) {
Button(
modifier = Modifier.height(48.dp).widthIn(144.dp),
onClick = onClicked,
enabled = enabled
) {
Text(
text = "text1",
)
}
}
if (component2Visible) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp).background(buttonColor, CircleShape).padding(2.dp),
strokeWidth = 2.dp
)
}
}
Now, width of the box reduces when the component1 is not visible.
You can use SubcomposeLayout to pass a Composable's dimension if another one depends on it without recomposition or in your case even if it's not even in composition.
#Composable
internal fun DimensionSubcomposeLayout(
modifier: Modifier = Modifier,
mainContent: #Composable () -> Unit,
dependentContent: #Composable (DpSize) -> Unit
) {
val density = LocalDensity.current
SubcomposeLayout(
modifier = modifier
) { constraints: Constraints ->
// Subcompose(compose only a section) main content and get Placeable
val mainPlaceable: Placeable = subcompose(SlotsEnum.Main, mainContent)
.map {
it.measure(constraints.copy(minWidth = 0, minHeight = 0))
}.first()
val dependentPlaceable: Placeable =
subcompose(SlotsEnum.Dependent) {
dependentContent(
DpSize(
density.run { mainPlaceable.width.toDp() },
density.run { mainPlaceable.height.toDp() }
)
)
}
.map { measurable: Measurable ->
measurable.measure(constraints)
}.first()
layout(mainPlaceable.width, mainPlaceable.height) {
dependentPlaceable.placeRelative(0, 0)
}
}
}
/**
* Enum class for SubcomposeLayouts with main and dependent Composables
*/
enum class SlotsEnum { Main, Dependent }
And you can use it to set dimensions of Box based on measuring or subcomposing your Button.
#Composable
private fun DimensionSample() {
var componentVisible1 by remember { mutableStateOf(false) }
var component2Visible by remember { mutableStateOf(true) }
Column {
Button(onClick = {
componentVisible1 = !componentVisible1
component2Visible = !component2Visible
}) {
Text(text = "Toggle")
}
val button = #Composable {
Button(
modifier = Modifier
.height(48.dp)
.widthIn(144.dp),
onClick = {}
) {
Text(
text = "text1",
)
}
}
val circularProgressIndicator = #Composable {
CircularProgressIndicator(
modifier = Modifier
.size(24.dp)
.background(Color.Yellow, CircleShape)
.padding(2.dp),
strokeWidth = 2.dp
)
}
DimensionSubcomposeLayout(mainContent = {
button()
}) {
Box(
modifier = Modifier
.size(it)
.then(Modifier.background(Color.Red)),
contentAlignment = Alignment.Center
) {
if (componentVisible1) {
button()
}
if (component2Visible) {
circularProgressIndicator()
}
}
}
}
}
In Jetpack Compose, How to find the width of each child of a Row?
I tried using onGloballyPositioned like below, but the size is wrong.
Also, not sure if this is the optimal one.
I need the width of each child and further processing will be done based on that, the below is a simplified code for example.
#Composable
fun MyTabSample() {
MyCustomRow(
items = listOf("Option 1 with long", "Option 2 with", "Option 3"),
)
}
#Composable
fun MyCustomRow(
modifier: Modifier = Modifier,
items: List<String>,
) {
val childrenWidths = remember {
mutableStateListOf<Int>()
}
Box(
modifier = modifier
.background(Color.LightGray)
.height(IntrinsicSize.Min),
) {
// To add more box here
Box(
modifier = Modifier
.widthIn(
min = 64.dp,
)
.fillMaxHeight()
.width(childrenWidths.getOrNull(0)?.dp ?: 64.dp)
.background(
color = DarkGray,
),
)
Row(
horizontalArrangement = Arrangement.Center,
) {
items.mapIndexed { index, text ->
Text(
modifier = Modifier
.widthIn(min = 64.dp)
.padding(
vertical = 8.dp,
horizontal = 12.dp,
)
.onGloballyPositioned {
childrenWidths.add(index, it.size.width)
},
text = text,
color = Black,
textAlign = TextAlign.Center,
)
}
}
}
}
The size you get from Modifier.onSizeChanged or Modifier.onGloballyPositioned is in px with unit Int, not in dp. You should convert them to dp with
val density = LocalDensity.current
density.run{it.size.width.toDp()}
Full code
#Composable
fun MyCustomRow(
modifier: Modifier = Modifier,
items: List<String>,
) {
val childrenWidths = remember {
mutableStateListOf<Dp>()
}
Box(
modifier = modifier
.background(Color.LightGray)
.height(IntrinsicSize.Min),
) {
val density = LocalDensity.current
// To add more box here
Box(
modifier = Modifier
.widthIn(
min = 64.dp,
)
.fillMaxHeight()
.width(childrenWidths.getOrNull(0) ?: 64.dp)
.background(
color = DarkGray,
),
)
Row(
horizontalArrangement = Arrangement.Center,
) {
items.mapIndexed { index, text ->
Text(
modifier = Modifier
.onGloballyPositioned {
childrenWidths.add(index, density.run { it.size.width.toDp() })
}
.widthIn(min = 64.dp)
.padding(
vertical = 8.dp,
horizontal = 12.dp,
),
text = text,
color = Black,
textAlign = TextAlign.Center,
)
}
}
}
}
However this is not the optimal way to get size since it requires at least one more recomposition. Optimal way for getting size is using SubcomposeLayout as in this answer
How to get exact size without recomposition?
TabRow also uses SubcomposeLayout for getting indicator and divider widths
#Composable
fun TabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
containerColor: Color = TabRowDefaults.containerColor,
contentColor: Color = TabRowDefaults.contentColor,
indicator: #Composable (tabPositions: List<TabPosition>) -> Unit = #Composable { tabPositions ->
TabRowDefaults.Indicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
)
},
divider: #Composable () -> Unit = #Composable {
Divider()
},
tabs: #Composable () -> Unit
) {
Surface(
modifier = modifier.selectableGroup(),
color = containerColor,
contentColor = contentColor
) {
SubcomposeLayout(Modifier.fillMaxWidth()) { constraints ->
val tabRowWidth = constraints.maxWidth
val tabMeasurables = subcompose(TabSlots.Tabs, tabs)
val tabCount = tabMeasurables.size
val tabWidth = (tabRowWidth / tabCount)
val tabRowHeight = tabMeasurables.fold(initial = 0) { max, curr ->
maxOf(curr.maxIntrinsicHeight(tabWidth), max)
}
val tabPlaceables = tabMeasurables.map {
it.measure(
constraints.copy(
minWidth = tabWidth,
maxWidth = tabWidth,
minHeight = tabRowHeight
)
)
}
val tabPositions = List(tabCount) { index ->
TabPosition(tabWidth.toDp() * index, tabWidth.toDp())
}
layout(tabRowWidth, tabRowHeight) {
tabPlaceables.forEachIndexed { index, placeable ->
placeable.placeRelative(index * tabWidth, 0)
}
subcompose(TabSlots.Divider, divider).forEach {
val placeable = it.measure(constraints.copy(minHeight = 0))
placeable.placeRelative(0, tabRowHeight - placeable.height)
}
subcompose(TabSlots.Indicator) {
indicator(tabPositions)
}.forEach {
it.measure(Constraints.fixed(tabRowWidth, tabRowHeight)).placeRelative(0, 0)
}
}
}
}
}
There is a regular list with some data and LazyColumn for showing it.
LazyColumn {
items (list) { item ->
ListItem(item)
}
}
Simplified ListItem looks like:
#Composable
fun ListItem(item: SomeItem) {
Row(
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min)
) {
//Some widgets
}
}
I need to set the footer's item (last in list) height to fill screen if there is not enough items for that. It's easy to find last item and provide some flag to ListItem(item: SomeItem, isLast: Boolean), but I don't know how to set the item's height to achieve my goal. Did anyone faced this problem?
It's almost the same question as was asked here about RecyclerView. And the image from that question illustatrates it.
If your items are all same height and you have have info of how tall they are via static height or using Modifier.onSizeChanged{} you can do this by getting height of the LazyColumn using BoxWithConstraints maxHeight or rememberLazyListState().layoutInfo.viewportSize
#Composable
private fun ListComposable() {
val myItems = mutableListOf<String>()
repeat(4) {
myItems.add("Item $it")
}
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.border(2.dp, Color.Cyan)
) {
itemsIndexed(myItems) { index: Int, item: String ->
if (index == myItems.size - 1) {
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.height((maxHeight - 50.dp * (myItems.size - 1)).coerceAtLeast(50.dp))
.background(Color.Green)
)
} else {
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color.Red)
)
}
}
}
}
}
If you don't know total height until last item you will need to use SubcomposeLayout with list that doesn't contain last item and pass LazyColumn height- (items.size-1) total height as your last item's height.
#Composable
fun DimensionMeasureSubcomposeLayout(
modifier: Modifier = Modifier,
mainContent: #Composable () -> Unit,
dependentContent: #Composable (IntSize, Constraints) -> Unit
) {
SubcomposeLayout(modifier = modifier) { constraints ->
// Subcompose(compose only a section) main content and get Placeable
val mainPlaceables: List<Placeable> = subcompose(SlotsEnum.Main, mainContent)
.map {
it.measure(constraints)
}
// Get max width and height of main component
var maxWidth = 0
var maxHeight = 0
mainPlaceables.forEach { placeable: Placeable ->
maxWidth += placeable.width
maxHeight = placeable.height
}
val maxSize = IntSize(maxWidth, maxHeight)
val dependentPlaceables = subcompose(SlotsEnum.Dependent) {
dependentContent(maxSize, constraints)
}.map {
it.measure(constraints)
}
layout(constraints.maxWidth, constraints.maxHeight) {
dependentPlaceables.forEach { placeable: Placeable ->
placeable.placeRelative(0, 0)
}
}
}
}
enum class SlotsEnum { Main, Dependent }
And use it as
#Composable
private fun SubcomposeExample() {
val myItems = mutableListOf<String>()
repeat(15) {
myItems.add("Item $it")
}
val subList = myItems.subList(0, myItems.size - 1)
val lasItem = myItems.last()
val density = LocalDensity.current
DimensionMeasureSubcomposeLayout(
modifier = Modifier,
mainContent = {
LazyColumn(
modifier = Modifier.fillMaxWidth()
) {
items(subList) { item: String ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color.Red)
)
}
}
},
dependentContent = { intSize, constraints ->
val lastItemHeight = with(density) {
(constraints.maxHeight - intSize.height).toDp().coerceAtLeast(50.dp)
}
LazyColumn(modifier = Modifier.fillMaxWidth()) {
if (myItems.size > 1) {
items(subList) { item: String ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(Color.Red)
)
}
}
item {
Text(
text = lasItem,
modifier = Modifier
.fillMaxWidth()
.height(lastItemHeight)
.background(Color.Green)
)
}
}
}
)
}
How can i set width of a group of composables, siblings layout from top to bottom, to width of longest one?
What i try to build is exactly same thing as in the images above. For simplicity let's say quote the component at the top and message box which contains message and another container that stores date and message status.
The longest one of quote and message box must be set as parent width and other one must be set to same width as longest one which requires a remeasuring for short one i assume.
Also if message box gets to resized there needs to be an internal parameter that passes this width to set position of container that stores date and status. As can be seen clearly with bounds message text is moved to start while status to end when quote is longer than message box. When message has more than one line message box width and height are set with a calculation as telegram or whatsapp does.
Built this initially with Layout as
#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)
}
}
}
Where i measure message box using width of quote constraint it works but only when quote is longer.
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
)
}
)
As it's must be remeasured i think solution for this question should be done with SubComposeLayout but couldn't figure out how to use it for this setup?
#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) }
}
}
}
Why cannot remeasure same component second time with same id? When i try to call subCompose with SlotsEnum.Dependent it throws an exception
subcompose(SlotsEnum.NEW) {
println("π SubcomposeLayout-> layout()->subcompose() maxWidth: $maxWidth")
dependentContent(maxWidth)
}
Still not remeasuring correctly after calling it? How can setting sibling can be solved with SubcomposeLayout?
I made a sample based on the sample provided by official documents and #chuckj's answer here.
Orange and pink containers are Columns, which direct children of DynamicWidthLayout, that uses SubcomposeLayout to remeasure.
#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 }
Usage
#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
)
}
}
)
}
}
And full source code is here.