Skip to content

屏幕适配:从清晰显示到动态窗口可用 ​

模块范围:主线实现栈为 Android View / Jetpack Compose / Flutter;iOS 在资源倍率、Auto Layout / Size Class、Dynamic Type、Safe Area 等处作必要对照,不扩成第四条等篇幅主线。

开篇案例:手机正常,为什么仍不算适配完成 ​

同一套页面在 QA 的 390dp 手机上可能“看起来没问题”,但上线后仍可能在以下场景翻车:

场景典型现象多半属于哪一层
1024dp 平板全屏图标巨大、列表稀疏,像“大号手机”布局 / 断点 → 02
系统字体 2.0x标题裁切、按钮点不到、Row 挤爆系统边界 / 字体 → 03
键盘弹起底部 CTA 被挡、输入框不可达系统边界 / IME → 03
平板分屏(窗口约 500dp)仍强行双栏,内容挤爆布局 / 当前窗口 → 02
xxhdpi 机放 mdpi 桶图图标发糊、内存暴涨资源 / 解码 → 01

完整适配的目标:在不同密度、窗口尺寸、无障碍字体和系统占用区域下,页面仍清晰、可读、可点、可达,且窗口变化后结构仍合理——不是把设计稿按比例放大到所有设备。

一句话定位 ​

这一模块讲「图片与 UI 如何在不同屏幕密度、不同屏幕尺寸、不同系统安全区下正确、清晰、可点击、可阅读地显示」,主线是:

dp 与密度(基础换算)→ 图片资源放置(密度桶 / 放图策略)→ 断点响应式布局(三端实现)→ 字体 / 安全区 / 折叠屏 / 分屏治理。

与 11-image-loading(图片加载链路)互补:11 管“怎么加载”,12 管“显示多大、放哪个桶、怎么布局、怎么避开系统栏与大屏坑”。11 的全链路索引会在此处回链。

本模块要回答的问题 ​

逻辑单位与换算机制 ​

  • dp、pt、逻辑像素如何在 Android View、Compose、Flutter、iOS 中换算?三端机制有何不同?
    • 一句话答:三端同构为 px = 逻辑单位 × 倍率,但倍率从哪来、资源怎么解码各不相同——Android(含 Compose)走连续 dpi/160 + 密度桶解码;Flutter 走 devicePixelRatio + 变体目录,不按 Android 桶机制扩大解码;iOS 走离散 @2x/@3x 1:1 匹配(详见 01 第一层)。
栈逻辑单位换算公式原理要点
Android Viewdppx = dp × (设备dpi / 160)以 160dpi(mdpi)为基准定义 1dp;布局按 dp 上屏,位图按 drawable-{桶} 放置,BitmapFactory 用 inDensity / inTargetDensity 解码期缩放,让图在不同密度屏显示成相同逻辑尺寸
Jetpack ComposeDp / sp同 dp:LocalDensity.current.density = dpi/160无独立密度体系,完全继承 Android 资源桶与解码公式;48.dp 与 View 的 48dp 等价,painterResource(R.drawable.*) 仍走 drawable-{桶} / VectorDrawable / nodpi
Flutter逻辑像素px = 逻辑像素 × devicePixelRatiodevicePixelRatio 来自宿主(Android 上 ≈ density,iOS 上 ≈ scale);资源用 1.0x/2.0x/3.0x 变体目录,AssetImage 按 dpr 选最近变体原像素解码——不会在解码期按 Android 密度桶再放大;缺高倍变体时尺寸按固有逻辑尺寸、绘制可能拉伸发糊
iOS(对照)ptpx = pt × scale(通常 2 或 3)设备密度离散三档(@1x/@2x/@3x),UIImage(named:) 1:1 匹配切图,不按 dpi 桶做二次解码缩放;仍须按倍率出多套切图,大图可用 Single Scale 单源
  • dp / 逻辑像素解决了什么、没解决什么?
    • 一句话答:它们是逻辑密度锚点——让同一 100dp 在不同 dpi 屏上视觉尺寸通常接近;但不归一化占屏比例(360dp 宽与 411dp 宽上同一 100dp 占屏不同)。占屏比例、375 补偿、断点换栏位归 02。另:1080px 宽在 dpr 2.0 与 2.625 上逻辑宽分别是 540dp / 411dp——适配看当前窗口逻辑宽,不看机型名。
  • 布局和字号为什么不能用同一套单位?
    • 一句话答:布局 / 间距 / 图标尺寸用 dp(Compose Dp、Flutter 逻辑像素、iOS pt);文字用 sp / TextUnit.Sp / TextScaler,跟用户无障碍字体设置走——别把图片倍率策略套到字号上(详见 01 · 03)。

图片资源放置策略 ​

  • 图片放哪个密度桶 / 变体?小图标和背景大图策略一样吗?

    • 先分两类再选策略——
      • ① UI 小图标:Android/Compose 矢量 XML 或 xxhdpi 单桶(只缩不放),Flutter 全量 1.0x~3.0x 变体或 flutter_svg,iOS @2x/@3x 精确切图;
      • ② 背景 / 运营大图:Android drawable-nodpi + inSampleSize,Flutter 单源 + cacheWidth 降采样,iOS Single Scale + contentMode。

    Android 低桶放高 dpi 机:内存按 (设备dpi/桶dpi)² 涨且插值变糊(详见 01 第二层)。

  • UI 上把图缩小了,为什么内存还不降?

    • 一句话答:内存由解码像素决定,不由最终显示尺寸决定——只改 width/height 不解码降采样,仍会按桶 / 原图全量解码进内存(详见 01 第三层 · 11-image-loading)。
栈UI 小图标背景 / 大图解码原理(你要记住的)
Android ViewVectorDrawable 或 drawable-xxhdpi 单桶drawable-nodpi + inSampleSize桶 dpi → inDensity,设备 dpi → inTargetDensity;解码倍率 r = 设备dpi/桶dpi,内存 ∝ r²
Compose同上(painterResource)同上与 View 同一套资源与解码,无 Flutter 式变体目录
Flutterflutter_svg 或配齐 1.0x/2.0x/3.0x单张 asset + cacheWidth/cacheHeightAssetImage 按 dpr 选变体;缺失高倍变体时不按 density 放大解码,绘制阶段可能拉伸低清源图
iOS(对照)Asset Catalog @2x/@3xSingle Scale 单源高清图离散 scale 1:1 点阵匹配,不做 Android 式桶缩放;缺 @3x 会在高分屏发糊

口诀:小图标靠倍率档(或矢量),大图靠单源 + 运行时自适应。完整决策流程见 01「资源问题的工程决策流程」。

布局与断点(详见 02) ​

  • 平板 / 横屏为什么不能用纯比例放大?
    • 一句话答:375 基准在 768~1024dp 平板会把整个 UI 放大 2~2.7 倍,图标巨大、布局稀疏;应改用断点(600 / 840dp)** 换布局而不是放大**(详见 02)。
  • 三端断点分别怎么实现?
    • 一句话答:Android View 用 values-sw600dp + 运行时窗口宽度分类;Compose 用 material3-window-size-class + material3-adaptive;Flutter 用 LayoutBuilder 自写断点或 responsive_builder 包(详见 02)。

系统边界(详见 03) ​

  • 字体放大、刘海屏、手势区、折叠屏为什么也属于“屏幕适配”?
    • 一句话答:因为真实适配不只是“宽高对不对”,还包括字能不能读、按钮能不能点、内容会不会被系统栏挡住、窗口变化时布局会不会崩(详见 03)。

先做归因:适配问题属于哪一层 ​

先问症状归 01 / 02 / 03 哪一层,再按「先检查什么」排查:

归属层常见症状先检查什么专题入口
01 密度与资源图标糊、大图 OOM、内存异常矢量 / 倍率档、nodpi、解码 inSampleSize / cacheWidth;桶放反则内存按 (设备dpi/桶dpi)² 涨01
02 窗口与断点平板像放大版手机、横屏挤、分屏后双栏挤爆是否全端套 375 缩放;是否缺 600/840 结构切换;是否按设备型号而非当前窗口宽度判断02
03 系统边界大字体裁切、底部按钮被挡、刘海 / 键盘 / 折叠态异常sp / TextScaler、容器是否写死高度、Expanded / weight;SafeArea / WindowInsets / viewInsets、键盘 IME03

屏幕适配三层模型 ​

屏幕适配模块知识图:密度、布局、系统边界与验证矩阵

层次解决什么典型故障不解决什么
第一层:密度与资源逻辑单位、倍率、图片清晰度、解码内存图标糊、大图 OOM、低桶内存 ×9平板双栏、键盘遮挡
第二层:窗口与断点当前窗口宽度、600/840 断点、栏位与导航平板空、横屏挤、分屏仍双栏字体截断、刘海挡内容
第三层:系统边界字体、Insets、键盘、折叠/分屏、验证矩阵大字体炸行、CTA 不可达图片放错桶

读图顺序:01 → 02 → 03。01 偏资源与清晰度,02 偏断点与布局结构,03 偏字体、安全区、键盘、分屏这类上线边界。只做其中一层,都不算完整适配。

跨端统一原则 ​

  1. 布局用逻辑单位(dp / Dp / 逻辑像素 / pt),避免布局尺寸直接写 px。
  2. 小屏可微调,大屏换结构:375 比例法仅适合 compact 小屏补偿(建议 clamp(1.0, 1.2)),平板应增长栏位与信息密度,不整体放大。
  3. 文本服从可读性与无障碍:字号用 sp / TextScaler 语义,不要全局禁用系统字体缩放。
  4. 按当前窗口与 Insets 判断,不按设备型号:分屏、折叠、多窗口会让同一台设备瞬间拥有不同可用宽度与安全区。

Android View / Compose / Flutter 实现速查 ​

这张表按适配维度横向对照四端(主线三栈 + iOS 对照):同一类问题在各端「用什么 API / 单位 / 机制」——不是完整教程,而是复习时快速定位「我这端该查什么」。密度与资源归 01,断点与结构归 02,字体与安全区归 03。

维度Android ViewComposeFlutteriOS(对照)
密度 / 资源drawable-{桶} / nodpi / VectorDrawable继承 Android 资源体系1.0x/2.0x/3.0x / flutter_svgAsset Catalog @1x/@2x/@3x
断点与结构values-sw600dp + WindowMetricsWindowSizeClass / PaneScaffoldLayoutBuilder / MediaQuerySize Class + 容器实际宽度
字体缩放sp(跟随系统 fontScale)TextUnit.Sp + MaterialTheme.typographyMediaQuery.textScaler(勿全局禁用)Dynamic Type / UIFontMetrics
最小可点击区约 48×48 dp(视觉可更小,触控区撑满)minimumInteractiveComponentSize()约 48×48 逻辑像素(Padding 扩区)约 44×44 pt(HIG 建议)
系统栏 / 安全区WindowInsetsCompat / edge-to-edgeWindowInsets / Scaffold paddingSafeArea / viewPadding / viewInsetssafeAreaLayoutGuide
动态窗口运行时窗口宽度 / 分屏WindowSizeClass + 自适应脚手架断点 + 可伸缩布局骨架Split View / Stage Manager 看容器宽

三篇专题分别解决什么 ​

序号主题文档你会在这里解决什么判断
01dp / dpi 与图片资源放置逻辑单位、资源倍率与图片解码:dp / dpi / scale 到内存图为什么糊、内存为什么涨、资源如何准备;px = dp × dpi/160;图标→矢量/xxhdpi,背景→nodpi+降采样
02断点响应式:三端实现当前窗口、断点与响应式布局:从 375 小屏补偿到多栏结构窗口变宽/变窄后如何换结构;统一断点 600/840dp;375 仅小屏;大屏换布局不放大
03字体缩放、安全区、折叠屏与验证矩阵字体、系统边界与动态窗口:屏幕适配的上线验收层默认状态正常但真实用户环境为何仍会失败;字体可读 + 内容不被挡 + 窗口变化不崩

业界通用适配方案总表 ​

这张表让你先知道行业里常见手段分别解决什么,再决定该深入 01、02 还是 03。

方案主流栈背后原理最常见用法重点边界深入阅读
密度桶 / nodpi / 资源变体Android / Flutter / iOS 对照先解决“同样物理尺寸下,图清不清、内存炸不炸”Android drawable-{桶}、Flutter 2.0x/3.0x 资源它只解决图片资源,不解决字体和布局01
sw600dp / dimen 分桶Android View用资源限定符按最小宽度切布局和尺寸资源values-sw600dp、layout-sw600dp、平板大间距/双栏 XML静态分桶好用,但分屏和动态窗口仍需运行时判断02
WindowMetrics / WindowSizeClassAndroid / Compose读取当前窗口宽度,按 600/840dp 做结构切换单栏/双栏/多栏、BottomBar/Rail/Drawer 切换不该退化成整页倍率放大02
Compose adaptive / pane scaffoldCompose官方把主从式和多 pane 页面封成自适应骨架列表-详情两栏、导航与内容并排适合主从页,不是所有复杂页面都能直接套02
LayoutBuilder / MediaQuery 断点Flutter在 widget 树内按约束/窗口宽度切布局600/840 断点、动态列数、NavigationRail不该只按机型判断,也不该混成 375 全端缩放02
375 设计稿缩放方案Flutter 为主用 当前宽度 / 设计稿宽度 做小屏尺寸补偿flutter_screenutil、设计稿边距/圆角/图标微调只适合 compact 小屏,不适合平板,更不该缩放正文文字02
安全区 / Insets / SafeAreaAndroid / Compose / Flutter / iOS 对照把系统栏、刘海、手势区、键盘占用空间让出来edge-to-edge、顶部标题避状态栏、底部 CTA 避 home indicator它解决系统边界,不解决断点结构03
字体缩放跟随系统Android / Compose / Flutter让正文和交互信息随用户无障碍字体设置变化Android sp、Compose sp、Flutter TextScaler不能靠全局禁用缩放来“修适配”03

最小验证路径 ​

上线前至少覆盖以下维度(完整矩阵与检查顺序见 03):

维度最小矩阵
宽度375 / 600 / 840+(可补 430、768、1024dp)
方向竖屏 / 横屏
字体默认 / 放大 1.3x / 2.0x
系统状态无键盘 / 键盘弹起;全屏 / 分屏
文案短中文 / 长中文 / 英文长词

对应 Labs 与推荐阅读顺序 ​

  1. 先读 01(dp / dpi / 密度桶 / 放图策略)——“显示多大、内存多少”的地基;
  2. 再读 02(断点响应式三端实现)——“手机 / 平板 / 横屏怎么布局”;
  3. 最后读 03(字体 / 安全区 / 折叠屏 / 验证矩阵)——“为什么线上适配经常不是宽度问题,而是系统边界问题”;
  4. 需要“怎么把图片加载出来”时,跳转 11-image-loading。

可交叉验证的 Lab 入口:

01 更像“资源怎么准备”;02/03 更像“布局和系统边界怎么兜住”。

常见考察与工程表达 ​

按「归因 → 方案 → 验证」组织,避免只背 API 名词:

类型考察什么应回到
基础换算dp / px / sp 区别;1080px 为何常 ≈ 360dp 逻辑宽01
资源策略图标为何矢量;背景为何 nodpi + 降采样;Flutter 只放 1x 为何发糊01
布局策略375 为何不能平推平板;为何增长栏位而非整体放大02
系统边界状态栏/刘海/手势区/键盘如何拿 insets03
验证横屏、分屏、折叠、大字体、多语言如何测03
选型何时 sw600dp、何时 WindowSizeClass、何时只是 SafeArea/字体弹性00 归因表

常见优化点 ​

  • 资源优化:图标矢量化;背景大图单高质量源 + 降采样;避免多桶重复大图导致包体膨胀。
  • 布局优化:大屏增加栏位、信息密度、导航形态(bottom bar → rail → drawer),而不是整套放大。
  • 可读性优化:文字用 sp / text scaler;按钮保留最小点击区域;长文案 / 多语言别写死宽高。
  • 系统边界优化:edge-to-edge + 安全区;键盘弹起时可滚动或抬升;顶部图文注意刘海遮挡。
  • 验证优化:建设“375 / 430 / 600 / 768 / 1024dp + 横竖屏 + 字体放大 + 分屏”矩阵,而不是只靠肉眼抽测。
  • 方案收口优化:资源问题先回 01,结构问题先回 02,字体 / 安全区 / 键盘 / 分屏问题先回 03,避免把所有适配都误判成“再调几个 dp”。

复习检查题 ​

  1. 一张 480×480 的位图放 drawable-mdpi,在 xxhdpi(3x)手机上解码后内存是原图的几倍?

    答:9 倍。解码尺寸 = 480×(480/160) = 1440px,宽高各放大 3 倍,内存按面积放大 3² = 9 倍;且只有 480px 细节,放大显示会模糊。

  2. 为什么 iOS 不使用 Android 式 density 桶,而 Android 需要?

    答:iOS 设备密度离散(@1x/@2x/@3x),用“点(point)”做逻辑单位并按 scale 精确匹配资源,不按 dpi 桶缩放位图;但仍需多倍率切图。Android 设备密度连续,必须用桶 + inDensity/inTargetDensity 解码缩放来保证 dp 物理尺寸一致。

  3. 平板(1024dp)用 375 基准法适配会出什么问题?正确做法是什么?

    答:缩放系数 s ≈ 1024/375 ≈ 2.73,整个 UI 被放大近 3 倍,图标巨大、布局稀疏。正确做法是按断点(compact <600 / medium 600~840 / expanded >840)切换布局(两栏 / 多列),而不是纯比例放大。

  4. 为什么字体缩放和安全区也属于屏幕适配?

    答:因为真实用户看到的是“完整页面”,不是单纯尺寸换算结果。只要字体放大后炸行、内容被刘海/手势区/键盘遮住,用户就会认为“这个页面没适配好”。

  5. 你会怎么快速区分这是“资源问题”“布局问题”还是“字体/系统边界问题”?

    答:先问它影响的是图像清晰度还是页面结构还是文本可读性。清晰度/内存先看 01,栏位/导航/断点先看 02,字体炸行/安全区/键盘/分屏先看 03。

一页速记 ​

三层:资源与密度 → 窗口与断点 → 系统边界与验证。

四条原则:逻辑单位;大屏换结构;文本跟系统;看当前窗口与 Insets。

三个专题:01 资源 · 02 断点 · 03 边界。

  • 换算:px = dp × (dpi/160);1080p ≈ 360dp 逻辑宽。
  • 放图:图标 → 矢量 / xxhdpi 单桶;背景大图 → nodpi + 降采样。
  • 低桶是坑:内存按 (设备dpi/桶dpi)² 放大,且模糊。
  • 断点:600 / 840dp,三端统一;375 只配小屏。
  • 真实适配:不仅看宽高,还看字体、点击区、安全区、键盘、折叠态和验证矩阵。

站点构建时间:2026/8/24 23:43:17