[ Compose ] Construct Auto Adaptive UI
文章目录
JetPack WindowManager for XML
JetPack WindowManager is an api published in android 34
the library enables developers to query screen info on more complicated modern device
WindowManager is mainly used in foldable or multi-screen devices
api("androidx.window:window:1.3.0")
api("androidx.window:window-java:1.3.0")
api("androidx.compose.material3.adaptive:adaptive-android:1.0.0")
lifecycleScope.launch { registerWindowTracker() }
suspend fun registerWindowTracker() {
val tracker = WindowInfoTracker.getOrCreate(this)
tracker.windowLayoutInfo(this).collect { info ->
val summary = StringBuilder()
val calculator = WindowMetricsCalculator.getOrCreate()
val currentMetrics = calculator.computeCurrentWindowMetrics(this).bounds
val maximumMetrics = calculator.computeMaximumWindowMetrics(this).bounds
summary.append(currentMetrics).appendLine()
summary.append(maximumMetrics).appendLine()
val feature = info.displayFeatures.filterIsInstance<FoldingFeature>().firstOrNull()
feature?.let {
val state = feature.state
val orientation = feature.orientation
val separating = feature.isSeparating
summary.append(state).appendLine()
summary.append(orientation).appendLine()
summary.append(separating).appendLine()
}
println(summary.toString())
updateUI()
}
}
WindowManager with Compose
this is a simple demo, in formal project, you can create different composables, depending on window size
@Composable
fun rememberWindowSize(): Size {
val configuration = LocalConfiguration.current
val context = LocalContext.current
val windowMetrics = remember(configuration) {
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
}
return windowMetrics.bounds.toComposeRect().size
}
@Composable
fun WindowSizeAwareText() {
val windowSize = rememberWindowSize()
Text("${windowSize.toIntSize().width}-${windowSize.toIntSize().height}")
}
WindowSizeClass
library window size classify width and height size into 3 levels
we can create different composables for different level sizes
@Composable
fun WindowSizeClass() {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val widthClass = windowSizeClass.windowWidthSizeClass
when (widthClass) {
COMPACT -> Text("COMPACT")
MEDIUM -> Text("MEDIUM")
EXPANDED -> Text("EXPANDED")
}
}
@Composable
fun Compose09() {
WindowSizeClass()
}
BoxWithConstraints
BoxWithConstraintsScope provides ability to access layout limits
such as maxWidth, minWidth, maxHeight and minHeight
then we can dynamicly calculate layout attributes
this is a photo gallery control, that only show part images, with a badge display remained image counts
private val imagesInternal = listOf(
"https://images.unsplash.com/photo-1511044568932-338cba0ad803",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803"
)
@Composable
private fun responsiveGallery(
images: List<String> = imagesInternal,
modifier: Modifier = Modifier
) {
BoxWithConstraints(modifier.wrapContentSize().background(Color.Red)) {
val itemSpacing = 10.dp
val itemWidth = 50.dp
val visibleCount = maxWidth.div(itemWidth + itemSpacing).toInt().minus(1)
Row(
modifier = Modifier.fillMaxWidth().background(Color.Yellow),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
verticalAlignment = Alignment.CenterVertically
) {
images.take(visibleCount).forEach {
Image(
painter = rememberAsyncImagePainter(it),
contentDescription = null,
modifier = Modifier.width(itemWidth).aspectRatio(1f).background(Color.Green)
)
}
val remainCount = images.size - visibleCount
if (remainCount > 0) {
Badge {
Text("+$remainCount")
}
}
}
}
}
@Preview
@Composable
fun Compose04() {
responsiveGallery(modifier = Modifier.width(250.dp).height(100.dp))
}
Fixed Size LazyRow
this time, we create a LazyRow with minimum item size ensured, by computing with BoxWithConstraints
private val imagesInternal = listOf(
"https://images.unsplash.com/photo-1511044568932-338cba0ad803?t=1",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803?t=2",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803?t=3",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803?t=4",
"https://images.unsplash.com/photo-1511044568932-338cba0ad803?t=5"
)
@Composable
private fun FixedSizeLazyRow(
images: List<String> = imagesInternal,
modifier: Modifier = Modifier
) {
BoxWithConstraints(modifier.background(Color.Red), contentAlignment = Alignment.Center) {
val itemSpacing = 10.dp
var itemWidth = 50.dp
val visibleCount = maxWidth.div(itemWidth + itemSpacing).toInt()
itemWidth = maxWidth.minus(itemSpacing.times(visibleCount - 1)).div(visibleCount)
val items = remember { images }
LazyRow(
modifier = Modifier.fillMaxWidth().wrapContentHeight().background(Color.Yellow),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
verticalAlignment = Alignment.CenterVertically
) {
items(items, key = { it }) {
Image(
painter = rememberAsyncImagePainter(it),
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier.width(itemWidth).aspectRatio(1f).background(Color.Green)
)
}
}
}
}
@Preview
@Composable
fun Compose05() {
FixedSizeLazyRow(modifier = Modifier.width(250.dp).height(100.dp))
}
ConstrainLayout for Compose
typealias ConstrainBlock = ConstrainScope.() -> Unit
val ConstrainScope.left get() = run { absoluteLeft }
val ConstrainScope.right get() = run { absoluteRight }
val ConstrainedLayoutReference.left get() = run { absoluteLeft }
val ConstrainedLayoutReference.right get() = run { absoluteRight }
@Composable
private fun ConstraintLayoutCompose(modifier: Modifier) {
ConstraintLayout(modifier) {
val (e1, e2, e3) = createRefs()
val guideline = createGuidelineFromTop(50.dp)
val constrain1: ConstrainBlock = {
left.linkTo(parent.left)
top.linkTo(parent.top)
}
val constrain2: ConstrainBlock = {
left.linkTo(e1.right)
top.linkTo(guideline)
}
val constrain3: ConstrainBlock = {
top.linkTo(e2.bottom)
left.linkTo(e1.left)
right.linkTo(e2.right)
width = Dimension.fillToConstraints
}
Box(Modifier.constrainAs(e1, constrain1).width(100.dp).height(100.dp).background(Color.Yellow))
Box(Modifier.constrainAs(e2, constrain2).width(100.dp).height(100.dp).background(Color.Green))
Box(Modifier.constrainAs(e3, constrain3).width(100.dp).height(100.dp).background(Color.Cyan))
}
}
@Preview
@Composable
fun Compose06() {
ConstraintLayoutCompose(Modifier.width(250.dp).height(250.dp).background(Color(0.2f, 0.2f, 0.2f, 0.2f)))
}
ConstrainSet
if multiple layout has same elements
we can share single layout, while dynamically apply constrains by ConstraintSet
ConstraintSet only define element names, then bind actual element until runtimes
private val ConstrainScope.left get() = run { absoluteLeft }
private val ConstrainScope.right get() = run { absoluteRight }
private val ConstrainedLayoutReference.left get() = run { absoluteLeft }
private val ConstrainedLayoutReference.right get() = run { absoluteRight }
private fun createConstraintSet(guidelinePosition: Dp) = ConstraintSet {
val (e1, e2, e3) = createRefsFor("e1", "e2", "e3")
val guideline = createGuidelineFromTop(guidelinePosition)
constrain(e1) {
left.linkTo(parent.left)
top.linkTo(parent.top)
}
constrain(e2) {
left.linkTo(e1.right)
top.linkTo(guideline)
}
constrain(e3) {
top.linkTo(e2.bottom)
left.linkTo(e1.left)
right.linkTo(e2.right)
width = Dimension.fillToConstraints
}
}
@Composable
private fun ConstraintSet(modifier: Modifier) {
val constrains = createConstraintSet(50.dp)
ConstraintLayout(constrains, modifier) {
Box(Modifier.layoutId("e1").width(100.dp).height(100.dp).background(Color.Yellow))
Box(Modifier.layoutId("e2").width(100.dp).height(100.dp).background(Color.Green))
Box(Modifier.layoutId("e3").width(100.dp).height(100.dp).background(Color.Cyan))
}
}
@Preview
@Composable
fun Compose07() {
ConstraintSet(Modifier.width(250.dp).height(250.dp).background(Color(0.2f, 0.2f, 0.2f, 0.2f)))
}
更多推荐


所有评论(0)