其他
使用 Jetpack Compose 为 JetLagged 构建响应式仪表盘布局
作者 / 开发者关系工程师 Rebecca Franks
在 2023 年 Google I/O 大会上发布了 JetLagged 示例之后,我们决定添加更多示例。具体来说,我们希望展示如何使用 Compose 创建一个美观的仪表盘式布局。本文将介绍我们如何实现这一目标。
△ Jetlagged 中的响应式设计,各个项目的位置会自动调整
借助 FlowRow 和 FlowColumn
构建能够响应不同屏幕尺寸的布局
FlowRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
maxItemsInEachRow = 3
) {
Box(modifier = Modifier.widthIn(max = 400.dp))
Box(modifier = Modifier.width(200.dp))
Box(modifier = Modifier.size(200.dp))
// etc
}
使用 WindowSizeClasses
区分不同设备
FlowRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
maxItemsInEachRow = 3
) {
JetLaggedSleepGraphCard(uiState.value.sleepGraphData)
if (windowSizeClass == WindowWidthSizeClass.COMPACT) {
AverageTimeInBedCard()
AverageTimeAsleepCard()
} else {
FlowColumn {
AverageTimeInBedCard()
AverageTimeAsleepCard()
}
}
if (windowSizeClass == WindowWidthSizeClass.COMPACT) {
WellnessCard(uiState.value.wellnessData)
HeartRateCard(uiState.value.heartRateData)
} else {
FlowColumn {
WellnessCard(uiState.value.wellnessData)
HeartRateCard(uiState.value.heartRateData)
}
}
}
根据上述逻辑,界面将在不同尺寸的设备上以如下方式呈现:
使用 movableContentOf
以在屏幕尺寸变化时
保持部分界面状态
@Composable
fun Tile1() {
val repeatingAnimation = rememberInfiniteTransition()
val float = repeatingAnimation.animateFloat(
initialValue = 0f,
targetValue = 100f,
animationSpec = infiniteRepeatable(repeatMode = RepeatMode.Reverse,
animation = tween(5000))
)
Box(modifier = Modifier
.size(100.dp)
.background(purple, RoundedCornerShape(8.dp))){
Text("Tile 1 ${float.value.roundToInt()}",
modifier = Modifier.align(Alignment.Center))
}
}
@Composable
fun WithoutMovableContentDemo() {
val mode = remember {
mutableStateOf(Mode.Portrait)
}
if (mode.value == Mode.Landscape) {
Row {
Tile1()
Tile2()
}
} else {
Column {
Tile1()
Tile2()
}
}
}
val tiles = remember {
movableContentOf {
Tile1()
Tile2()
}
}
@Composable
fun MovableContentDemo() {
val mode = remember {
mutableStateOf(Mode.Portrait)
}
val tiles = remember {
movableContentOf {
Tile1()
Tile2()
}
}
Box(modifier = Modifier.fillMaxSize()) {
if (mode.value == Mode.Landscape) {
Row {
tiles()
}
} else {
Column {
tiles()
}
}
Button(onClick = {
if (mode.value == Mode.Portrait) {
mode.value = Mode.Landscape
} else {
mode.value = Mode.Portrait
}
}, modifier = Modifier.align(Alignment.BottomCenter)) {
Text("Change layout")
}
}
}
这样系统就会记住由这些可组合项生成的节点,并保留这些可组合项当前的内部状态。
Language
val timeSleepSummaryCards = remember {
movableContentOf {
AverageTimeInBedCard()
AverageTimeAsleepCard()
}
}
LookaheadScope {
FlowRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
maxItemsInEachRow = 3
) {
//..
if (windowSizeClass == WindowWidthSizeClass.Compact) {
timeSleepSummaryCards()
} else {
FlowColumn {
timeSleepSummaryCards()
}
}
//
}
}
使用 Modifier.animateBounds()
在不同窗口大小之间
实现流畅的动画效果
val boundsTransform = { _ : Rect, _: Rect ->
spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium,
visibilityThreshold = Rect.VisibilityThreshold
)
}
LookaheadScope {
val animateBoundsModifier = Modifier.animateBounds(
lookaheadScope = this@LookaheadScope,
boundsTransform = boundsTransform)
val timeSleepSummaryCards = remember {
movableContentOf {
AverageTimeInBedCard(animateBoundsModifier)
AverageTimeAsleepCard(animateBoundsModifier)
}
}
FlowRow(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(insets),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
maxItemsInEachRow = 3
) {
JetLaggedSleepGraphCard(uiState.value.sleepGraphData, animateBoundsModifier.widthIn(max = 600.dp))
if (windowSizeClass == WindowWidthSizeClass.Compact) {
timeSleepSummaryCards()
} else {
FlowColumn {
timeSleepSummaryCards()
}
}
FlowColumn {
WellnessCard(
wellnessData = uiState.value.wellnessData,
modifier = animateBoundsModifier
.widthIn(max = 400.dp)
.heightIn(min = 200.dp)
)
HeartRateCard(
modifier = animateBoundsModifier
.widthIn(max = 400.dp, min = 200.dp),
uiState.value.heartRateData
)
}
}
}
总结
点击图片关注精彩活动