前言
2026 年的可折叠手机出货量预计破 5000 万台。但你打开国内某 Top 5 媒体 App 看看——横屏、大屏、折叠屏状态全是"撑大到顶"模式,跟普通竖屏 App 没差。
工程师的难处:市面上至少三种折叠态(书、笔记本、平板)、两种铰链姿态(垂直、水平)、三种屏幕比例(21:9、4:3、1:1),光配置资源就够你喝一壶。
这篇文章不讲概念,直接上能落地的工程方案。
一、Jetpack WindowManager 1.4:从感知到响应
dependencies {
implementation("androidx.window:window:1.4.0")
// 测试
testImplementation("androidx.window:window-testing:1.4.0")
}
val tracker = WindowInfoTracker.getOrCreate(activity)
lifecycleScope.launch {
tracker.windowLayoutInfo(activity).collect { info ->
val feature = info.displayFeatures
.filterIsInstance<FoldingFeature>()
.firstOrNull()
val foldingState = feature?.state ?: FoldingFeature.State.FLAT
val orientation = feature?.orientation ?: FoldingFeature.Orientation.VERTICAL
when {
foldingState == FoldingFeature.State.HALF_OPENED -> showBookLayout(orientation)
foldingState == FoldingFeature.State.FLAT && feature != null -> showFoldedLayout()
else -> showStandardLayout()
}
}
}
要在仿真器调试:Android Studio Koala 起内置了 Foldable、Flip、Tablet 几种虚拟设备。
二、Hinge-aware 布局:别让按钮落在铰链上
工程上最容易翻车的是按钮或文字被铰链"截两半"。
@Composable
fun HingeAwareLayout(
content: @Composable ColumnScope.() -> Unit
) {
val feature = LocalFoldingFeature.current
if (feature?.state == FoldingFeature.State.HALF_OPENED) {
val hingeRect = feature.bounds
Column {
Box(modifier = Modifier.weight(1f).fillMaxWidth()) { content() }
Spacer(modifier = Modifier.height(hingeRect.height.dp))
Box(modifier = Modifier.weight(1f).fillMaxWidth()) { content() }
}
} else {
Column { content() }
}
}
原理是给铰链让出明确的 Spacer,把内容拆到两块独立区域。微软 Surface Duo 的官方 Sample 里也是这个套路。
三、Tabletop / Book Mode:交互模型根本不同
Tabletop 模式比较特殊——下半屏可以变成 Touchpad:
// 监测 FoldingFeature.Orientation.HORIZONTAL + State.HALF_OPENED
val tableTopMode = foldingFeature?.let {
it.orientation == FoldingFeature.Orientation.HORIZONTAL &&
it.state == FoldingFeature.State.HALF_OPENED
} ?: false
if (tableTopMode) {
BottomPanelTouchpad(onMove = { dx, dy -> scrollBy(dx, dy) })
}
四、资源目录:按屏幕宽度而不是"sw600dp"
这是常被忽略的点。折叠屏展开后宽度普遍 ≥ 600dp,但铰链一关瞬间回到 320dp。
正确的资源配置:
res/
├── layout/ # 默认(折叠态)
├── layout-sw600dp/ # 平板与展开态
├── layout-sw720dp/ # 折叠屏展开 + 桌面模式
├── layout-sw840dp/ # 大折叠屏双屏合盖
└── values-h600dp/ # 仅用于高度型折叠屏
不要用 drawable-sw600dp 这种"按唯一维度"的策略——折叠态的 sw 跟展开态能差到 600dp。
五、相机预览与多任务:相机别独占屏幕
折叠屏展开后,用户的"视频预览+操控"如果还像普通手机那样全屏,会出现两种错位:
用户两个手握在上半屏,下半屏的按钮够不着;
横屏布局被迫拉成竖向挤压。
val hinge = windowLayoutInfo.displayFeatures
.filterIsInstance<FoldingFeature>().firstOrNull()
if (hinge?.state == FoldingFeature.State.HALF_OPENED) {
// 把相机预览控件推到上半屏,按钮推到下半屏
CameraDualPanelLayout(
topPanel = { PreviewView() },
bottomPanel = { CameraControls() }
)
} else {
CameraFullScreenLayout() // 折叠态走原方案
}
六、性能:折叠屏 120Hz 不是默认
折叠屏屏幕大、刷新率高,渲染压力翻倍。优化建议:
避免全屏渐变——会强制重绘整个 surface;
用 LazyColumn/LazyRow——别再用 ScrollView 在 7 英寸屏上铺一堆子 View;
Material 3 Expressive 的 spring 动画——在折叠屏上 eST 会放大 1.5 倍,要用
Modifier.animateContentSize而不是Animatable;折叠时回收资源——监听到
State.FLAT时主动onTrimMemory把不用的纹理释放掉。
七、测试:仿真器 + 真机缺一不可
仿真器只能跑 WindowInfoTracker,跑不了铰链物理动画。要测手感必须真机:
三星 Galaxy Z Fold / Z Flip:铰链角度传感器最准;
华为 Mate X:铰链垂直 90° 时触发"笔记本模式"更稳定;
Pixel Fold:用
adb shell wm size 2200x1840模拟展开。
每种机型都要走一遍:
启动 App → 系统级折叠监听是否触发;
旋转 → 重新订阅 layoutInfo;
锁屏 → onStop 后 state 缓存是否正确还原。
八、上线门槛检查清单
折叠态资源都能找到对应 dpi;
横屏 / 折叠态没有依赖单一竖屏布局;
大屏下没有显示"建议使用手机访问"提示;
平板模式支持 orientation lock 控制;
关键交互(购买、表单提交)在折叠态/展开态都可用。
小结
折叠屏的"挑战"不在 API,而在测试矩阵。下一篇文章我们换到大屏的另一头:Android TV 与智能投影应用适配,工程难度更高——焦点逻辑跟触控根本不是一套。
参考阅读
Jetpack WindowManager 1.4 API
大屏应用质量指南(developer.android.com)
「Foldables by Google」官方 Sample