Home > Enterprise >  Jetpack compose width of row children
Jetpack compose width of row children

Time:08-29

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,
                )
            }
        }
    }
}

Screenshot

CodePudding user response:

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)
                }
            }
        }
    }
}
  • Related