在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、前置思考

1.1 分辨率适配的本质:不是"像素",是"密度"

很多开发者以为分辨率适配就是"适配不同像素",于是写 px 硬编码、做等比缩放。但鸿蒙多端设备的差异是三维的

维度1: 屏幕物理尺寸(1.2寸手表 ~ 10寸平板 ~ 55寸智慧屏)
维度2: 屏幕密度(LCD 160dpi ~ OLED 640dpi+)
维度3: 屏幕形态(圆屏/方屏/折叠屏/异形屏/超宽屏)

同样是"宽 1080px",手表上是巨大按钮,平板上是蚂蚁文字——只适配像素必然翻车

1.2 传统适配的三宗罪

❌ 罪1: px 硬编码
   → 高密度屏文字变小,低密度屏布局溢出

❌ 罪2: 统一倍率缩放
   → 大屏拉伸变形,小屏挤压丢失

❌ 罪3: 无视安全区
   → 异形屏刘海/圆角裁掉内容,折叠屏折痕区错位

1.3 鸿蒙的答案

鸿蒙用 vp 虚拟像素(与密度无关的抽象单位)+ fp 字体像素 + GridRow 断点栅格 + SafeArea 安全区 + 比例适配构建了一套声明式分辨率适配体系

二、核心原理

2.1 vp / fp / px 的关系

px = vp × (dpi / 160)

  vp (virtual pixel): 虚拟像素,与设备密度无关
    → 160dpi 时 1vp = 1px,320dpi 时 1vp = 2px
    → 同样的 100vp 在任何设备上物理尺寸一致

  fp (font pixel): 字体像素,跟随系统字号设置
    → 用户调大系统字体,应用文字同步放大

  px (physical pixel): 物理像素,不要直接用于布局

结论:所有布局尺寸用 vp,所有文字尺寸用 fp,px 只用于位图资源。

2.2 断点栅格系统(GridRow)

断点 (breakpoint) 由容器宽度决定:

xs: < 320vp      手表 / 极窄屏
sm: 320 ~ 600vp  手机竖屏
md: 600 ~ 840vp  折叠屏展开 / 手机横屏
lg: 840 ~ 1200vp 平板
xl: ≥ 1200vp     智慧屏 / 车机

栅格列数: xs=4, sm=8, md=12, lg=12, xl=12

GridRow + GridCol 组件让布局自动按宽度重新排列,无需手动计算。

2.3 比例适配与极限容错

比例适配: 用百分比 / fraction / 权重 而非固定值
  width('50%') / layoutWeight(1) / aspectRatio(1)

极限容错: 应对"意外"尺寸
  → 折叠屏展开/合拢瞬间的中间态
  → 分屏后 300vp 的窄窗口
  → 车机 21:9 超宽屏
  → 系统字体调到 200% 后的极端布局

三、源码/API 深度解析

3.1 vp/fp 使用范式

@Entry
@ComponentV2
struct ResolutionDemo {
  build() {
    Column() {
      // vp: 布局尺寸(自适应密度)
      Row()
        .width('100%')
        .height(48)          // 48vp,任何设备物理高度一致
        .padding(12)         // 12vp
      // fp: 文字尺寸(跟随系统字号)
      Text('标题').fontSize(20)          // 20fp
      Text('正文').fontSize(14)          // 14fp
      // 比例: 自适应宽度
      Column()
        .width('60%')        // 60% 容器宽度
        .aspectRatio(1.0)    // 1:1 比例
    }
  }
}

3.2 GridRow 断点系统

@Entry
@ComponentV2
struct GridBpDemo {
  @Local currentBp: string = 'sm';

  build() {
    // 声明式断点: 不同断点不同列数
    GridRow({
      columns: { xs: 4, sm: 8, md: 12, lg: 12, xl: 12 },
      gutter: { xs: 8, sm: 12, md: 16 }
    }) {
      ForEach(this.items, (it: GridItemInfo) => {
        GridCol({
          span: { xs: 2, sm: 4, md: 6, lg: 4, xl: 3 }  // 每行卡片数随断点变化
        }) {
          this.renderCard(it)
        }
      }, (it: GridItemInfo) => it.id)
    }
    .onBreakpointChange((bp: string) => {
      this.currentBp = bp;
      LoggerUtil.info(TAG, '断点切换: ' + bp);
    })
  }
}

3.3 安全区适配(异形屏)

import { safeArea } from '@kit.ArkUI';

// 1. 安全区避开刘海/挖孔/圆角
Column() {
  Text('内容').fontSize(16)
}
.expandSafeArea([safeArea.SafeAreaType.SYSTEM], [safeArea.SafeAreaEdge.TOP, safeArea.SafeAreaEdge.BOTTOM])

// 2. 获取窗口安全区数值
const safeTop: number = safeArea.getSafeAreaTop();   // 状态栏/刘海高度
const safeBottom: number = safeArea.getSafeAreaBottom(); // 底部导航/圆角
const safeLeft: number = safeArea.getSafeAreaLeft();
const safeRight: number = safeArea.getSafeAreaRight();

// 3. 自定义偏移(避开挖孔)
Column()
  .margin({ top: safeTop + 12, left: safeLeft + 8, right: safeRight + 8 })

3.4 极限宽高容错

// 1. 文本超长截断(避免撑破布局)
Text(this.longText)
  .maxLines(2)
  .textOverflow({ overflow: TextOverflow.Ellipsis })

// 2. 容器最小/最大约束
Column()
  .constraintSize({ minWidth: 240, maxWidth: 1200, minHeight: 120 })

// 3. 折叠屏中间态监听
onAreaChange((oldArea: Area, newArea: Area) => {
  const w = newArea.width as number;
  // 宽度突变(折叠/展开瞬间)→ 平滑过渡布局
  this.animWidth = w;
})

// 4. 字号极端放大时的布局保护
if (this.textScale > 1.5) {
  // 字体放太大 → 切换为列表布局
  this.switchToListLayout();
}

四、企业级实战落地

4.1 分辨率适配清单

阶段 动作 验收
单位规范 全部 vp/fp,禁止 px 布局 代码扫描 0 处 px 布局
栅格布局 GridRow 断点重写主布局 5 断点均正确
安全区 全面接入 SafeArea 异形屏无遮挡
极限测试 窄窗口/大字号/折叠中间态 无崩溃无错位

4.2 完整示例:自适应商品页

@Entry
@ComponentV2
struct ProductPageDemo {
  @Local bp: string = 'sm';

  private cardSpan(): GridColColumnOption {
    // 不同断点: 每行卡片数不同
    switch (this.bp) {
      case 'xs': return { span: 1 };       // 单列
      case 'sm': return { span: 1 };       // 单列
      case 'md': return { span: 1 };       // 双列
      case 'lg': return { span: 1 };       // 三列
      default: return { span: 1 };         // 四列
    }
  }

  build() {
    Column() {
      // 顶部信息(全断点一致)
      Text('🛍️ 商品广场').fontSize(22).fontWeight(FontWeight.Bold)
      Text('当前断点: ' + this.bp.toUpperCase()).fontSize(11).fontColor('rgba(255,255,255,0.4)')

      GridRow({
        columns: { xs: 4, sm: 8, md: 12, lg: 12, xl: 12 },
        gutter: { xs: 8, sm: 12, md: 16 }
      }) {
        ForEach(this.products, (p: Product) => {
          GridCol({ span: { xs: 2, sm: 2, md: 3, lg: 3, xl: 3 } }) {
            Column({ space: 4 }) {
              Text(p.icon).fontSize(30)
              Text(p.name).fontSize(12).fontWeight(FontWeight.Bold).maxLines(1)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
              Text(p.price).fontSize(13).fontWeight(FontWeight.Bold).fontColor('#FFD54F')
            }
            .width('100%').padding(10)
            .backgroundColor('rgba(255,255,255,0.06)').borderRadius(10)
          }
        }, (p: Product) => p.id)
      }
      .onBreakpointChange((bp: string) => { this.bp = bp; })
    }
    .width('100%').height('100%').padding(12)
    .backgroundColor('#0D1B2A')
  }
}

4.3 各形态适配策略表

形态 分辨率策略 断点 布局
手表 1.2" 圆屏安全区 + 单列 xs 垂直列表
手机 6" 常规 vp sm 单列/双列
折叠屏展开 中间态平滑 md 双栏
平板 11" 三栏栅格 lg 列表+详情+工具
智慧屏 55" 超大间距 + 焦点导航 xl 多栏网格

五、问题排查与性能优化

问题 原因 解决
高密度屏文字变小 px 硬编码 全部改 vp/fp
大屏布局空白 固定宽度 百分比/栅格
异形屏内容被裁 未处理安全区 SafeArea 适配
折叠屏错位 未处理中间态 onAreaChange 平滑过渡
字体放大后溢出 未设 maxLines 文本截断 + 布局切换
横竖屏切换抖动 硬编码方向 断点系统自动重排

5.1 性能优化要点

1. 断点变化回调中不做重计算,只切布局标志
2. 大图按密度加载(@media 资源目录分密度)
3. 避免 onAreaChange 高频回调里的昂贵操作(节流)
4. 栅格嵌套层级控制在 3 层内,避免过度布局计算

六、高阶总结与最佳实践

  1. 单位即规范:vp 管布局、fp 管文字、px 只管位图——写进团队代码规范。
  2. 栅格即骨架:主布局一律 GridRow + 断点系统,不要手写媒体查询。
  3. 安全区即底线:异形屏适配不到位,再多设计都白搭。
  4. 容错即质量:窄窗口、大字号、折叠中间态都要测,这些是崩溃高发区。
  5. 比例优于固定:能用百分比/权重就不写死数值,让布局自己呼吸。

一句话记住:分辨率适配的真相是"适配密度而非像素"——vp/fp 保物理尺寸一致,栅格断点保布局自适应性,安全区保形态兼容,极限容错保极端场景不崩。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐