我读了 HarmonyOS 的断点声明:有 5 个档位,高度断点算的其实是宽高比(附 SDK 行号对照)
我读了 HarmonyOS 的断点声明:有 5 个档位,高度断点算的其实是宽高比(附 SDK 行号对照)
适用版本:HarmonyOS 7.0.0(API 26)。文中所有结论核对自本机 DevEco Studio 随附 SDK 的 .d.ts 文件,文件名和行号都写在文中。
起因
做多设备适配,我先搜了一圈别人的做法。断点这块,搜出来的文章几乎都是同一个套路:自己写一个 BreakpointSystem 类,用媒体查询监听宽度,然后 if (width >= 840) { ... } 手撕分支。
看多了会以为,鸿蒙的断点就是这么回事:三个档位,一个 840。
我把 SDK 打开翻了一下。事实不是这样。
一、官方断点有 5 个档位,不是 3 个
文件:ets/component/enums.d.ts,第 4741 行:
declare enum WidthBreakpoint {
WIDTH_XS = 0, // 第 4751 行
WIDTH_SM = 1, // 第 4761 行
WIDTH_MD = 2, // 第 4771 行
WIDTH_LG = 3, // 第 4781 行
WIDTH_XL = 4 // 第 4791 行
}
档位和阈值对应如下。
WIDTH_XS(0):窗口宽度小于 320 vpWIDTH_SM(1):大于等于 320 vp,小于 600 vpWIDTH_MD(2):大于等于 600 vp,小于 840 vpWIDTH_LG(3):大于等于 840 vp,小于 1440 vpWIDTH_XL(4):大于等于 1440 vp
所以那三个口口相传的 sm、md、lg,只是中间三档。两头还各有一档:320 vp 以下的 XS,和 1440 vp 以上的 XL。
这个差别不是学术问题。手机竖屏基本都落在 SM(320 到 600 vp),折叠屏展开和平板横屏常常落在 LG(840 到 1440 vp),而平板模拟器的宽度可以到 1440 vp,正好踩在 XL 的门槛上。你要是只写了三档,XL 那条分支就是空的。
注意 WIDTH_XS 的取值是 0。用 if (!breakpoint) 这种写法判断,XS 会被当成"没取到值",这是个容易埋雷的地方。
二、高度断点算的是宽高比,不是高度
这是我觉得最容易被搞错的一处。同一个文件,第 4807 行:
declare enum HeightBreakpoint {
HEIGHT_SM = 0, // 第 4817 行
HEIGHT_MD = 1, // 第 4827 行
HEIGHT_LG = 2 // 第 4837 行
}
只有三档。但关键在注释的措辞。三档的说明原文分别是:
HEIGHT_SM:The window aspect ratio is less than 0.8HEIGHT_MD:The window aspect ratio is greater than or equal to 0.8 and less than 1.2HEIGHT_LG:The window aspect ratio is greater than or equal to 1.2
三处写的都是 aspect ratio,宽高比,不是高度值。
第 4797 行的段落注释把这件事说得更清楚,大意是:下表列出的是典型设备的默认宽高比阈值,供基于窗口宽高比的响应式布局参考。
这个设计有它的道理。折叠屏展开之后,屏幕常常接近正方形;同一个"高度值",在 16:9 的手机和 1:1 的折叠屏上,意味着完全不同的视觉感受。用宽高比判断,判断的是"屏幕形状",比判断绝对高度更贴近布局的真实需求。
但如果你按字面理解成"高度断点就是看高度",写出来的逻辑会完全错。**它是拿宽除以高。**比如一个 1440 x 960 的窗口,宽高比是 1.5,落在 HEIGHT_LG。
三、阈值只是默认值,厂商可以改
这是我认为比档位数量更重要的一条。第 4731 到 4733 行,WidthBreakpoint 的段落注释原文:
The following table lists default width breakpoint thresholds for typical devices, serving as a reference for responsive layout design based on window width breakpoints. Device manufacturers may customize these thresholds through product-specific configurations when needed.
翻译过来:下面列出的是典型设备的默认阈值,作为参考;设备厂商在需要时可以通过产品级配置自定义这些阈值。
HeightBreakpoint 也有同样一句,在第 4798 行。
所以那三个被反复引用的数字(320、600、840),性质是参考默认值,不是恒定契约。
这句话直接否掉了两种常见写法:
- 硬编码
840。厂商改了阈值,你的布局逻辑就跟系统的断点判断脱节了。 - 把 sm/md/lg 当成跨项目通用的常量。它取决于设备实现。
正确做法是判断枚举值,而不是判断数字。
四、窗口级断点:两个函数加一个事件
ets/api/@ohos.arkui.UIContext.d.ts 第 5269 行和第 5282 行:
getWindowWidthBreakpoint(): WidthBreakpoint;
getWindowHeightBreakpoint(): HeightBreakpoint;
第 5259 行的注释说明了前者的计算依据:按窗口宽度的 vp 值。第 5272 行注释说明后者按窗口宽高比。这跟上一节是同一件事,两处互相印证。
获取变化用事件,第 2142 行:
on(type: 'windowSizeLayoutBreakpointChange',
callback: Callback<observer.WindowSizeLayoutBreakpointInfo>): void;
回调对象在 ets/api/@ohos.arkui.observer.d.ts 第 730 行:
export class WindowSizeLayoutBreakpointInfo {
readonly widthBreakpoint: WidthBreakpoint; // 第 740 行
readonly heightBreakpoint: HeightBreakpoint; // 第 750 行
}
一个事件同时给你宽和高的断点,不用分别注册两个监听。注销用第 2158 行的 off。
五、容器级断点:API 26 才出现的那套方案
上面那些都是窗口级断点。它们回答的问题是"当前窗口有多宽"。
但在一多布局里,这个问题常常不够用。左边栏固定占掉 300 vp 之后,右侧内容区能用的宽度,跟窗口宽度是两回事。窗口 1000 vp 的平板上,右栏实际只有 700 vp。你拿窗口断点去控制右侧容器,判断必然偏大。
SDK 里有一套专门解决这个问题的 API,在 ets/api/@ohos.arkui.components.ContainerReader.d.ts:
export interface ContainerReaderInfo {
size: Size; // 第 43 行,必填
widthBreakpoint?: WidthBreakpoint; // 第 55 行
heightBreakpoint?: HeightBreakpoint; // 第 67 行
}
export interface ContainerReaderInterface {
(value: ContainerReaderInfo): ContainerReaderAttribute; // 第 132 行
}
export declare class ContainerReaderAttribute extends CommonMethod<ContainerReaderAttribute> {
breakpointConfig(value?: BreakpointOptions): ContainerReaderAttribute; // 第 159 行
}
export declare const ContainerReader: ContainerReaderInterface; // 第 173 行
export declare const ContainerReaderInstance: ContainerReaderAttribute; // 第 186 行
第 163 行的注释写的是它的定位:一个分析容器尺寸、为响应式布局提供断点信息的组件。
这里有个必须注意的版本门槛。这个文件里每一个声明的 @since 都是 26.0.0,包括 ContainerReaderInfo、BreakpointOptions、ContainerReaderAttribute、ContainerReader 本身。
对比一下,WidthBreakpoint 和 HeightBreakpoint 两个枚举的 @since 是 13。
也就是说:**断点枚举在 API 13 就有了,容器级断点方案是 API 26 才加进来的。**你要在低版本上用这套东西,编译期就会找不到。
ContainerReaderInfo 里的 size: Size 是必填项,注释说是"用于布局分析的目标容器尺寸",作为断点计算和布局适配的参考尺寸。也就是说尺寸由你传进去,而不是组件自己去测量。
六、容器级还能自定义阈值数组
breakpointConfig 接收的 BreakpointOptions,定义在第 80 行:
export interface BreakpointOptions {
width?: Array<number>; // 第 92 行
height?: Array<number>; // 第 104 行
}
两个字段都是数组。
第 150 行的参数注释写得很直接:
@param { BreakpointOptions } [value] - An array of breakpoint values in vp
这里允许你传入自己定义的阈值数组,而不是只能接受默认档位。
这件事和第三节是呼应的。系统默认阈值是"典型设备的参考值",厂商可以改;而当你要在自己的页面里按业务需要划定断点(例如把内容区的分界定在 500 vp 而不是 600 vp),breakpointConfig 就是官方给的自定义入口。
这是我认为整个断点体系里最值得记住的一点:阈值是可配置的,不是常量。
七、一个能自己算的例子
手边只有平板模拟器,型号规格是 2880 x 1920,320 dpi。
320 dpi 意味着 1 vp = 2 px(320 除以基准 160)。换算成 vp:
- 宽:2880 除以 2 = 1440 vp
- 高:1920 除以 2 = 960 vp
对照默认阈值:
- 宽度 1440 vp,正好达到
WIDTH_XL的下界,落在WIDTH_XL - 宽高比 2880 除以 1920 = 1.5,大于等于 1.2,落在
HEIGHT_LG
所以这个平板在默认配置下,应该报 WIDTH_XL 加 HEIGHT_LG。
需要说明的是,**这是按设备规格做的换算,我没有在模拟器里跑代码验证。**以 1440 vp 正好压线这一点来看,它是个很适合验证边界行为的设备。等你手上有能跑工程的环境,把 getWindowWidthBreakpoint() 的返回值打出来,就能确认。
几处和常见说法不一样的地方
整理一下这篇里几个和常见说法不一样的地方。
- 宽度断点有 5 档,不是 3 档,两头还有
XS和XL - 高度断点判断的是宽高比,不是高度值,而且只有 3 档
- 阈值是默认值,厂商可自定义,不要硬编码数字
- 容器级断点方案
ContainerReader是 API 26 新增的,和 API 13 就存在的断点枚举不是一批东西 breakpointConfig允许你传入自己的阈值数组
如果你之前写的是"自己实现一套 BreakpointSystem 监听宽度",那你实现的其实是官方已经提供、而且考虑得更细的东西。差别在于官方的这套知道"容器宽度不等于窗口宽度",也知道阈值可能被厂商改过。
判断响应式布局,先想清楚一件事:**你要判断的是窗口,还是容器?**这两个问题的答案,在 SDK 里对应两套完全不同的 API。
更多推荐


所有评论(0)