Skip to content

Instantly share code, notes, and snippets.

@fvilarino
Last active December 21, 2021 16:11
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 fvilarino/b0ebbe35ae45544d8296d8ef1d6a0a19 to your computer and use it in GitHub Desktop.
Save fvilarino/b0ebbe35ae45544d8296d8ef1d6a0a19 to your computer and use it in GitHub Desktop.
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