这篇学习笔记想记的不是"把页面拉宽以后左右排一排"这么简单的事,而是我这次做折叠屏适配时,对窗口尺寸、断点、布局模式和状态保持这几个问题重新梳理了一遍。

一、我一开始把折叠屏适配想简单了
第一次做这类题目时,我最容易犯的一个错误,就是把折叠屏适配理解成"页面变宽了"。
如果只是从视觉上看,好像确实是这样:
- 直板机是单列;
- 展开以后空间更大;
- 把卡片多摆几个、把详情区域露出来,好像就完了。
但真正做起来,我发现麻烦根本不止在宽度上,而在于状态变化。
因为折叠屏项目里,页面不是一次性确定形态的。它会随着设备状态和窗口尺寸持续变化:
- 折叠态可能是紧凑布局;
- 展开态可能进入双栏布局;
- 横竖屏切换后,信息密度又要重新分配;
- 页面如果带列表选中项,还要考虑切换前后的状态保持。
也就是说,折叠屏适配真正难的地方,不是写一个更大的页面,而是:页面能不能跟着窗口状态稳定响应。
所以这次我没有把重点放在"把界面做大",而是从三个问题入手:
- 怎么识别当前窗口属于哪种尺寸等级;
- 什么时机切单列,什么时机切双栏;
- 视图模式切换以后,页面状态怎么尽量不断层。
二、我先做了一个最朴素的单列版本,让结构立起来
适配这件事我后来有一个体会:不要一上来就同时做两三种布局。
因为一旦基础页面结构还没理顺,就急着做多形态切换,最后往往是每个模式都能跑,但每个模式都不顺。
所以我先做的是一个窄屏单列版本:
- 顶部标题与搜索;
- 分类 Tab;
- 内容卡片流;
- 列表项可点击进入详情。
先把单列版本跑顺,页面结构才有一个稳定起点。这个阶段的界面大概就是下面这样:

这个页面看上去很普通,但它对后面的适配很重要。因为后面无论变成双栏还是展开态,本质上都是在回答一个问题:哪些内容应该保留在主列表区域,哪些内容应该进入详情区域。
如果单列结构一开始就没拆清楚,后面双栏只会更乱。
三、进入适配阶段后,真正重要的不是布局组件,而是"状态判定"
当基础页面有了以后,接下来最关键的事情,并不是马上写分栏,而是先定义判定规则。
我这次最后收敛出三个核心状态:
sizeClass:当前窗口尺寸等级;breakpoint:当前命中的断点;paneMode:页面最终应该采用单列还是双栏。
只有这三个状态稳定,页面切换才会稳定。
我当时先写了一层窗口变化监听,让页面知道自己什么时候该重新判断:
arkts
import { window } from '@kit.ArkUI'
@State sizeClass: string = 'COMPACT'
@State breakpoint: string = 'SM'
@State paneMode: 'single' | 'dual' = 'single'
aboutToAppear() {
window.getLastWindow(getContext(), (err, win) => {
if (!err && win) {
this.updateWindowState(win)
win.on('windowSizeChange', () => {
this.updateWindowState(win)
})
}
})
}
这里我最在意的是"监听"而不是"读一次值"。
因为折叠屏适配最怕的就是初始化正确,但状态变化以后页面没有及时跟上。你第一次进入页面可能是单列,展开设备以后如果监听链没接住,界面就会停留在一个过时状态里。
监听有了以后,下一步才是根据窗口尺寸做分类。
四、断点不是装饰,它决定了页面到底按什么规则组织
我后来越来越觉得,所谓自适应布局,真正有用的不是"看起来会变",而是"背后有明确规则"。
所以我没有把页面切换写成一堆散落的 if else,而是尽量把它压成一段清楚的状态计算。
arkts
updateWindowState(win: window.Window) {
const rect = win.getWindowProperties().windowRect
const width = rect.width
if (width >= 840) {
this.sizeClass = 'EXPANDED'
this.breakpoint = 'LG'
this.paneMode = 'dual'
} else if (width >= 600) {
this.sizeClass = 'MEDIUM'
this.breakpoint = 'MD'
this.paneMode = 'single'
} else {
this.sizeClass = 'COMPACT'
this.breakpoint = 'SM'
this.paneMode = 'single'
}
}
这段代码看起来非常朴素,但它把整个适配问题做了两次拆分。
1. 先判断窗口属于什么等级
这一层解决的是"物理条件"。窗口到底是紧凑、中等还是展开,是后面一切布局决策的起点。
2. 再决定页面该切到什么布局模式
这里我没有直接把"宽度大就双栏"写死到 UI 层,而是落成了 paneMode。这样做的好处是,后面如果某个页面即使很宽也仍然要保持单列,只要改决策层,不用满页面去翻布局代码。
到这一步我才真正理解了一件事:折叠屏适配不是页面自己长大,而是页面被一层窗口状态逻辑驱动着变化。
五、真正联调时,我更多是在 DevEco 里看"状态变化有没有接住"
这种项目到了联调阶段,UI 漂不漂亮反而不是我最先看的。因为页面看起来对,不代表状态链真的对。
我这次调试时,最主要就是盯下面几件事:
- 模拟器切换折叠 / 展开以后,日志有没有正确打出尺寸变化;
- 当前断点有没有跟着变化;
- 页面模式是不是按预期从单列切到双栏;
- 当前选中的列表项,在切换后有没有丢。
整个开发现场基本就是这样:

这张图对我来说,最重要的不是编辑器本身,而是它把几个关键观察点放在了一起:
- 左边项目结构是不是按页面、组件、视图模型拆开了;
- 中间代码有没有把状态逻辑收口;
- 右边模拟器是不是已经进入了双栏阅读工作台形态;
- 底部日志有没有把折叠态、展开态、断点切换这些节点打出来。
如果这几个点能对应上,说明适配不是"看上去对",而是"状态和界面真的统一了"。
六、真正进入展开态以后,双栏不是目的,信息层级才是目的
当窗口状态稳定以后,下一步才轮到布局真正落地。
这一步我给自己定的原则很明确:双栏不是为了把空间占满,而是为了让信息层级更合理。
也就是说,展开态不是把单列页面粗暴拆成左右两半,而是要重新分配信息密度:
- 左边保留导航、列表、筛选;
- 右边承接详情、预览、操作;
- 同时让"当前选中的内容"足够明确。
所以我后来把页面主干拆成了两部分:
arkts
if (this.paneMode === 'dual') {
Row() {
ListPane({ items: this.articles, currentIndex: this.currentIndex })
.layoutWeight(1)
DetailPane({ article: this.articles[this.currentIndex] })
.layoutWeight(2)
}
} else {
ListPage({ items: this.articles })
}
写成这样以后,我觉得整个思路一下清楚了不少。
因为页面终于不是在"挤一个更宽的单列",而是在根据状态明确决定:
- 当前是列表页;
- 还是列表 + 详情工作台。
这个差别其实非常关键。它决定了页面在展开态下,用户获得的不是"更大的卡片",而是"更高的信息吞吐量"。
七、做完以后我专门补了一页"适配详情",因为我想看清楚页面到底处在什么状态
做适配这类学习项目,有一个很现实的问题:很多时候页面确实在变,但你不一定能一眼判断它到底为什么这样变。
所以这次我专门补了一页"适配详情",把页面当前的状态做了结构化展示,比如:
- 当前宽度等级;
- 命中的 breakpoint;
- 当前窗口状态;
- 当前布局模式;
- 当前方向;
- 自适应设置项。
这样做以后,我对整个项目的把握一下子清晰很多。这个诊断页大概就是下面这种感觉:

我觉得这张图特别适合作为学习笔记里的"验收页"。
因为它把以前隐身在代码里的东西,全都翻到了页面上:
- 为什么现在是双栏;
- 为什么现在属于 Expanded;
- 当前是不是展开态;
- 哪些调试项已经打开。
对于适配类文章来说,这种页面其实非常有价值。它让"适配成功"不再只是肉眼判断,而是变成了一个可验证的状态集合。
八、这次学习里,我最后真正记住的是"布局跟着状态走"
回头看这次学习,我觉得最重要的收获不是会写一个双栏页面,而是理解了这条逻辑顺序:
- 先有窗口变化;
- 再有尺寸等级与断点判断;
- 然后才有布局模式切换;
- 最后才是组件如何摆放。
很多时候我们会本能地先做第 4 步,也就是先摆界面。结果一旦状态变了,页面就开始失控。
但如果先把前面三层收稳,布局这件事反而会容易很多。
对我自己来说,这次项目至少让我把下面几个判断记牢了:
- 折叠屏适配不是简单放大页面;
- 断点策略应该独立于页面细节;
- 布局模式最好显式建模,不要散落在各个组件里;
- 调试页和日志对适配项目特别重要;
- 页面状态保持,比"看上去切对了"更重要。
九、本文小记
如果用一句话总结这次学习,我会写成:折叠屏适配不是布局技巧题,而是窗口状态响应题。
页面宽了只是现象,真正决定页面怎么变的,是窗口尺寸、断点和状态之间那层关系。
做完这一轮以后,我对 ArkUI 自适应布局的理解也比之前扎实很多了。以前我更关注"有没有合适的布局容器",现在我会先问:
- 当前页面的主信息流是什么;
- 哪些区域可以在展开态下拆出去;
- 哪些状态必须跨形态保持一致;
- 诊断与调试信息有没有留够。
后面如果继续往下做,我会优先补两块:
- 一块是更复杂的三段式布局,比如列表、详情、辅助信息并存的工作台结构;
- 一块是更稳定的状态恢复,比如切形态以后保留滚动位置、选中项和筛选条件。
这一篇先把第一阶段的学习过程记到这里。至少到这一步,我已经不再把折叠屏适配理解成"页面拉宽",而是理解成"页面跟着窗口状态做结构级响应"。