2025年的Google DevFest,郭霖老师讲Android 16的那场,我蹲在第一排靠走廊的位置听完。主题叫"庖丁解牛",但现场更像一场外科手术演示——他对着大屏折叠样机,把Android 16的自适应机制一层层剥开:窗口怎么变、布局怎么重排、数据怎么保活、系统怎么限制你。散场后我回酒店干了件很朴素的事:把手里几个项目的targetSdk全部翻出来过了一遍。因为听完那场分享我很确定,Android 16这一波"自适应"不是又加了十几个新API,而是逼着所有Android开发者重构一个底层认知:你的应用不再活在一个固定尺寸的"手机窗口"里了。
这场分享适合谁?适合所有还在维护Android项目的开发者——不管你做的是工具类App还是重交互的电商应用,只要你的界面还是按着竖屏手机那套"宽360dp、高800dp"的假设写的,Android 16的适配清单里就一定有你的名字。这篇文章不打算逐字复述演讲,而是把"自适应"的秘密拆开、揉碎,结合我在实际项目里踩过的坑,整理成一份能直接拿去用的路线图。
1. 从DevFest现场说起:为什么Android 16这波"自适应"动了底层认知
1.1 现场最扎心的一句话:你的App不是活在手机上,而是活在窗口里
郭霖在分享开头放了一张很旧的截图——是当年iPhone 4和初代Galaxy S的对比,两款设备屏幕分辨率差了不少,但开发者适配起来并不难:横竖屏切一下,搞个xhdpi资源目录就完了。但他紧接着放了一张2025年的设备全家福:竖折、横折、三折、平板、车载屏、带鱼屏的桌面模式外接屏,宽度从320dp一路干到1000dp开外。现场氛围就是从那一刻开始变凝重的。
他说了一句话,我记到现在:“你为‘屏幕’做的每一处假设,都是折叠屏上的一颗地雷。”这句话精准地概括了Android 16把"自适应"提到战略高度的原因:设备形态已经从"几个固定尺寸"彻底转向"一个连续变化的尺寸空间"。你的Activity在竖屏手机上可能只有360dp宽,在横折展开态可能变成800dp,在桌面窗口模式下用户可以拖到任意宽度,甚至缩成一个带鱼屏比例。过去"适配屏幕"是静态的,现在"适应窗口"是动态的、连续的、用户可操控的。
1.2 从手机思维到窗口思维:三个真实现象倒逼出来的转变
这三四年我自己的体感也很明显,有三个现象是整个行业不得不转向"窗口思维"的催化剂:
第一个现象是折叠屏从尝鲜设备变成了生产力设备。用户买折叠屏不是为了炫,而是真的要一边看视频一边回消息,或者一边开会议纪要一边翻日历。这类使用场景要求应用在窗口尺寸变化时不能重启、不能白屏、不能布局错乱。你可以想象一下:用户在展开的屏幕上拖拽应用边缘,把窗口从800dp逐步缩到500dp,如果这段过程里应用直接重建或者控件叠在一起,用户的第一反应不是"我要调一下设置",而是"这App太烂了,删掉"。
第二个现象是分屏和多窗口成了默认能力,而不是可选能力。Android 16延续了前几代系统对多窗口的强化,再加上桌面模式、外接显示器的普及,手机上的应用几乎都会被并排显示。在这种场景里,你的Activity可能只分到一半甚至三分之一的宽度,同时还要保证核心功能和手势区可用。很多应用根本扛不住这种压缩——不是崩溃,而是按钮挤成一团、文字截断、RecyclerView的item宽度没有最小值保护。
第三个现象是系统级的能力开始"自适应"地分配资源。比如刷新率会根据你的滚动速度动态切换,图标会随着主题和壁纸变化重绘,返回手势动画会根据预测结果动态调整。这些变化虽然不是布局层面的,但它们共同构成了一种新的用户预期:Android设备应该是"聪明"的,App也应该跟系统一样聪明,能在不同的窗口形态下自动找到最合适的表达方式。
1.3 为什么这篇文章值得你读完
我见过太多开发者在适配清单面前的第一反应是:跑起来没问题啊,我用的是dp,怎么会错?直到他们在折叠屏上打开自己的应用,看到左侧屏幕一个巨大的空白、中间一条明显的分割线把按钮劈成两半,才明白那些历史包袱有多重。
这篇文章接下来会从三个层次展开:先给你一张完整的"Android 16自适应全景图",讲清楚系统到底想让你适配哪些内容;再往原理层切一刀,讲窗口尺寸变化的完整链路,以及那些API背后的设计意图;最后进入排错与实操,把踩坑排查链路和一套可复用的适配流程整理出来。全程不绕弯子,能抄作业的直接给作业。
2. 自适应的全景拆解:Android 16让开发者适配哪些内容
2.1 主线一:窗口尺寸变化与多窗口支持
这是Android 16自适应机制里优先级最高的一条主线,也是"自适应"这个词在Android语境里最核心的含义。过去我们用screenOrientation锁竖屏、用固定宽高的layout_gravity、在onConfigurationChanged里手动处理横竖屏切换,这套思路在2025年已经完全不够用了。原因很简单:窗口尺寸变化不再只有横竖屏两个离散值,而是一条用户可以自由拖拽的连续区间。
Android 16对开发者的要求可以总结成三条:
- 应用必须声明可调整大小。Google Play政策早就在强推这一点,Android 16系统层面也会对锁方向、锁尺寸的行为做更严格的约束,尤其不能阻止用户在多窗口模式或折叠状态下切换。
- 布局必须响应用户窗口尺寸变化。这意味着你的界面在宽度从360dp变成840dp时,不应该只是"拉伸放大",而是应该自动重排——列表可以变成双列,底部工具栏可以挪到侧边,详情页可以从独立的Activity变成并排的面板。
- 必须处理好窗口尺寸变化时的生命周期。这涉及状态保存、重建、资源重新加载等一堆脏活累活。
为了落实这三条,Google推荐了一套组合工具:Jetpack WindowManager负责提供窗口度量数据和折叠状态信息;WindowSizeClass把连续的尺寸映射成Compact/Medium/Expanded三档,方便你按档位切换布局;Activity Embedding让两个独立Activity在大屏上并排展示。这套东西在Android 12L时期就有了雏形,到Android 16基本成为标准解法。
2.2 主线二:系统级自适应(图标、字体、刷新率)
除了窗口布局,Android 16的自适应还体现在几个系统级维度上。
自适应图标(Adaptive Icon)是这条线里比较成熟的部分,Android 8就引入了,但直到现在仍有应用没有正确提供。自适应图标要求你同时提供前景层和背景层,系统根据不同的设备环境裁剪成不同的形状。Android 16对图标的要求更严格了,如果你的应用还只提供一张正方形的android:icon,在部分设备上会显得要么巨大要么被强行裁切,非常影响整机美感。
字体与显示大小自适应是很多团队忽视的重灾区。系统支持字体缩放最高到200%,显示大小(Display Size)也可以调大调小。如果你的布局用了固定高度的TextView或者写死的行高,用户一开大字模式,文本截断和重叠现场就会出现。郭霖在分享里特别提到一个案例:某个银行类应用在字体缩放200%时,登录按钮直接飞出屏幕外,用户连验证码都输不了。这不是段子,是真实事故。
动态刷新率(Refresh Rate Switching)属于系统底层的自适应能力。现在的LTPO屏幕可以在1Hz到120Hz之间动态切换,Android系统会根据你的当前场景决定刷新率:看静态图片时降低,滚动列表时拉满。对应用来说,这一块基本不需要开发适配,但如果你在代码里强行锁定刷新率,比如通过某些非公开API强制屏幕保持高刷,在Android 16上可能会被系统策略忽略甚至限制。能不用就别用,尊重系统的调度逻辑。
2.3 主线三:隐私与安全自适应的收紧方向
"自适应"还有一个容易被忽略的方向:安全策略的适应与收紧。Android 16在隐私权限、后台行为、跨进程数据访问上都有进一步调整,其中社区讨论最热烈的是跨进程共享存储受到的SELinux策略限制问题——很多团队一直在用多进程SharedPreferences或者直接用文件共享来做跨进程数据同步,在高版本系统上越来越难跑通,这不是"玄学兼容问题",而是权限模型的根本变化。
我个人的建议是:凡是涉及多进程共享数据的方案,尽早迁移到ContentProvider或Storage Access Framework这类系统认可的组件上。别看现在跑得好好的,系统版本一升,xSharedPreferences这种野路子大概率会是最先翻车的那个。
另外,Android 16对剪贴板的访问提示、权限弹窗的交互方式、后台获取位置的频率限制也有改动。这些改动的共同点是:系统会根据用户当前的使用场景和敏感度,动态调整对App的信任尺度。这种"场景敏感的自适应安全机制",以后会成为Android生态的常态。
2.4 一张表理清Android 16的适配全景
我整理了一份自用的适配清单,做成表格放在项目Wiki里,每次升级targetSdk都会对着过一遍:
| 适配维度 | 核心要求 | 推荐方案 | 常见翻车点 |
|---|---|---|---|
| 窗口尺寸 | 支持任意宽度变化 | Jetpack WindowManager、WindowSizeClass | 固定宽高、锁方向 |
| 多窗口 | 声明resizable,状态可恢复 | Activity Embedding、多窗口测试 | 崩溃恢复、状态丢失 |
| 折叠屏 | 处理铰链区与姿态变化 | FoldingFeature API | 把内容放在铰链区 |
| 图标 | 提供前景层+背景层 | Adaptive Icon导出 | 单层方形图标被裁切 |
| 字体 | 支持字重、字体缩放200% | 动态字号、自适应行高 | 固定行高、固定文字宽度 |
| 刷新率 | 不强制锁定 | 尊重系统调度 | 非公开API强制高刷 |
| 隐私安全 | 跨进程数据合规 | ContentProvider替代直接共享 | xSharedPreferences类方案 |
| 返回手势 | 支持预测性返回动画 | Predictive Back API | 无视动画导致视觉撕裂 |
这张表不一定覆盖每个人碰到的所有问题,但足以帮你梳理出一个排查骨架。接下来我们往深处剖,看看窗口自适应这条主线的技术链路到底是怎么设计的。
3. 庖丁解牛第一刀:窗口自适应的核心技术链路原理
3.1 配置变更的进化史:为什么老一套撑不住了
Android早期处理屏幕变化的思路很简单:横竖屏切换的时候,系统销毁当前Activity再重新创建,你只需要提供两套布局资源layout-port和layout-land就行。后来为了性能,Android加入了configChanges机制,让你在AndroidManifest里声明"我自己处理某些配置变化",从而避免Activity重建。但这条路走到今天已经山穷水尽了。
为什么?因为系统能枚举的配置变更类型是有穷的,而窗口尺寸是无穷的。折叠屏展开、分屏拖动、外接屏弹入,这一系列变化不是简单的"横竖屏"两个状态,而是一整条连续谱系。系统如果每次都重建Activity,体验上是不可接受的;但configChanges又没法穷举所有尺寸值,于是只能靠开发者主动用窗口度量API来感知变化并手动更新布局。
这就是为什么Jetpack WindowManager成为核心组件的原因:它把"窗口状态"抽象成了可观测的数据流,而不是依赖资源目录的静态切换。
3.2 WindowMetrics:正确获取窗口度量值的姿势
从Android 11开始,DisplayMetrics和display.getSize()这类旧API就被标记为"拿不到真实窗口尺寸",因为它们在多窗口、自由窗口、折叠屏场景下返回的是整个屏幕的参数,而不是你应用实际占用的窗口大小。正确的方法是使用WindowMetricsCalculator:
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val metrics = WindowMetricsCalculator.getOrCreate() .computeCurrentWindowMetrics(this) val widthDp = metrics.bounds.width() / resources.displayMetrics.density val heightDp = metrics.bounds.height() / resources.displayMetrics.density // 用窗口实际尺寸而不是屏幕尺寸做布局决策 val sizeClass = getWindowSizeClass(widthDp) applySizeClassBasedLayout(sizeClass) } private fun getWindowSizeClass(widthDp: Float): WindowSizeClass { return when { widthDp < 600f -> WindowSizeClass.COMPACT // 手机竖屏宽度 widthDp < 840f -> WindowSizeClass.MEDIUM // 折叠屏展开/平板半屏 else -> WindowSizeClass.EXPANDED // 平板、桌面模式 } } } enum class WindowSizeClass { COMPACT, MEDIUM, EXPANDED }这段代码的意图很明确:把所有布局决策建立在"当前窗口的实际宽度"之上,而不是缓存一份静态配置。窗口宽度变化时,Activity可能不会重建,但你拿到的WindowMetrics已经变了,所以需要注册一个监听器,在尺寸变化的窗口期里及时刷新UI。
Kotlin侧的监听可以包装成一个Flow:
class WindowSizeObserver(private val activity: Activity) { private val _sizeClass = MutableStateFlow(WindowSizeClass.COMPACT) val sizeClass: StateFlow<WindowSizeClass> = _sizeClass.asStateFlow() fun observeWindowSize() { WindowInfoTracker.getOrCreate(activity).windowLayoutInfo(activity) .map { info -> computeSizeClass(info) } .distinctUntilChanged() .collect { size -> _sizeClass.value = size } } }实际项目里不需要写得太重,但千万不要在onCreate里取完尺寸就再也不管了。折叠屏展开、分屏拖动这些场景下,你的Activity完全是"活着"的,布局却需要跟着窗口动态变化,这是自适应机制和过去最大的不同。
3.3 折叠状态与铰链角度:新的"窗口参数"
折叠屏设备给自适应机制引入了两个全新的参数:展开姿态(Posture)和铰链状态(Hinge State)。Jetpack WindowManager通过WindowLayoutInfo暴露这些信息:
WindowInfoTracker.getOrCreate(this).windowLayoutInfo(this) .collect { layoutInfo -> layoutInfo.displayFeatures.forEach { feature -> if (feature is FoldingFeature) { when (feature.state) { FoldingFeature.State.HALF_OPENED -> { // 设备处于书页半开状态,铰链区不要放交互控元素 } FoldingFeature.State.FLAT -> { // 设备展开平放,可以按整块屏幕设计 } else -> {} } } } } }这里有一个非常容易踩的细节:在HALF_OPENED状态下,屏幕通过铰链折成了一个夹角,你的布局会被实际分成两块物理区域。如果你把一行按钮横跨两块区域放置,按钮中心恰好落在铰链上,用户体验就是灾难性的。正确的做法是拿到FoldingFeature.bounds之后,把关键控件主动避让到铰链两侧的显示区域内。郭霖现场演示了一个案例:一个普通的日历视图在铰链半开状态下,交易日历的格子正好被铰链从中间劈开,他们用FoldingFeature的边界信息把日历的关键操作按钮下沉到底部安全区,问题立刻消失。这就是"庖丁解牛"里说的"切中肯綮"——知道刀应该落在哪里,比把整头牛切碎重要得多。
3.4 窗口尺寸变化的完整生命周期
从系统源码的视角看,一次窗口尺寸变化的链路大致是:
- 窗口管理器(WindowManagerService)感知到窗口边界变化,计算新的布局约束;
- ViewRootImpl收到布局请求,触发一次新的
relayout,把新的尺寸参数同步给顶层View; - 如果Configuration里的关键属性(如
screenWidthDp、orientation)发生变化,会依次回调到onConfigurationChanged并评估是否重建Activity; - 如果窗口尺寸变化但Configuration属性没变,Activity不会重建,只会触发一次
View的尺寸重排。 - 系统在资源合并时,会把最新的窗口尺寸作为资源选择依据,所以你依然可以利用
-sw600dp这类资源限定符来自动切换布局。
理解了这条链路,你就能明白适配的关键战场在第4步:大多数窗口拖拽行为并不会触发Activity重建,但会让你所有的View重新测量和布局。如果你的根布局是ConstraintLayout,并且关键约束写得有问题,窗口缩小时就可能出现约束冲突或者控件重叠。这一点在排查各类"窗口变化后布局错乱"问题时非常有用。
4. 庖丁解牛第二刀:适配过程中最常翻车的几个坑位排错篇
4.1 坑位一:锁死方向与固定尺寸的历史包袱
接手过老项目的读者应该都对这段代码不陌生:
<activity android:name=".MainActivity" android:screenOrientation="portrait" tools:ignore="DiscouragedApi,LockedOrientationActivity" />这类代码在Android 16上是典型的"历史包袱"。Google Play政策早几年就开始限制新应用锁定方向,Android 16更是把自适应窗口当做事关用户体验的关键能力。如果你的应用强行锁定竖屏,在折叠屏展开时系统会强行触发onConfigurationChanged甚至忽略你的声明,结果就是页面出现黑边、排版错乱,还找不到原因。
排查思路很简单:全局搜索screenOrientation和设置了fixedSize的布局,把“锁竖屏”策略从业务Activity上剥离开,改成根据窗口尺寸自适应切换。如果实在有一些页面在竖屏下才符合合规要求(比如扫脸页面),也要用requestedOrientation = SCREEN_ORIENTATION_USER这类更宽松的限制方式,至少给系统留出多窗口的余地。
4.2 坑位二:跨进程存储的SELinux限制
这个坑最近社区里讨论得很猛:xSharedPreferences在Android 16因SELinux限制导致跨进程读写异常。简单说,部分机型/系统策略开始收紧应用跨进程直接共享文件与偏好数据的访问路径,你的多进程组件可能读写的是同一份XML文件,但SELinux策略不允许某个进程访问另一个进程创建的文件上下文,于是出现"主进程写进去,子进程读不到,或者直接抛异常"的诡异现象。
排查链路我复述一下,帮助大家举一反三:
- 先复现问题,观察异常发生时机是不是在
Application启动或者多进程组件首次调用时; - 查看Logcat里有没有SELinux的avc denied日志,这是最直接的证据;
- 如果确认是跨进程文件访问权限问题,检查项目里有多少处是用
getSharedPreferences("xxx", MODE_MULTI_PROCESS)或者直接读Team文件的; - 把这些点全部替换为
ContentProvider,或者改用DataStore的单一进程方案; - 加一道进程边界测试:主进程写入,子进程读取,验证稳定性。
这个坑的本质是系统安全模型演进,不完全是你的代码问题,但在Android 16的适配清单里必须处理。早改早踏实,别等项目上线被用户反馈打蒙了再回来查。
4.3 坑位三:窗口变化时的竞态条件与布局抖动
窗口尺寸变化不是瞬时的,在拖拽过程中会连续触发多次测量布局。如果你的代码里监听了尺寸变化并在回调中执行耗时操作或者直接notifyDataSetChanged(),就很容易出现两类问题:一是RecyclerView内容跳动、图片闪一下;二是监听器回调堆积导致界面抖动。
我处理过的一个真实案例:一个聊天页面在分屏拖动时,输入框和消息列表不断抖动,像是在"打架"。排查过程是这样的:第一轮怀疑是ConstraintLayout约束问题,检查了 margins 和链条关系,没有明显错误;第二轮在onWindowFocusChanged和onConfigurationChanged里打了日志,发现尺寸变化一次,RecyclerView被反复 notify 了好几次;第三轮定位到代码里用了一个自定义的OnGlobalLayoutListener,在布局变化时调用scrollToPosition,把用户正在看到的列表位置强行拽回底部。
问题根因清楚了:布局监听和滚动逻辑互相触发,形成正反馈循环。解决方案也不复杂,给滚动行为加一个标志位,在用户手动滚动或尺寸变化稳定前不执行自动滚动,同时用ObjectAnimator做一次平滑的位移而不是直接跳变。
这一类坑的排查套路可以总结成一条排查链路:先判断是布局约束问题还是逻辑触发问题,方法是在关键回调里打点统计调用次数;再用二分法缩小触发范围;最后看有没有互相触发的事件循环。窗口自适应时代,这种竞态问题会比以前多很多,因为尺寸变化的频率大幅提高了。
4.4 坑位四:只适配了内容区,没适配系统栏与安全区
折叠屏和窗口拖拽还有一个隐性杀手:系统栏形态的变化。竖屏手机有圆角、挖孔,折叠屏展开时分屏安全区不同,桌面模式下窗口可以停靠到底部导航栏附近。如果你的页面还在用硬编码的statusBarHeight或者把内容直接頂到屏幕最顶端,在Android 16的多窗口场景下会出现内容被系统栏遮挡的情况。
正确做法是用ViewCompat.setOnApplyWindowInsetsListener处理系统栏插入区域,或者用WindowInsetsCompat拿到systemBars的Insets,动态调整页面的padding。这一条务必写进所有Activity的基类里,否则每开一个新页面都要和系统栏搏斗一次。
4.5 排查链路实录:一次分屏下的白屏问题
最后放一个完整的排查链路,这是我最近在一个平板适配项目里真实遇到的:分屏启动时,右侧窗口经常白屏3秒,然后恢复正常。
第一步复盘现场现象:不是崩溃,是慢,启动时机和分屏有关系。第二步看Logcat,发现有一条Choreographer卡顿日志和一个InputDispatch超时警告,初步怀疑是主线程被阻塞。第三步用systrace抓启动链路,看到onCreate里有一个File读取操作,读取的是一个几千行的本地配置文件。第四步分析:这个文件读取在普通竖屏启动时只要20ms,但分屏窗口启动时资源竞争更严重,磁盘IO被其他进程抢占,于是主线程被卡住,等到读取完成才绘制第一帧,就表现为白屏。
解决方案是把这个文件读取改到子线程,配合android:windowDisablePreview的关闭,让系统首帧先出来,再用异步数据更新布局。改完之后分屏启动时间从3秒降到700ms左右。这类问题的共性是:窗口变小了,系统资源竞争反而更激烈,原来能蒙混过关的耗时操作全都会暴露出来。自适应的本质之一,就是让应用在任何窗口形态下都保持同样的响应速度,而不是只在标准竖屏下表现良好。
5. 照着做就行:Android 16自适应适配的落地路线图
5.1 第一步:资产盘点,建立Adaptive Todolist
先别急着改代码,准备一张表,把所有页面和组件过一遍。我建议从下面这四个维度做盘点:
- Activity维度:每个Activity的launchMode、方向设置、是否可以调整大小、是否支持多窗口恢复;
- 布局维度:哪些布局有固定宽高?哪些用到了绝对定位?哪些依赖横竖屏的限定资源?
- 组件维度:有没有自定义View写死了绘制尺寸?有没有Dialog/Fragment在窗口变化时不响应?
- 数据维度:哪些地方用了跨进程数据共享?哪些页面启动时有阻塞主线程的I/O?
把每一项的状态标成"安全/待处理/高危",高危项目优先处理。这一步看起来很笨,但能帮你避免在适配过程中被没完没了的新问题淹没。
5.2 第二步:按WindowSizeClass建立布局矩阵
不要针对几百种具体尺寸做适配,而是按WindowSizeClass三档建立布局矩阵:
| WindowSizeClass | 典型场景 | 布局策略 |
|---|---|---|
| Compact(<600dp) | 手机竖屏、分屏窄窗 | 单列列表、底部导航、抽屉式菜单 |
| Medium(600-840dp) | 折叠屏展开半屏、平板竖屏 | 双列布局、侧边导航、列表+详情 |
| Expanded(>=840dp) | 平板横屏、桌面模式 | 多栏面板、Activity Embedding、快捷键 |
矩阵定义好后,每个页面只需要回答一个问题:在这三种典型宽度下,我的界面应该长成什么样?如果你的页面在Medium和Expanded下的布局几乎没区别,那也不用强行区分,但至少你要确认控件不会在840dp下被拉得过分稀疏或者变形。
5.3 第三步:从"能跑"到"耐操",建立回归验证清单
代码改完之后,真正的难点在于验证。只在一台手机上看效果完全不够,我建议准备一个回归测试矩阵,至少覆盖以下几类环境:
- 手机竖屏(500dp左右宽度),打开所有页面;
- 折叠屏展开态(700-900dp宽度),打开所有页面;
- 分屏模式,把应用拖到屏幕左半和右半,来回拖动;
- 字体缩放150%、200%,检查文本溢出;
- 桌面模式/外接屏,检查自由窗口和超宽屏下的布局。
如果时间有限,优先跑折叠屏展开态和分屏拖动这两项,因为这两个场景最容易暴露问题。
自动化层面,建议用Compose UI测试或Espresso配置几种模拟窗口尺寸,把"尺寸变化后关键按钮仍然可见可点"写成断言。这部分的工程化投入会随着折叠屏保有量上升而越来越值钱。
5.4 我个人的几条经验
做完整轮适配后说几个实际操作中的体会,希望能帮你少踩几个坑。
体会一:不要迷信dp。dp能保证物理尺寸一致,但它在窗口自适应上并不表示"布局一定合理"。一个按钮在360dp宽的窗口里占了一半宽度是合理的,在840dp宽的窗口里还是占一半宽度就会显得很傻。要用WindowSizeClass控制相对比例,而不是让dp解决一切问题。
体会二:建议把"窗口变化"当成一等事件来对待。以前我们只监听横竖屏切换,以后应该养成监听窗口尺寸的习惯,并把尺寸变化纳入页面状态管理。只要在架构设计里留出一个"窗口状态"的数据源,后续适配任何新形态设备都会轻松很多。
体会三:越是老项目,越不要指望一次性把全部页面改完。先挑用户访问频率最高的核心路径适配,比如登录、首页、列表页、详情页,把这几条路径跑顺了,再逐步扩展。同时保证每次改动都能单独上线,而不是憋一个大版本最后一起发——自适应适配是持续性的,不是一次性的重构项目。
最后再分享一个小技巧。适配完之后,把项目里所有"尺寸魔法数"全部抽成基于WindowSizeClass的决策函数,不要在布局文件里直接写死大段宽度值。你会在下一次窗口形态变化时发现,这套做法的收益远比你想象中高。Android 16只是开始,后续的Android版本只会把"窗口自适应"这件事做得更彻底。早一点把思维切过来,后面就都是顺水推舟的事。