一、概念
基于可用空间而非设备类型来设计自适应布局,实现设备无关性和动态适配性,避免硬编码,以不同形态布局更好的展示内容。
二、区分可用空间 WindowSizeClasses
传统根据屏幕大小和方向做适配的方式已不再适用,APP的显示方式还可能是:手机平板的分屏、ChormeOS的窗口可调整大小、折叠设备的屏幕形态切换等显示方式,因此需要根据可用显示空间做适配。
窗口尺寸类(Window size classes)将可用的显示区域分别在宽高上划分为紧凑型(Compact)、中等型(Medium)和扩展型(Expanded),由于垂直滚动的普遍性,可用宽度通常比可用高度更为重要。
最新版本
implementation("androidx.compose.material3.adaptive:adaptive:1.1.0-alpha02")
@Composable
fun Demo(
windowWidthSizeClass: WindowWidthSizeClass = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass
) {
//【方式一】分别加载不同界面
when(windowWidthSizeClass) {
WindowWidthSizeClass.COMPACT -> { CompactScreen() }
WindowWidthSizeClass.MEDIUM -> { MediumScreen() }
WindowWidthSizeClass.EXPANDED -> { ExpandedScreen() }
}
//【方式二】直接将状态传递
AdapterScreen(windowWidthSizeClass)
}
三、自适应布局
官方文章
3.1 自适应导航 NavigationSuiteScaffold
导航在小窗口上(例如手机)一般位于界面底部,在展开窗口上(例如平板)一般位于界面侧边。
NavigationSuiteScaffold 会根据 WindowSizeClass 显示适当的导航方式,包括在运行时窗口大小发生变化时动态更改界面。当宽或高较小、或处于桌上模式会显示在底部,其他情况显示在侧边。
implementation("androidx.compose.material3:material3-adaptive-navigation-suite")
3.2 列表详情布局
3.3 辅助窗格布局
四、响应式排版
参考文章
3.1 流式布局 FlowRow
流式布局能实时响应屏幕可用空间进行重排界面。
@Composable
fun AdapterScreen(
windowWidthSizeClass: WindowWidthSizeClass = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass
) {
FlowRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalArrangement = Arrangement.Center,
maxItemsInEachRow = 3 可用空间再大一行也只有3个子元素
) {
//大型组件作为第一个直接放
Big()
//对紧凑型来说,小型组件横着放,放不下会自动换行(效果图横着显示2个)
//对中等型和展开型来说,用容器 FlowColumn 包裹小型组件使之成为一个整体
//充分利用纵向空间,在容器中竖着放,放不下会自动换列(效果图竖着显示两个)
if(windowWidthSizeClass == WindowWidthSizeClass.COMPACT) {
Small()
Small()
} else {
FlowColumn {
Small()
Small()
}
}
//中型组件同理小型组件
if(windowWidthSizeClass == WindowWidthSizeClass.COMPACT) {
Medium()
Medium()
} else {
FlowColumn {
Medium()
Medium()
}
}
}
}
3.2 保持子元素内部动画状态 movableContentOf()
当流式布局进行重排的时候,子元素的动画会被打断(重新启动)。借助可移动内容 (Movable content)可以在子元素被移动的时候不丢失动画状态。
val smallContent = remember {
movableContentOf {
Small()
Small()
}
}
FlowRow {
if (windowSizeClass == WindowWidthSizeClass.Compact) {
smallContent()
} else {
FlowColumn { smallContent() }
}
}
3.3 子元素位移动画 Modifier.animateBounds()
当流式布局进行重排的时候,子元素是瞬间移动的,没有流畅自然的感觉。最外层使用 LookaheadScope 包裹能够让 Compose 在布局变化时执行中间测量过程,并告知子元素这些中间状态。使用 Modifier.animateBounds() 构建一个自定义的 Modifier 传递给子元素,构建时可以指定 boundsTransform 参数到自定义的 spring 规范从而定制动画的运行方式。
最新版本
implementation("androidx.compose.animation:animation:1.8.0-beta01")
//自定义动画
val boundsTransform = { _: Rect, _: Rect->
spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium,
visibilityThreshold = Rect.VisibilityThreshold
)
}
//最外层包裹一下
LookaheadScope {
//单独创建 Modifier 方便多次传参给子元素
val MyModifier = Modifier.animateBounds(
lookaheadScope = this@LookaheadScope,
boundsTransform = boundsTransform
)
val smallContent = remember {
movableContentOf {
small(modifier = MyModifier) //将自定义的 Modifier 传递给子元素
small(modifier = MyModifier)
}
}
FlowRow {
if (windowSizeClass == WindowWidthSizeClass.Compact) {
smallContent()
} else {
FlowColumn { smallContent() }
}
}
}