本文基于 HarmonyOS 6.1.0 Release 官方 Codelab《基于一多能力实现天气应用》整理编写。文中代码为本人自写的完整示例(非官方示例搬运),API 名称、枚举取值与版本号来自华为官方文档;未在真机逐行验证的部分,请以真机实测为准。

引子:一份代码真能喂饱五个端吗
上周V哥想做一个能跑在手机、折叠屏、平板、电脑、手表上的天气应用。第一反应是:写五套界面?那不成五倍工作量了。
后来翻到一个官方 Codelab,叫《基于一多能力实现天气应用》。跟着它做完,V哥搞清楚了一件事:一多不是"写一份 UI 跑五个端",是"一套工程 + 分层架构 + 断点驱动"。 业务逻辑只写一遍,界面差异交给分层和断点去消化。
这篇文章不重复官方章节顺序,V哥把实操路径拆成七步:先拆"一多"这句话,再讲三层怎么分,然后死磕断点,最后给一个最小示例和一份真机校准清单。
一、先把"一多"这句话拆开
官方对一多的概括是"一套代码工程,一次开发上架,多端按需部署"(Codelab《基于一多能力实现天气应用》)。
但这句话容易被读歪成"写一份界面到处跑"。V哥做完天气应用才明白,它实际是两件事拧在一起:
- 业务统一:看天气、管城市这套逻辑,五端必须一致,不能各写一套;
- 界面差异:手机单列、平板多列、电脑带侧边栏、手表独立翻页------这些差异要能被"收口",不能污染业务逻辑。
解决这对矛盾的钥匙,就是下面要说的分层架构。把"不变的"和"变的"分开,一多就成了。
反过来说更扎心:不分层会怎样?手机写一套天气页、平板再抄一套、电脑又抄一套、手表再写一套------四套界面各自演化,三个月后"看天气"的逻辑就悄悄分叉了:手机显示晴,平板显示多云。这种漂移,靠人肉 review 根本拦不住。分层就是给"业务只此一份"上一道硬保险。
二、三层架构怎么落:V哥的分层三问
官方 Codelab 把工程按 products / features / common 三层组织。但新手最容易犯的错是:代码随手一放,结果手机要改、平板也得跟着改。
V哥给自己定了三个问题,拿到一段代码先问:

| 代码类型 | 归属层 | 官方职责 | V哥的口诀(放哪一层) |
|---|---|---|---|
| 设备专属页面、入口 Ability(EntryAbility / PcAbility / WearableAbility) | products | 存放不同设备的独立界面与交互逻辑 | 随变进 P:会因设备而改,就进产品层 |
| 可复用的核心业务(天气视图、天气逻辑) | features | 多设备复用,保证核心逻辑统一 | 可复用进 F:每端都要用,就进特性层 |
| 通用常量、数据模型、公共组件、工具类 | common | 与业务无关,给上层提供基础支持 | 通用进 C:谁都可能用,就进公共层 |
三个问题连起来就是V哥的判定口诀:
这代码会不会因为设备而改?会 → P。每端都要用吗?是 → F。谁都可能用吗?是 → C。
天气应用里,所有"看天气"的视图(WeatherForecastView、WeekWeatherView 等)都进 features 层,三个产品入口统一调用,保证每端的天气业务一模一样;常量、模型、日志、窗口工具进 common 层。差异全锁在 products 层,业务层干干净净。
三、断点是唯一抓手:先定断点,再谈布局
分层解决了"代码放哪",断点解决"界面怎么变"。
官方把窗口宽度按断点划分区间(响应式布局最佳实践),推荐值如下:
| 断点 | 窗口宽度(vp) | 典型设备形态 |
|---|---|---|
WIDTH_XS |
< 320 | 智能穿戴 |
WIDTH_SM |
[320, 600) | 手机竖屏 |
WIDTH_MD |
[600, 840) | 折叠屏展开 / 小平板 |
WIDTH_LG |
[840, 1440) | 平板 / 电脑 |
WIDTH_XL |
≥ 1440 | 大屏 |
这个枚举在 @kit.ArkUI 里叫 WidthBreakpoint,通过 getUIContext().getWindowWidthBreakpoint() 拿到当前值。
V哥踩过的最大的坑,是对着机型写布局 。折叠屏合上是手机、展开是平板,同一台设备横竖一转就换了断点。官方也明确:断点面向窗口而非设备类型(响应式布局)。
所以V哥的第一句铁律:
看断点,别看机型。机型只是障眼法,宽度才是真相。
断点值不是凭空来的。实践中V哥在页面里用一个轻量工具类监听窗口宽度,再写回 @State:
typescript
// common/WidthBreakpointType.ets
import { WidthBreakpoint } from '@kit.ArkUI';
class BpUtil {
// 取当前窗口断点枚举
static current(): WidthBreakpoint {
return this.getUIContext().getWindowWidthBreakpoint();
}
}
aboutToAppear 里读一次初值,窗口尺寸变化(折叠、分屏、拖窗口)时在回调里更新。关键提醒:折叠屏展开/合上只触发回调、不重建组件,所以布局切换逻辑必须放在回调里,不能只在初始化时算一次。先看窗口落在哪个断点,再决定单列还是多列、侧边栏悬浮还是嵌入。这一步没想清楚,后面全是补丁。
四、最小示例:一个 build 吃完五端
把断点接进页面。下面是V哥自写的天气主页最小示例,演示"同一份代码随断点变版式":
typescript
// pages/WeatherHome.ets
import { WidthBreakpoint, SideBarContainerType } from '@kit.ArkUI';
@Component
struct WeatherHome {
// 当前宽度断点,由上层监听窗口宽度写入(WidthBreakpoint 枚举)
@State bp: WidthBreakpoint = WidthBreakpoint.WIDTH_SM;
// 已添加的城市列表
private cities: string[] = ['北京', '上海', '深圳'];
build() {
// 窄屏用悬浮侧边栏,宽屏用嵌入侧边栏
SideBarContainer(
this.bp === WidthBreakpoint.WIDTH_SM
? SideBarContainerType.Overlay // 悬浮:覆盖在内容之上,需收起
: SideBarContainerType.Embed // 嵌入:常驻在内容区旁
) {
// 侧边栏:管理城市
CityList({ onPick: (c: string) => this.switchCity(c) })
// 内容区:多城市用 Swiper 横向滑动切换
Swiper() {
ForEach(this.cities, (city: string) => {
CityWeatherPage({ city: city })
})
}
.loop(true) // 首尾循环滑动
.indicator(false) // 窄屏隐藏指示点,宽屏按需开启
}
}
// 切换当前展示城市
private switchCity(city: string): void {
// 实际项目中在此驱动内容区 Swiper 的 index
}
}
内容区里的天气信息,再用 GridRow / GridCol 控制列数:窄屏 span 占满,宽屏占一半或三分之一。这样"一屏一列、大屏多列"就自动来了。
五、SideBarContainer:窄屏悬浮、宽屏嵌入
上面示例里最关键的一行就是 SideBarContainer 的构造参数。SideBarContainerType 有两个值,行为完全不同:
Embed:嵌入------侧边栏常驻在内容区旁边,宽屏下不占额外层;Overlay:悬浮------侧边栏盖在内容之上,窄屏下默认收起,点按钮才展开。
Codelab 的做法是:窗口处于 WIDTH_SM 时给 Overlay,其余断点给 Embed(Codelab 完整示例)。这个切法很顺:手机屏幕窄,侧边栏常驻会挤掉内容,所以让它悬浮收起;平板电脑屏幕宽,侧边栏常驻反而更顺手。
V哥加的一条经验:别让 SideBarContainer 单独背锅。 它只管"侧边栏怎么挂",列数还要靠 GridRow/GridCol 配合。断点一变,两处要一起变,少一处版式就错位。
如果页面还要"列表 + 详情"的双栏导航,可以换成 Navigation:它的 mode 在窄屏走单栏、宽屏走双栏(导航栏与内容区分栏显示)。SideBarContainer + Navigation 组合还能做三分栏------侧边栏、导航栏、内容区三层并排。这也是官方组件布局场景里点名的响应式组件之一(组件布局场景最佳实践)。选 SideBarContainer 还是 Navigation,看你要的是"抽屉"还是"导航分栏"。
六、三个 hap 入口:default / pc / wearable
界面差异收口到 products 层后,交付形态是多个 hap 包。天气应用建了三个产品入口模块:

| 入口模块 | hap 名称 | 安装目标 |
|---|---|---|
products/default |
multiweatherdefaultsample |
手机、平板 |
products/pc |
multiweatherpcsample |
电脑 |
products/wearable |
multiweatherwearablesample |
穿戴设备 |
features 层的天气模块和 common 层模块以 HAR 形式被三个入口统一调用------这就是"业务一份、界面多端"在工程上的落点。
这里有个容易忽略的点:手表屏幕和手机差太大,不能把手机 UI 缩小塞进去 。穿戴入口要做独立的页面序列(授权页 → 首页 → 上下滑动看预报),放进 wearable 模块单独写。源码见 GitCode 仓库。
七、真机断点校准清单
多设备真机差异大,模拟器跑通不等于真机 OK。V哥把上线前要核的对列成清单,建议贴进 PR:
-
WidthBreakpoint取值区间记住了吗?SM是 [320,600),不是"手机专属" - 断点切换逻辑写在回调里了吗?折叠屏展开/合上只触发回调,不重建组件
- 折叠屏合上(SM)侧边栏是 Overlay 吗?展开(MD+)是 Embed 吗?
- 内容区列数随断点变了吗?GridRow
columns/ GridColspan配齐了吗? - 手表入口独立写了吗?没把手机 UI 直接缩小吧?
- 三个 hap 包分别装到对应设备验证了吗?还是只跑了一个入口?
- 横屏/分屏/自由窗口测了吗?同一设备不同窗口形态可能落不同断点
- Swiper 的
indicator在不同断点表现一致吗?窄屏关、宽屏开的对吗? - 用
getWindowWidthBreakpoint()打日志,确认真机断点和你预期一致吗?
注:折叠屏、电脑、穿戴的真机表现以真机实测为准,本文未在真机逐行验证。
参考与出处
本文涉及的事实性信息(API 名称、枚举取值、版本号、官方约束)来自以下官方文档,文中的结构、代码示例、决策流程与自检清单为本人整理编写:
- Codelab:基于一多能力实现天气应用(更新 2026-08-21,适用 HarmonyOS 6.1.0 Release)
- 源码仓库 GitCode:HarmonyOS_Codelabs/MultiWeather
- 组件布局场景(响应式组件:SideBarContainer / Navigation / Swiper / GridRow-GridCol)
- 响应式布局最佳实践(断点定义 xs/sm/md/lg/xl)
- 响应式布局(断点面向窗口而非设备类型)
- WidthBreakpoint 枚举说明(WIDTH_XS/SM/MD/LG/XL 取值)
- 分层架构设计最佳实践(common/features/products)
最后一句 :一多真正难的不是某个组件------组件就那几行。难的是先想清楚分层 (代码放哪)和断点(界面怎么变)。这两步想明白,一份工程就能老老实实喂饱五个端。