窗口大小分类
Compose 将 Android 设备的屏幕尺寸分为三类:
- Compact: 小屏幕,一般就是手机设备,屏幕宽度 < 600dp
- Medium:中等屏幕,大号的板砖手机如折叠屏或平板的竖屏,600dp < 屏幕宽度 < 840dp
- Expanded:展开屏幕,平板或平板电脑等,屏幕宽度 > 840dp
它是以某个维度来划分的,如上图是以宽度作为划分点,当然也可以按照高度来作为划分点:
但由于垂直滚动的普遍存在,可用宽度通常比可用高度更重要;因此,宽度窗口大小类别很可能与应用的界面更相关。所以大部分开发者只需要根据宽度调整应用即可。
窗口大小类别不适用于“isTablet-type”逻辑,而是由应用可用的窗口大小决定(无论运行应用的设备是什么类型);这有两个重大影响:
-
实体设备不能保证特定的窗口大小类别。应用可用的屏幕空间可能会与设备的屏幕尺寸不同,这有很多原因。在移动设备上,分屏模式可以在多个应用之间拆分屏幕。在 Chrome 操作系统中,Android 应用可以呈现在可任意调整大小的自由式窗口中。可折叠设备可以有两个大小不同的屏幕,分别可通过折叠或展开设备使用。
-
窗口大小类别在应用的整个生命周期内可能会发生变化。当应用处于运行状态时,设备更改屏幕方向、进行多任务处理和折叠/展开可能会改变可用的屏幕空间量。因此,窗口大小类别是动态的,应用的界面应相应地调整。
基于 Compose 的应用可以通过 calculateWindowSizeClass()
函数来当前窗口的分类,它使用 material3-window-size-class 库计算 WindowSizeClass
,需要添加依赖:
implementation "androidx.compose.material3:material3-window-size-class:1.0.0"
调用示例代码:
import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 计算Activity当前窗口的窗口大小类别
// 如果窗口大小改变了,例如当设备旋转时,calculateSizeClass返回的值也会改变。
val windowSizeClass = calculateWindowSizeClass(this)
MyApp(windowSizeClass)
}
}
}
@Composable
fun MyApp(windowSizeClass: WindowSizeClass) {
// 根据不同的窗口大小类别,进行不同的视图展示逻辑
when(windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact -> Text("当前是 Compact 屏幕")
WindowWidthSizeClass.Medium -> Text("当前是 Medium 屏幕")
WindowWidthSizeClass.Expanded -> Text("当前是 Expanded 屏幕")
}
}
在非 Compose 的应用中,也可以判断窗口大小类别,但是要麻烦一点:
enum class WindowSizeClass {
COMPACT, MEDIUM, EXPANDED }
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
// Replace with a known container that you can safely add a
// view to where it won't affect the layout and the view
// won't be replaced.
val container: ViewGroup = binding.container
// Add a utility view to the container to hook into
// View.onConfigurationChanged. This is required for all
// activities, even those that don't handle configuration
// changes. We also can't use Activity.onConfigurationChanged,
// since there are situations where that won't be called when
// the configuration changes. View.onConfigurationChanged is
// called in those scenarios.
container.addView(object : View(this) {
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
computeWindowSizeClasses()
}
})
computeWindowSizeClasses()
}
// 非 Compose 应用中的使用方法
enum class WindowSize {
COMPACT, MEDIUM, EXPANDED }
private fun computeWindowSizeClasses() {
val metrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
val widthDp = metrics.bounds.width() / Resources.getSystem().displayMetrics.density
val widthWindowSizeClass = when {
widthDp < 600f -> WindowSize.COMPACT
widthDp < 840f