Skip to content

Instantly share code, notes, and snippets.

@wm3
Last active October 15, 2022 14:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wm3/4048a41fbb35b9daded373f2ff393788 to your computer and use it in GitHub Desktop.
Save wm3/4048a41fbb35b9daded373f2ff393788 to your computer and use it in GitHub Desktop.
Jetpack Compose のレイアウトまとめ2: alignment, weight, arrangement
import android.graphics.Paint
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.*
import kotlin.math.abs
import kotlin.math.roundToInt
// ----------------------------------------------------------------
// alignment (box)
// ----------------------------------------------------------------
@Composable
private fun Sample1(contentAlignment: Alignment) = SampleContainer(Modifier.size(120.dp)) {
ProvideTextStyle(TextStyle(fontSize = 24.sp)) {
// Box への引数で設定
Box(contentAlignment = contentAlignment) { Text("🍄") }
// Box の子要素への Modifier.align() で設定
Box { Text("🍄", Modifier.align(contentAlignment)) }
// Modifier.wrapContentSize() で設定
Text("🍄", Modifier.wrapContentSize(contentAlignment))
}
}
@Preview @Composable private fun Sample1TopStart() = Sample1(Alignment.TopStart)
@Preview @Composable private fun Sample1TopCenter() = Sample1(Alignment.TopCenter)
@Preview @Composable private fun Sample1TopEnd() = Sample1(Alignment.TopEnd)
@Preview @Composable private fun Sample1CenterStart() = Sample1(Alignment.CenterStart)
@Preview @Composable private fun Sample1CenterCenter() = Sample1(Alignment.Center)
@Preview @Composable private fun Sample1CenterEnd() = Sample1(Alignment.CenterEnd)
@Preview @Composable private fun Sample1BottomStart() = Sample1(Alignment.BottomStart)
@Preview @Composable private fun Sample1BottomCenter() = Sample1(Alignment.BottomCenter)
@Preview @Composable private fun Sample1BottomEnd() = Sample1(Alignment.BottomEnd)
@Preview
@Composable
private fun Sample1VerticalTextAlignment() = SampleContainer(Modifier.size(120.dp)) {
Text(
"Keep it simple stupid",
Modifier.wrapContentHeight().background(Color.Yellow),
textAlign = TextAlign.Center
)
}
// ----------------------------------------------------------------
// alignment (column/row)
// ----------------------------------------------------------------
@Composable
private fun Sample2H(alignment: Alignment.Horizontal) = ColumnAlignContainer {
// 一括設定
Column(horizontalAlignment = alignment) {
Text("Android")
Text("❤️")
Text("Jetpack Compose")
}
}
@Preview @Composable private fun Sample2HStart() = Sample2H(Alignment.Start)
@Preview @Composable private fun Sample2HCenter() = Sample2H(Alignment.CenterHorizontally)
@Preview @Composable private fun Sample2HEnd() = Sample2H(Alignment.End)
@Composable
private fun Sample2ModifierH(alignment: Alignment.Horizontal) = ColumnAlignContainer {
// 個別設定
Column {
Text("Android")
Text("❤️", Modifier.align(alignment))
Text("Jetpack Compose")
}
}
@Preview @Composable private fun Sample2HModifierStart() = Sample2ModifierH(Alignment.Start)
@Preview @Composable private fun Sample2HModifierCenter() = Sample2ModifierH(Alignment.CenterHorizontally)
@Preview @Composable private fun Sample2HModifierEnd() = Sample2ModifierH(Alignment.End)
@Composable
private fun ColumnAlignContainer(content: @Composable () -> Unit) {
SampleContainer(Modifier.width(120.dp)) {
content()
}
}
@Composable
private fun Sample2V(alignment: Alignment.Vertical) = RowAlignContainer {
Row(verticalAlignment = alignment) {
Text("Android")
Text("❤️")
Text("Jetpack\nCompose")
}
}
@Preview @Composable private fun Sample2VTop() = Sample2V(Alignment.Top)
@Preview @Composable private fun Sample2VCenter() = Sample2V(Alignment.CenterVertically)
@Preview @Composable private fun Sample2VBottom() = Sample2V(Alignment.Bottom)
@Composable
private fun Sample2ModifierV(alignment: Alignment.Vertical) {
RowAlignContainer {
Row {
Text("Android")
Text("❤️", Modifier.align(alignment))
Text("Jetpack\nCompose")
}
}
}
@Preview @Composable private fun Sample2VModifierTop() = Sample2ModifierV(Alignment.Top)
@Preview @Composable private fun Sample2VModifierCenter() = Sample2ModifierV(Alignment.CenterVertically)
@Preview @Composable private fun Sample2VModifierBottom() = Sample2ModifierV(Alignment.Bottom)
@Composable
private fun RowAlignContainer(content: @Composable () -> Unit) {
SampleContainer(Modifier.height(80.dp)) {
content()
}
}
// ----------------------------------------------------------------
// weight
// ----------------------------------------------------------------
@Preview
@Composable
private fun Sample3Weight() {
val width by rememberArrangementAnimationState(160f, 360f)
ProvideTextStyle(TextStyle(fontSize = 9.sp)) {
Column(
Modifier.background(Color.White).padding(16.dp).width(360.dp),
Arrangement.spacedBy(16.dp),
Alignment.CenterHorizontally,
) {
Row(Modifier.width(width.dp)) {
Box(Modifier.width(60.dp).height(40.dp)) { BoxInspector(Color.Red, "width(60.dp)") }
Box(Modifier.weight(1f).height(40.dp)) { BoxInspector(Color.Blue, "weight(1f)") }
Box(Modifier.width(60.dp).height(40.dp)) { BoxInspector(Color.Red, "width(60.dp)") }
}
Row(Modifier.width(width.dp)) {
Box(Modifier.weight(1f).height(40.dp)) { BoxInspector(Color.Blue, "weight(1f)") }
Box(Modifier.weight(2f).height(40.dp)) { BoxInspector(Color.Blue, "weight(2f)") }
Box(Modifier.weight(1f).height(40.dp)) { BoxInspector(Color.Blue, "weight(1f)") }
}
}
}
}
@Composable
private fun BoxScope.BoxInspector(color: Color, text: String) {
Box(Modifier.matchParentSize().inspect(color), Alignment.Center) {
Text(text, color = color)
}
}
// ----------------------------------------------------------------
// arrangement
// ----------------------------------------------------------------
@Composable
fun RedBox(modifier: Modifier = Modifier) {
Box(
modifier
.size(40.dp)
.background(Color.Red.copy(alpha = 0.25f))
.wrapContentSize(Alignment.Center, true)
.size(41.dp)
.border(1.dp, Color.Red)
)
}
@Preview(widthDp = 640)
@Composable
private fun Sample4Arrangement() {
val params = listOf(
Arrangement.Start to "Arrangement.Start (デフォルト)",
Arrangement.Center to "Arrangement.Center",
Arrangement.End to "Arrangement.End",
Arrangement.spacedBy(24.dp) to "Arrangement.spacedBy(24.dp)",
Arrangement.spacedBy(24.dp, Alignment.End) to "Arrangement.spacedBy(24.dp, Alignment.End)",
Arrangement.SpaceBetween to "Arrangement.SpaceBetween",
Arrangement.SpaceEvenly to "Arrangement.SpaceEvenly",
Arrangement.SpaceAround to "Arrangement.SpaceAround"
)
Column(
Modifier.background(Color.White).padding(16.dp).fillMaxWidth(),
Arrangement.spacedBy(16.dp)
) {
val fullWidth = 300f
val width by rememberArrangementAnimationState(168f, fullWidth)
val inspectorWidth = (fullWidth + 2).dp
params.forEach {
Row(Modifier.fillMaxWidth(), Arrangement.SpaceBetween, Alignment.CenterVertically) {
Text(it.second)
RowSpaceInspector(Modifier.width(inspectorWidth)) {
Row(Modifier.width(width.dp), it.first) {
repeat(3) { RedBox(Modifier.inspectSpace(it)) }
}
}
}
}
}
}
private class RowInspectScope(private val ranges: MutableMap<Any, IntRange>) {
fun Modifier.inspectSpace(key: Any): Modifier = onPlaced {
val bounds = it.boundsInParent()
ranges[key] = bounds.left.toInt()..bounds.right.toInt()
}
}
@Composable
private fun RowSpaceInspector(
modifier: Modifier = Modifier,
content: @Composable RowInspectScope.() -> Unit
) {
val color = Color.Blue
val state = remember { mutableStateMapOf<Any, IntRange>() }
val density = LocalDensity.current.density
val lineHeight = 9f * density
Box(
modifier
.wrapContentWidth(Alignment.Start)
.border(1.dp, Color.Black)
.padding(1.dp)
.layout { measurable, constraints ->
state.clear()
state["First"] = 0..0
val place = measurable.measure(constraints)
layout(place.width, place.height) { place.placeRelative(IntOffset.Zero) }.also {
state["Last"] = place.width..place.width
}
}
.drawWithContent {
drawContent()
val spaces = state.values
.sortedWith { first, second ->
when (val diff = first.first - second.first) {
0 -> first.last - second.last
else -> diff
}
}
.zipWithNext { prev, next -> prev.last..next.first }
.filter { abs(it.last - it.first).toFloat() >= density }
spaces.forEach { range ->
val width = (range.last - range.first) / density
drawText(
"${width.roundToInt()}",
Offset(range.first + lineHeight / 8, lineHeight),
color,
lineHeight,
)
}
}
) {
content(RowInspectScope(state))
}
}
@Preview
@Composable
private fun Sample4Spacer() = SampleContainer {
Box(Modifier.padding(1.dp)) {
Row(Modifier.fillMaxWidth()) {
Box(Modifier.size(40.dp).border(1.dp, Color.Red))
Spacer(Modifier.weight(1f))
Box(Modifier.size(40.dp).border(1.dp, Color.Red))
}
}
}
// ----------------------------------------------------------------
// utilities
// ----------------------------------------------------------------
@Composable
private fun rememberArrangementAnimationState(
initialValue: Float,
targetValue: Float
) = rememberInfiniteTransition().animateFloat(
initialValue, targetValue, infiniteRepeatable(
keyframes {
durationMillis = 4000
initialValue at 1000 with CubicBezierEasing(0.6f, 0.0f, 0.4f, 1.0f)
targetValue at 2000
targetValue at 3000 with CubicBezierEasing(0.6f, 0.0f, 0.4f, 1.0f)
initialValue at 4000
},
)
)
private fun Modifier.inspect(color: Color): Modifier = composed {
var state by remember { mutableStateOf<String?>(null) }
val density = LocalDensity.current.density
val lineHeight = 9.dp.value * density
this
.onPlaced {
val size = it.size.toSize() / density
state = "${size.width.roundToInt()}x${size.height.roundToInt()}"
}
.border(1.dp, color)
.drawWithContent {
drawContent()
drawText(
state ?: return@drawWithContent,
Offset(lineHeight / 4, size.height - lineHeight / 4),
color,
lineHeight
)
}
}
private fun DrawScope.drawText(text: String, offset: Offset, color: Color, lineHeight: Float) {
val paint = Paint().also {
it.textSize = lineHeight
it.color = color.toArgb()
}
drawIntoCanvas {
it.nativeCanvas.drawText(text, offset.x, offset.y, paint)
}
}
@Composable
private fun SampleContainer(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit
) {
Box(
modifier
.background(Color.White)
.border(1.dp, Color.Black),
propagateMinConstraints = true,
content = content,
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment