
起因做折叠屏适配我照惯例先去搜别人的做法。搜出来的文章长得都差不多判断折叠态、配个断点、贴一段media结束。然后我打开了 SDK 的声明文件。那些文章讲的大概只覆盖了真实能力的三分之一。更麻烦的是其中有几处是错的。一、FoldStatus 是 10 个状态不是 3 个文件ets/api/ohos.display.d.tsenum FoldStatus在第 708 行。网上讲折叠屏的文章状态判断基本都写成三个分支。而 SDK 里的枚举是这样FOLD_STATUS_UNKNOWN0FOLD_STATUS_EXPANDED1FOLD_STATUS_FOLDED2FOLD_STATUS_HALF_FOLDED3FOLD_STATUS_EXPANDED_WITH_SECOND_EXPANDED11FOLD_STATUS_FOLDED_WITH_SECOND_EXPANDED12FOLD_STATUS_HALF_FOLDED_WITH_SECOND_EXPANDED13FOLD_STATUS_EXPANDED_WITH_SECOND_HALF_FOLDED21FOLD_STATUS_FOLDED_WITH_SECOND_HALF_FOLDED22FOLD_STATUS_HALF_FOLDED_WITH_SECOND_HALF_FOLDED23后六个带_WITH_SECOND_的是双折轴设备的状态也就是三折叠。SDK 注释交代了两件事。一只有单折轴的设备只可能处于EXPANDED、FOLDED、HALF_FOLDED——所以你以前写三个分支在单折轴设备上是对的。二双折轴设备的铰链在充电口朝下时从右到左依次称为第一折轴和第二折轴。这个方向定义是所有状态命名的基准。数字里有一套编码规则把双折轴的语义按值排开规律就露出来了。值 1FOLD_STATUS_EXPANDED第一折轴全开第二折轴折叠值 2FOLD_STATUS_FOLDED两个折轴都折叠值 3FOLD_STATUS_HALF_FOLDED第一折轴半折第二折轴折叠值 11EXPANDED_WITH_SECOND_EXPANDED两个折轴都全开值 12FOLDED_WITH_SECOND_EXPANDED第一折轴折叠第二折轴全开值 13HALF_FOLDED_WITH_SECOND_EXPANDED第一折轴半折第二折轴全开值 21EXPANDED_WITH_SECOND_HALF_FOLDED第一折轴全开第二折轴半折值 22FOLDED_WITH_SECOND_HALF_FOLDED第一折轴折叠第二折轴完全折叠注释与命名不一致见下文值 23HALF_FOLDED_WITH_SECOND_HALF_FOLDED两个折轴都半折个位表示第一折轴十位表示第二折轴。个位取值 1 全开、2 折叠、3 半折十位留空表示第二轴折叠十位为 1 表示第二轴全开十位为 2 表示第二轴半折。拿12验一下个位 2 是第一轴折叠十位 1 是第二轴全开读作第一轴折叠、第二轴全开——和 SDK 注释一模一样。13、21、23也都对得上。会真出错的地方三屏全开的状态是11不是1。在双折轴设备上FOLD_STATUS_EXPANDED1的语义是第一折轴全开、第二折轴仍然折叠也就是只展开两屏。真正的三屏全开是11。于是这段看起来很正常的代码会出问题if(statusdisplay.FoldStatus.FOLD_STATUS_EXPANDED){// 以为是“全展开”其实是“只展开第一轴”}else{// 三屏全开的 11 会掉进这里}反过来FOLD_STATUS_EXPANDED_WITH_SECOND_HALF_FOLDED21表示第一轴全开、第二轴半折这是三折叠特有的一种折起一角的形态很多布局没考虑它。只处理1/2/3至少会漏掉11和21两种展开态。有一处注释和命名对不上FOLD_STATUS_FOLDED_WITH_SECOND_HALF_FOLDED 22名字直译是第一轴折叠、第二轴半折。但 SDK 注释写的是第一折轴折叠第二折轴完全折叠。按上面那套编码规则22的十位是 2应该对应第二轴半折跟名字一致、跟注释冲突。我倾向这是文档笔误但我手上没有三折叠真机验不了铰链形态所以不下结论。你要是在真机上测到这个值以实测为准。二、还有三个东西被忽略得更彻底折痕区域第 425 行functiongetCurrentFoldCreaseRegion():FoldCreaseRegion;FoldCreaseRegion第 974 行只有两个字段readonlydisplayId:number;readonlycreaseRects:ArrayRect;creaseRects是数组。单折轴设备只有一条折痕双折轴有两条——又是一处用数组形状来兼容两种设备的例子。它的用途很实际折痕是内容禁区按钮压上去体验就毁了。但折痕位置会随显示模式和屏幕方向变化写死坐标不行得实时取。函数名里的Current就是在强调这点。显示模式枚举第 814 行enum FoldDisplayModeFOLD_DISPLAY_MODE_UNKNOWN0FOLD_DISPLAY_MODE_FULL1FOLD_DISPLAY_MODE_MAIN2FOLD_DISPLAY_MODE_SUB3FOLD_DISPLAY_MODE_COORDINATION注释区分了两类设备。内屏外屏都能当主屏的机型大折叠、阔折叠内屏是FULL、外屏是MAIN外屏只作辅助显示的机型小折叠内屏是MAIN、外屏是SUB。这个区分比拿屏幕宽度去猜可靠——宽度是连续量显示模式是离散状态不会因为分屏、悬浮窗误判。至于COORDINATION注释没给出足够的行为说明我不猜它的语义。折展角度事件三个监听事件里第三个几乎没人提functionon(type:foldStatusChange,callback:CallbackFoldStatus):void;// 246 行functionon(type:foldAngleChange,callback:CallbackArraynumber):void;// 283 行functionon(type:foldDisplayModeChange,callback:CallbackFoldDisplayMode):void;// 397 行foldAngleChange的回调参数是Arraynumber数组。注释说得很直白双折轴设备的数组包含两个角度第一个值是第一折轴的折叠角度第二个值是第二折轴的。foldStatusChange只告诉你状态跳变了foldAngleChange给你实时角度。做折到某个角度触发某件事这类交互后者才是唯一的入口。三、断点官方给的阈值就摆在那儿这是我这次翻 SDK 收获最大的一段。网上教鸿蒙多设备适配的文章讲断点几乎都是同一套自己写一个BreakpointSystem类用媒体查询听宽度再if (width 840)手撕分支。鸿蒙有现成的断点 API而且阈值就写在 SDK 里。文件ets/component/enums.d.tsenum WidthBreakpoint在第 4741 行。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真实边界是600 和 840 vp而完整档位有五个。但这里有个前提很少有人说第 4732 行的注释明确写着这些只是典型设备的默认阈值设备厂商可以自定义。这句话的含义是不要把 840 硬编码进你的布局逻辑。用枚举值判断或者用下面这两个函数拿当前实例的断点// ets/api/ohos.arkui.UIContext.d.tsgetWindowWidthBreakpoint():WidthBreakpoint;// 5269 行getWindowHeightBreakpoint():HeightBreakpoint;// 5282 行getWindowWidthBreakpoint()是按窗口宽度的vp 值算的。而getWindowHeightBreakpoint()的注释写着它按窗口宽高比算不是按高度值。这一点挺关键。折叠屏展开后常见的形态是接近正方形这时候窄高的手机布局会很难看。用宽高比判断比用高度阈值稳。高度断点的阈值同样在第 4807 行。HEIGHT_SM值 0窗口宽高比小于 0.8HEIGHT_MD值 1窗口宽高比大于等于 0.8小于 1.2HEIGHT_LG值 2窗口宽高比大于等于 1.2窗口断点不等于容器断点还有一个更少人知道的东西。kit.ArkUI的导出清单里藏着ContainerReader和BreakpointOptions// ets/api/ohos.arkui.components.ContainerReader.d.tsinterfaceBreakpointOptions{widthBreakpoint?:WidthBreakpoint;heightBreakpoint?:HeightBreakpoint;}breakpointConfig(value?:BreakpointOptions):ContainerReaderAttribute;// 159 行UIContext那套是窗口级的ContainerReader是容器级的。区别在实际项目里很要命一多布局里左边栏占了 300 vp 之后右侧内容区剩下的宽度和窗口宽度是两码事。你拿窗口断点去控制右侧容器平板横屏下必错。四、折叠 PC 的跨屏ets/api/ohos.window.d.ts第 7406 行maximize(presentation?:MaximizePresentation,acrossDisplay?:boolean):Promisevoid;acrossDisplay只对具备折叠能力的 2in1 设备生效。注释说传true表示窗口可以直接进入跨屏模式并在设备半折叠状态下保持跨屏且只支持主窗口。折叠 PC 半折时屏幕分成上下两半窗口怎么摆本来是产品决策SDK 把它做成了显式参数。五、一条给所有人提个醒写这篇之前我搜过资料搜到过这样的代码import{displayFoldStatus}fromohos.app.ability.common;这是编的。我这套 SDK 的 kits 目录里没有SceneKit也没有任何叫displayFoldStatus的导出。折叠相关能力全在ohos.display里这个模块的末尾一行第 1703 行写着export default display;。这类文章现在很多特征很明显接口名看着像那么回事导入路径也顺但一编译就报找不到模块。照着抄先卡编译再卡排查时间就没了。判断方法只有一个打开你本地 SDK 的.d.ts文件看。DevEco 安装目录/sdk/default/openharmony/ets/api/ DevEco 安装目录/sdk/default/openharmony/ets/component/这两个目录是你手上最权威、最不会骗人的文档离线、可搜索、和你装的 SDK 版本严格对应。网上任何二手信息都不如它。顺带说一句ets/component/这个目录容易被人忽略——断点枚举就在那儿不在api/里。写在最后这篇没有跑通截图因为我手上只有模拟器给不了铰链角度这类物理输入。这里只做一件事把 SDK 里折叠相关的声明完整摊开。看完你会发现折叠屏适配的难点从来不是判断是不是折叠屏而是十个状态你覆盖了几个。线上的问题多半出在没想到的那七个上。另外我把这篇文章的核对过程反过来用了一遍写完之后我又回 SDK 对了一次原文结果改掉了自己两处错误——一处是把FOLD_STATUS_EXPANDED_WITH_SECOND_EXPANDED的语义记反了另一处是折痕函数名写成了不存在的getLiveCreaseRegion真名是getCurrentFoldCreaseRegion。凭记忆写 API谁都会错。这也算是这篇文章最想说的那句话。