引言

如果你同时使用手机和平板打开同一款应用,你可能会注意到:手机上是单列纵向滚动,平板上是双列或三列网格。这不是两套代码——这是同一套代码根据屏幕尺寸动态切换了布局模式。这种"同一套代码、多种屏幕表现"的能力,在移动端开发中称为"响应式布局"。

HarmonyOS NEXT 提供了 @ohos.mediaquery API 来实现响应式布局。它的工作原理类似于 Web 前端的 CSS Media Queries:定义一个条件(如屏幕宽度 ≥ 768vp),监听这个条件的变化(如设备旋转),当条件匹配时切换布局。

本文将通过构建一个"响应式布局适配器",深入讲解 mediaquery 的核心 API:matchMediaSyncMediaQueryListeneron('change') 事件监听、以及基于断点的自适应布局策略。

读完本文你将能够:

  • 使用 mediaquery.matchMediaSync() 检测屏幕方向和尺寸
  • 使用 on('change', callback) 监听设备状态变化
  • 设计断点策略:手机竖屏 / 手机横屏 / 平板三档
  • 使用 Grid.columnsTemplate 动态切换列数
  • 理解响应式布局与自适应布局的区别

CSS Media Queries 的 ArkUI 对应

如果你有 Web 开发经验,你一定用过 CSS Media Queries:

@media (min-width: 768px) {
  .container { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 767px) {
  .container { grid-template-columns: 1fr; }
}

ArkUI 的 @ohos.mediaquery 是这个概念的编程式对应。它不写在样式表中,而是写在 ArkTS 代码中——通过 JavaScript API 检测条件、注册监听、驱动状态变化。

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

// 创建媒体查询监听器
let mqListener = mediaquery.matchMediaSync('(min-width: 768vp)');

// 检查当前是否匹配
if (mqListener.matches) {
  // 平板布局
} else {
  // 手机布局
}

// 监听后续变化(如设备旋转、折叠屏展开)
mqListener.on('change', (result: mediaquery.MediaQueryResult) => {
  if (result.matches) {
    // 切换到平板布局
  }
});

这个编程式 API 的一个优势是:你可以在回调中执行任意的 ArkTS 逻辑——不仅仅是切换样式,还可以重新组织数据结构、切换导航模式、甚至加载不同的子组件。

核心 API 解析

matchMediaSync(condition: string): MediaQueryListener

matchMediaSync 接收一个条件字符串,返回一个 MediaQueryListener 对象。条件字符串的语法与 CSS Media Queries 基本一致,支持以下条件:

条件类型 示例 含义
宽度 (min-width: 768vp) 视口宽度 ≥ 768vp
宽度 (max-width: 599vp) 视口宽度 ≤ 599vp
宽度 (width: 360vp) 视口宽度 = 360vp
方向 (orientation: landscape) 横屏
方向 (orientation: portrait) 竖屏
设备类型 (device-type: tablet) 平板设备
设备类型 (device-type: phone) 手机设备
深色模式 (dark-mode: true) 系统深色模式

注意单位使用 vp(virtual pixel),这是 ArkUI 的标准长度单位,与 CSS 的 px 不同。

MediaQueryListener

MediaQueryListener 继承自 MediaQueryResult,增加了一个 on 方法用于注册变化监听:

  • matches: boolean — 当前条件是否匹配(只读)
  • media: string — 匹配条件的原始字符串(只读)
  • on(type: 'change', callback: Callback<MediaQueryResult>): void — 注册变化监听
  • off(type: 'change', callback?: Callback<MediaQueryResult>): void — 移除监听

当设备方向改变、折叠屏展开/折叠、窗口大小调整等事件发生时,on('change') 的回调会被触发,result.matches 反映了新的匹配状态。

生命周期管理

Listener 应该在 aboutToAppear() 中创建,在 aboutToDisappear() 中移除:

private mqListener: mediaquery.MediaQueryListener | null = null;

aboutToAppear(): void {
  this.mqListener = mediaquery.matchMediaSync('(orientation: landscape)');
  this.mqListener.on('change', this.onOrientationChange);
}

aboutToDisappear(): void {
  if (this.mqListener !== null) {
    this.mqListener.off('change', this.onOrientationChange);
  }
}

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

Demo 设计:三档断点策略

本文 Demo 定义了三档断点:

断点 条件 列数 布局模式
手机竖屏 width < 600vp 1 列 单列纵向
手机横屏 600vp ≤ width < 768vp 2 列 双列网格
平板 width ≥ 768vp 3 列 三列网格

断点值的选取参考了常见设备尺寸:

  • 手机竖屏约 320-400vp
  • 手机横屏约 600-700vp
  • 平板约 768vp+

Demo 提供两个入口来感知断点变化:

  1. 真实设备检测aboutToAppear() 中使用 matchMediaSync 检测实际屏幕尺寸
  2. 手动模拟切换:三个按钮允许用户在手机上手动切换到平板布局模式

手动模拟是必要的——因为在手机设备上,真实屏幕尺寸永远处于"手机竖屏"断点中。如果没有模拟器,用户无法在手机设备上看到平板布局的效果。

断点更新逻辑

updateBreakpoint(name: string): void {
  this.currentBreakpoint = name;
  if (name === '手机竖屏 (< 600vp)') {
    this.columns = 1;
    this.layoutMode = '单列';
  } else if (name === '手机横屏 (600-768vp)') {
    this.columns = 2;
    this.layoutMode = '双列';
  } else if (name === '平板 (> 768vp)') {
    this.columns = 3;
    this.layoutMode = '三列';
  }
}

columns 这个 @State 变量的变化会触发 Grid 组件的重新渲染,从而改变列数。

核心实现:自适应 Grid 布局

Demo 的内容区是一个包含 6 张卡片的 Grid,列数由 columns 状态决定:

Grid() {
  ForEach(this.cardData, (card: CardData) => {
    GridItem() {
      Column() {
        Text(card.title)
          .fontSize(15)
          .fontColor('#FFFFFF')
          .fontWeight(FontWeight.Bold)
        Text(card.desc)
          .fontSize(11)
          .fontColor('#FFFFFFCC')
      }
      .width('100%')
      .padding(16)
      .borderRadius(10)
      .backgroundColor(card.color)
    }
  }, (card: CardData) => card.title)
}
.columnsTemplate(this.getColumnsTemplate())
.columnsGap(10)
.rowsGap(10)

getColumnsTemplate() 根据当前 columns 值返回不同的模板字符串:

getColumnsTemplate(): string {
  if (this.columns === 1) {
    return '1fr';
  }
  if (this.columns === 2) {
    return '1fr 1fr';
  }
  return '1fr 1fr 1fr';
}

columnsTemplate 使用 CSS Grid 的 fr 单位——每列等分可用空间。1fr 1fr 表示两列等宽,1fr 1fr 1fr 表示三列等宽。

这种基于模板字符串的动态列数是 Grid 组件最灵活的用法——你不需要为每种布局写三套 Grid,只需要改变 columnsTemplate 的参数。

交互流程

Demo 提供三个主要交互:

  1. 真实设备信息展示:页面加载时自动检测设备方向和平板状态,显示当前断点和布局模式
  2. 手动断点切换:三个按钮(手机竖屏 / 手机横屏 / 平板)让用户手动切换,Grid 列数实时响应
  3. 方向变化监听:如果用户旋转设备,on('change') 回调自动检测并更新断点信息

设备信息面板显示四个状态:

Row() {
  this.infoBadge('当前断点', this.currentBreakpoint, '#1677FF')
  this.infoBadge('布局模式', this.layoutMode, '#D53F8C')
}
Row() {
  this.infoBadge('方向', this.isLandscape ? '横屏' : '竖屏', '#38A169')
  this.infoBadge('列数', this.columns.toString() + ' 列', '#ED8936')
}

这些信息让用户清楚看到:当前运行在哪个断点中、Grid 是如何响应的。

响应式布局 vs 自适应布局

虽然"响应式"和"自适应"常被混用,但它们有明确的区别:

维度 响应式布局(Responsive) 自适应布局(Adaptive)
布局变化 连续性变化(任意宽度都有合适表现) 离散性变化(几个固定断点)
实现方式 百分比、flex、相对单位 不同断点的固定布局
设计成本 高(需要保证所有宽度都好看) 中(只设计几个关键断点)
典型场景 Web 网站 移动端 App
ArkUI 方式 Flex + 百分比 + weight mediaquery 断点 + Grid

对于移动端 App 开发,自适应布局(Adaptive)通常是更务实的选择——你只需要针对三到四个关键断点设计布局即可,不需要像响应式网站那样覆盖从 320px 到 2560px 的所有宽度。

本文 Demo 采取的是自适应策略:定义三个断点,每个断点对应固定的列数。这符合绝大多数 App 的多设备适配需求。

@ohos.mediaquery 的局限性

理解 mediaquery 的局限性能帮助你做出更合理的技术决策:

  1. 只能检测,不能改变:mediaquery 告诉你当前屏幕是什么状态,但它不能改变屏幕尺寸。真正的布局变化需要你根据检测结果手动切换。

  2. 断点是全局的:mediaquery 检测的是整个应用窗口的尺寸,而不是某个组件的容器尺寸。如果你需要在特定容器内做响应式布局(如侧边栏内部的宽度变化),mediaquery 不适用,需要使用组件的 onAreaChange 回调。

  3. 不支持嵌套查询:与 CSS @media (min-width: 768px) and (orientation: landscape) 不同,ArkUI 的 mediaquery 单次调用只支持一个条件。如果需要组合条件,需要分别创建多个 listener。

  4. API 18 已标记废弃:在 API 18 中,matchMediaSync 被标记为 deprecated,推荐使用 ohos.arkui.UIContext.MediaQuery#matchMediaSync。不过 API 24 中旧 API 仍然可用。

完整页面结构

Column(根容器)
├── Header(深色标题栏:"响应式布局" + @ohos.mediaquery 标签)
├── Scroll
│   └── Column
│       ├── 设备信息面板(4 个 badges:断点、模式、方向、列数)
│       ├── 断点模拟器(3 个按钮:手机竖屏 / 手机横屏 / 平板)
│       ├── 自适应内容区(Grid,列数动态变化)
│       └── API 参考文字
└── 根容器结束

总结

本文通过构建一个"响应式布局适配器",深入讲解了 HarmonyOS ArkUI 中 @ohos.mediaquery 媒体查询 API 的核心用法:

  1. matchMediaSync:接收条件字符串,返回 MediaQueryListener,支持宽度、方向、设备类型、深色模式等条件
  2. MediaQueryListener:提供 matches(当前是否匹配)、on('change')(变化监听)、off('change')(移除监听)
  3. 三档断点策略:手机竖屏 1 列 / 手机横屏 2 列 / 平板 3 列
  4. Grid.columnsTemplate:通过 1fr 模板字符串动态切换列数
  5. 手动断点模拟:通过按钮切换覆盖真实设备检测结果,方便开发调试

响应式/自适应布局是"一次开发、多设备运行"的基础设施。在一个鸿蒙应用需要同时运行在手机、折叠屏、平板、车机上的时代,mediaquery 提供的设备感知能力是构建多端适配界面的第一步。

在某种意义上,mediaquery 是"设备与 UI 之间的翻译官"——它把硬件的物理属性(屏幕尺寸、方向)翻译成软件能理解的条件表达式。开发者只需要关心"匹配什么条件时用什么布局",而不需要关心"这个硬件的像素密度是多少、物理尺寸是多少"。


Logo

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

更多推荐