アプリ開発備忘録

PlayStationMobile、Android、UWPの開発備忘録

【Jetpack Compose】等幅・幅が足りない場合はスクロールさせるタブを作る

作るもの

収まる場合は等幅

収まらない場合は横スクロールさせる。なおかつ等幅

使用するもの

Jetpack Composeは1度しかmeasureを呼ぶ事ができない制約がありますが、レイアウトにとある高さを与えた場合、無限の横幅があると仮定してレイアウトが変わらない最小の横幅を計測してくれる maxIntrinsicWidth を使います。
Intrinsic measurements in Compose layouts  |  Jetpack Compose  |  Android Developers

コード

@Composable
fun TabLayout(
    modifier: Modifier,
    size: Int,
    content: @Composable (index: Int) -> Unit
) {
    var enableScroll by rememberSaveable {
        mutableStateOf(false)
    }
    BoxWithConstraints(
        modifier = modifier
    ) {
        val containerWidthPx = with(LocalDensity.current) { maxWidth.toPx() }

        Layout(
            modifier = Modifier
                .fillMaxSize()
                .horizontalScroll(state = rememberScrollState(), enabled = enableScroll),
            content = {
                (0 until size).forEach { index ->
                    content(index)
                }
            }) layout@{ measurables, constraints ->
            // 最大の横幅のアイテムを計測する
            val contentMaxOfMinIntrinsicWidth = measurables.map { it.maxIntrinsicWidth(constraints.maxHeight) }
                .maxOfOrNull { it } ?: return@layout layout(0, 0) {}

            val contentWidth: Int
            if (contentMaxOfMinIntrinsicWidth * measurables.size > containerWidthPx) {
                enableScroll = true
                contentWidth = contentMaxOfMinIntrinsicWidth
            } else {
                enableScroll = false
                contentWidth = (containerWidthPx / measurables.size).toInt()
            }

            val placeables = measurables.map {
                it.measure(Constraints.fixed(width = contentWidth, height = constraints.maxHeight))
            }
            layout(contentWidth * measurables.size, constraints.maxHeight) {
                placeables.forEachIndexed { index, placeable ->
                    placeable.place(x = contentWidth * index, y = 0)
                }
            }
        }
    }
}

プレビュー

@Composable
@Preview(showBackground = true)
private fun ShortPreview() {
    val items = remember {
        listOf(
            "000000000000",
            "1",
        )
    }
    PreviewTab(items = items)
}

@Composable
@Preview(showBackground = true)
private fun LongPreview() {
    val items = remember {
        listOf(
            "000000000000",
            "1",
            "2",
            "3",
            "400000",
        )
    }
    PreviewTab(items = items)
}

@Composable
private fun PreviewTab(
    items: List<String>
) {
    TabLayout(
        modifier = Modifier
            .fillMaxWidth()
            .height(80.dp),
        size = items.size,
        content = { index ->
            val item = items[index]
            Box(
                modifier = Modifier
                    .fillMaxSize(),
                contentAlignment = Alignment.Center,
            ) {
                Text(text = item)
            }
        }
    )
}