HarmonyOS 7 + ArkUI + Adaptive Layout 学习笔记:折叠屏多形态布局适配与窗口状态响应机制【鸿蒙心迹】

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 顶部标题与搜索;
  • 分类 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;
  • 当前是不是展开态;
  • 哪些调试项已经打开。

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

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

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

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

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

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

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

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

九、本文小记

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

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

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

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

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

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

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

相关推荐
路漫漫其修远兮sjw1 小时前
Linux 运维知识点笔记(中级)
linux·运维·笔记
kyrie_sakura1 小时前
大模型学习6 -- 深度学习 1 概述和PyTorch
pytorch·深度学习·学习
贾伟康1 小时前
【HarmonyOS 7新能力|067】LTPO可变帧率实战:让动画流畅而静态页面省电
harmonyos·arkts·低功耗·ltpo·可变帧率
LucianaiB1 小时前
华为云码道 AI 编程实战:我用 CodeArts 智能体打造了一款 HarmonyOS 游戏化专注应用「智办 ZhiBan」
人工智能·ai·华为云·harmonyos·codearts·材料
宵时待雨1 小时前
linux笔记归纳24:多路转接select
linux·服务器·笔记·高并发
一只旭宝2 小时前
【C++复习】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)
c++·redis·笔记·mysql·设计模式
91刘仁德2 小时前
RAG实战-Embedding模型(BGE)
笔记·embedding·rag
传奇开心果编程2 小时前
【Flutter入门练中学】第3课:滚动与列表
android·学习·flutter·ui·ios
李游Leo2 小时前
HarmonyOS 7 + ArkTS + Image Kit 学习笔记:图像超分处理链路与 PixelMap 数据流实践【鸿蒙心迹】
笔记·学习·harmonyos