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

一、我一开始把折叠屏适配想简单了

第一次做这类题目时,我最容易犯的一个错误,就是把折叠屏适配理解成“页面变宽了”。

如果只是从视觉上看,好像确实是这样:

  • 直板机是单列;
  • 展开以后空间更大;
  • 把卡片多摆几个、把详情区域露出来,好像就完了。

但真正做起来,我发现麻烦根本不止在宽度上,而在于状态变化。

因为折叠屏项目里,页面不是一次性确定形态的。它会随着设备状态和窗口尺寸持续变化:

  • 折叠态可能是紧凑布局;
  • 展开态可能进入双栏布局;
  • 横竖屏切换后,信息密度又要重新分配;
  • 页面如果带列表选中项,还要考虑切换前后的状态保持。

也就是说,折叠屏适配真正难的地方,不是写一个更大的页面,而是:页面能不能跟着窗口状态稳定响应。

所以这次我没有把重点放在“把界面做大”,而是从三个问题入手:

  1. 怎么识别当前窗口属于哪种尺寸等级;
  2. 什么时机切单列,什么时机切双栏;
  3. 视图模式切换以后,页面状态怎么尽量不断层。

二、我先做了一个最朴素的单列版本,让结构立起来

适配这件事我后来有一个体会:不要一上来就同时做两三种布局。

因为一旦基础页面结构还没理顺,就急着做多形态切换,最后往往是每个模式都能跑,但每个模式都不顺。

所以我先做的是一个窄屏单列版本:

  • 顶部标题与搜索;
  • 分类 Tab;
  • 内容卡片流;
  • 列表项可点击进入详情。

先把单列版本跑顺,页面结构才有一个稳定起点。这个阶段的界面大概就是下面这样:

这个页面看上去很普通,但它对后面的适配很重要。因为后面无论变成双栏还是展开态,本质上都是在回答一个问题:哪些内容应该保留在主列表区域,哪些内容应该进入详情区域。

如果单列结构一开始就没拆清楚,后面双栏只会更乱。

三、进入适配阶段后,真正重要的不是布局组件,而是“状态判定”

当基础页面有了以后,接下来最关键的事情,并不是马上写分栏,而是先定义判定规则。

我这次最后收敛出三个核心状态:

  • sizeClass:当前窗口尺寸等级;
  • breakpoint:当前命中的断点;
  • paneMode:页面最终应该采用单列还是双栏。

只有这三个状态稳定,页面切换才会稳定。

我当时先写了一层窗口变化监听,让页面知道自己什么时候该重新判断:

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,而是尽量把它压成一段清楚的状态计算。

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 漂不漂亮反而不是我最先看的。因为页面看起来对,不代表状态链真的对。

我这次调试时,最主要就是盯下面几件事:

  • 模拟器切换折叠 / 展开以后,日志有没有正确打出尺寸变化;
  • 当前断点有没有跟着变化;
  • 页面模式是不是按预期从单列切到双栏;
  • 当前选中的列表项,在切换后有没有丢。

整个开发现场基本就是这样:

这张图对我来说,最重要的不是编辑器本身,而是它把几个关键观察点放在了一起:

  • 左边项目结构是不是按页面、组件、视图模型拆开了;
  • 中间代码有没有把状态逻辑收口;
  • 右边模拟器是不是已经进入了双栏阅读工作台形态;
  • 底部日志有没有把折叠态、展开态、断点切换这些节点打出来。

如果这几个点能对应上,说明适配不是“看上去对”,而是“状态和界面真的统一了”。

六、真正进入展开态以后,双栏不是目的,信息层级才是目的

当窗口状态稳定以后,下一步才轮到布局真正落地。

这一步我给自己定的原则很明确:双栏不是为了把空间占满,而是为了让信息层级更合理。

也就是说,展开态不是把单列页面粗暴拆成左右两半,而是要重新分配信息密度:

  • 左边保留导航、列表、筛选;
  • 右边承接详情、预览、操作;
  • 同时让“当前选中的内容”足够明确。

所以我后来把页面主干拆成了两部分:

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;
  • 当前是不是展开态;
  • 哪些调试项已经打开。

对于适配类文章来说,这种页面其实非常有价值。它让“适配成功”不再只是肉眼判断,而是变成了一个可验证的状态集合。

八、这次学习里,我最后真正记住的是“布局跟着状态走”

回头看这次学习,我觉得最重要的收获不是会写一个双栏页面,而是理解了这条逻辑顺序:

  1. 先有窗口变化;
  2. 再有尺寸等级与断点判断;
  3. 然后才有布局模式切换;
  4. 最后才是组件如何摆放。

很多时候我们会本能地先做第 4 步,也就是先摆界面。结果一旦状态变了,页面就开始失控。

但如果先把前面三层收稳,布局这件事反而会容易很多。

对我自己来说,这次项目至少让我把下面几个判断记牢了:

  • 折叠屏适配不是简单放大页面;
  • 断点策略应该独立于页面细节;
  • 布局模式最好显式建模,不要散落在各个组件里;
  • 调试页和日志对适配项目特别重要;
  • 页面状态保持,比“看上去切对了”更重要。

九、本文小记

如果用一句话总结这次学习,我会写成:折叠屏适配不是布局技巧题,而是窗口状态响应题。

页面宽了只是现象,真正决定页面怎么变的,是窗口尺寸、断点和状态之间那层关系。

做完这一轮以后,我对 ArkUI 自适应布局的理解也比之前扎实很多了。以前我更关注“有没有合适的布局容器”,现在我会先问:

  • 当前页面的主信息流是什么;
  • 哪些区域可以在展开态下拆出去;
  • 哪些状态必须跨形态保持一致;
  • 诊断与调试信息有没有留够。

后面如果继续往下做,我会优先补两块:

  • 一块是更复杂的三段式布局,比如列表、详情、辅助信息并存的工作台结构;
  • 一块是更稳定的状态恢复,比如切形态以后保留滚动位置、选中项和筛选条件。

这一篇先把第一阶段的学习过程记到这里。至少到这一步,我已经不再把折叠屏适配理解成“页面拉宽”,而是理解成“页面跟着窗口状态做结构级响应”。

Logo

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

更多推荐