Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Info Labels Complete
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.francescsoftware.themeswitcher.ui.theme.ThemeSwitcherTheme
import kotlin.math.max
enum class InfoAlignment {
Top,
Center,
Bottom,
}
@Composable
fun InfoLabels(
modifier: Modifier = Modifier,
content: @Composable InfoLabelsScope.() -> Unit,
) {
Layout(
content = { InfoLabelsScope.content() },
modifier = modifier,
) { measurables, constraints ->
require(measurables.size % 2 == 0) { "InfoLabels requires an even number of children" }
val looseConstraints = constraints.copy(
minWidth = 0,
minHeight = 0,
)
val measurableAlignment = measurables.map { measurable ->
measurable.alignment
}
val placeables = measurables.map { measurable ->
measurable.measure(looseConstraints)
}
val labels = List(placeables.size / 2) { index ->
placeables[2 * index]
}
val labelsAlignment = List(placeables.size / 2) { index ->
measurableAlignment[2 * index]
}
val descriptions = List(placeables.size / 2) { index ->
placeables[2 * index + 1]
}
val descriptionsAlignment = List(placeables.size / 2) { index ->
measurableAlignment[2 * index + 1]
}
val maxLabelWidth = labels.maxByOrNull { it.width }?.width ?: 0
val height = List(labels.size) { index ->
max(labels[index].height, descriptions[index].height)
}.sum()
layout(
constraints.maxWidth,
height.coerceAtMost(constraints.maxHeight)
) {
var yPosition = 0
for (i in labels.indices) {
val label = labels[i]
val labelAlignment = labelsAlignment[i]
val description = descriptions[i]
val descriptionAlignment = descriptionsAlignment[i]
val labelHeight = label.height
val descriptionHeight = description.height
val cellHeight = max(label.height, description.height)
val labelDelta = cellHeight - labelHeight
val descriptionDelta = cellHeight - descriptionHeight
label.place(
x = 0,
y = yPosition + when (labelAlignment) {
InfoAlignment.Top -> 0
InfoAlignment.Center -> labelDelta / 2
InfoAlignment.Bottom -> labelDelta
}
)
description.place(
x = maxLabelWidth,
y = yPosition + when (descriptionAlignment) {
InfoAlignment.Top -> 0
InfoAlignment.Center -> descriptionDelta / 2
InfoAlignment.Bottom -> descriptionDelta
}
)
yPosition += cellHeight
}
}
}
}
interface InfoLabelsScope {
@Stable
fun Modifier.align(alignment: InfoAlignment) = this.then(
InfoLabelsData(
alignment = alignment,
)
)
companion object : InfoLabelsScope
}
private class InfoLabelsData(
val alignment: InfoAlignment,
) : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?) = this@InfoLabelsData
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifier = other as? InfoLabelsData ?: return false
return alignment == otherModifier.alignment
}
override fun hashCode(): Int {
return alignment.hashCode()
}
override fun toString(): String =
"InfoLabelsData(alignment=$alignment)"
}
private val Measurable.infoLabelChildData: InfoLabelsData?
get() = parentData as? InfoLabelsData
private val Measurable.alignment: InfoAlignment
get() = infoLabelChildData?.alignment ?: InfoAlignment.Top
@Preview(showBackground = true)
@Composable
private fun InfoLabelsPreview() {
InfoLabelsTheme {
Surface(modifier = Modifier.width(320.dp)) {
InfoLabels {
Text(
text = "City:",
style = MaterialTheme.typography.body2,
modifier = Modifier.align(InfoAlignment.Center),
)
Text(
text = "Vancouver",
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(start = 8.dp).align(InfoAlignment.Center),
)
Text(
text = "Country:",
style = MaterialTheme.typography.body2,
modifier = Modifier.align(InfoAlignment.Center),
)
Text(
text = "Canada",
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(start = 8.dp).align(InfoAlignment.Center),
)
Text(
text = "Country code:",
style = MaterialTheme.typography.body2,
modifier = Modifier.align(InfoAlignment.Center),
)
Text(
text = "CA",
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(start = 8.dp).align(InfoAlignment.Center),
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment