HarmonyOS 折叠屏适配实战:双栏与单栏自适应布局(FoldStatus 监听 + Navigation)

📅 2026/7/27 4:07:06
HarmonyOS 折叠屏适配实战:双栏与单栏自适应布局(FoldStatus 监听 + Navigation)
前言折叠屏是 HarmonyOS 生态的重要设备形态。展开时屏幕宽度接近平板适合双栏布局左列表 右详情折叠时退化为手机形态需要切换为单栏。本文用displayAPI 监听折叠状态配合Navigation实现真正自适应的折叠屏布局。运行效果初始状态展开态双栏布局切换至折叠态单栏布局核心概念折叠状态display.FoldStatus值推荐布局展开 EXPANDED2双栏左侧列表 右侧详情半折 HALF_FOLDED3上半屏内容 下半屏操作折叠 FOLDED1单栏全屏列表点击跳转详情完整示例代码interface ArticleItem { id: number icon: string title: string summary: string tag: string readTime: string } Entry Component struct Index { // 模拟折叠状态expanded | folded | halfFolded State foldState: string expanded State selectedArticleId: number 1 private articles: ArticleItem[] [ { id: 1, icon: , tag: 布局, title: Stack 层叠布局与 Overlay 浮层详解, summary: 深入理解 alignContent 九宫格、zIndex 层叠顺序与 overlay 实战..., readTime: 8分钟 }, { id: 2, icon: , tag: 列表, title: List 高性能长列表与分组吸顶, summary: 虚拟化渲染原理、ListItemGroup 分组、sticky 吸顶完整实现..., readTime: 10分钟 }, { id: 3, icon: , tag: 轮播, title: Swiper 轮播图与页面滑动指南, summary: DotIndicator 自定义、SwiperController 编程翻页、卡片流效果..., readTime: 7分钟 }, { id: 4, icon: , tag: 导航, title: Tabs 标签页底部导航到顶部分类, summary: 底部导航栏、嵌套 Tabs、自定义 TabBar 样式完整示例..., readTime: 9分钟 }, { id: 5, icon: , tag: 侧栏, title: SideBarContainer 侧边导航实战, summary: Embed vs Overlay 模式、autoHide 自适应、自定义汉堡菜单..., readTime: 11分钟 }, ] private getSelectedArticle(): ArticleItem { for (let i 0; i this.articles.length; i) { if (this.articles[i].id this.selectedArticleId) { return this.articles[i] } } return this.articles[0] } Builder foldStateSelector() { Column({ space: 12 }) { Text(模拟折叠状态) .fontSize(13) .fontColor(#888) .width(100%) .textAlign(TextAlign.Center) Row({ space: 8 }) { Button(展开).height(36).fontSize(13) .backgroundColor(this.foldState expanded ? #0066ff : #e0e0e0) .fontColor(this.foldState expanded ? #fff : #333) .onClick(() { this.foldState expanded }) Button(半折).height(36).fontSize(13) .backgroundColor(this.foldState halfFolded ? #ff8800 : #e0e0e0) .fontColor(this.foldState halfFolded ? #fff : #333) .onClick(() { this.foldState halfFolded }) Button(折叠).height(36).fontSize(13) .backgroundColor(this.foldState folded ? #cc3300 : #e0e0e0) .fontColor(this.foldState folded ? #fff : #333) .onClick(() { this.foldState folded }) } .justifyContent(FlexAlign.Center) Text( this.foldState expanded ? 展开态双栏布局左列表 右详情 : this.foldState halfFolded ? 半折态上内容 下操作区 : 折叠态单栏仅显示列表 ) .fontSize(12) .fontColor( this.foldState expanded ? #0066ff : this.foldState halfFolded ? #ff8800 : #cc3300 ) .textAlign(TextAlign.Center) } .width(100%) .padding({ top: 12, bottom: 12, left: 16, right: 16 }) .backgroundColor( this.foldState expanded ? #f0f5ff : this.foldState halfFolded ? #fff8f0 : #fff0f0 ) .border({ width: { bottom: 1 }, color: #f0f0f0 }) } Builder articleList() { Column({ space: 0 }) { Row() { Text( 文章列表).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#1a1a1a) Text(this.articles.length.toString() 篇).fontSize(13).fontColor(#888) } .width(100%) .height(48) .padding({ left: 16, right: 16 }) .border({ width: { bottom: 1 }, color: #eeeeee }) ForEach(this.articles, (article: ArticleItem) { Row() { Text(article.icon).fontSize(20).width(36) Column({ space: 4 }) { Text(article.title) .fontSize(13) .fontColor(this.selectedArticleId article.id ? #0066ff : #222) .fontWeight(this.selectedArticleId article.id ? FontWeight.Bold : FontWeight.Normal) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row({ space: 6 }) { Text(article.tag) .fontSize(10).fontColor(#0066ff).backgroundColor(#f0f5ff) .padding({ left: 5, right: 5, top: 2, bottom: 2 }).borderRadius(4) Text(article.readTime).fontSize(10).fontColor(#aaa) } } .layoutWeight(1).alignItems(HorizontalAlign.Start).padding({ left: 8 }) } .width(100%) .padding({ left: 12, right: 12, top: 12, bottom: 12 }) .backgroundColor(this.selectedArticleId article.id ? #f0f5ff : #ffffff) .border({ width: { left: this.selectedArticleId article.id ? 3 : 0, bottom: 1 }, color: { left: #0066ff, bottom: #f5f5f5 } }) .onClick(() { this.selectedArticleId article.id }) }) } .width(100%) .height(100%) .backgroundColor(#ffffff) } // Builder 内不允许 let/const 变量声明用 this.getSelectedArticle() 直接调用 Builder articleDetailPanel() { Scroll() { Column({ space: 20 }) { Column({ space: 12 }) { Row({ space: 12 }) { Text(this.getSelectedArticle().icon).fontSize(36) Column({ space: 4 }) { Text(this.getSelectedArticle().tag) .fontSize(11).fontColor(#0066ff).backgroundColor(#f0f5ff) .padding({ left: 8, right: 8, top: 3, bottom: 3 }).borderRadius(10) Text(this.getSelectedArticle().readTime 阅读).fontSize(12).fontColor(#aaa) } .alignItems(HorizontalAlign.Start) } Text(this.getSelectedArticle().title) .fontSize(20).fontWeight(FontWeight.Bold).fontColor(#111).lineHeight(28) Text(this.getSelectedArticle().summary) .fontSize(14).fontColor(#666).lineHeight(22) } .width(100%).padding(20) .backgroundColor(#ffffff).borderRadius(12) .border({ width: 1, color: #f0f0f0 }) Column({ space: 12 }) { Text(正文内容).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333) Text(HarmonyOS 的折叠屏适配是现代应用开发的重要能力。通过监听屏幕折叠状态开发者可以动态调整布局策略为用户提供最佳的视觉体验。) .fontSize(14).fontColor(#555).lineHeight(22) Text(在展开状态下建议采用双栏布局左栏展示列表右栏展示详情充分利用大屏幕空间在折叠状态下切换为单栏布局保证内容可读性。) .fontSize(14).fontColor(#555).lineHeight(22) } .width(100%).padding(20) .backgroundColor(#ffffff).borderRadius(12) .border({ width: 1, color: #f0f0f0 }) .alignItems(HorizontalAlign.Start) // 真机 API 代码展示 Column({ space: 10 }) { Text(真机折叠状态检测 API).fontSize(14).fontWeight(FontWeight.Bold).fontColor(#333) Column({ space: 6 }) { Text(import display from ohos.display).fontSize(11).fontColor(#00aa55).fontFamily(monospace) Text().height(4) Text(let foldStatus display.getFoldStatus()).fontSize(11).fontColor(#0066ff).fontFamily(monospace) Text(// FoldStatus.FOLD_STATUS_EXPANDED 2).fontSize(11).fontColor(#666).fontFamily(monospace) Text(// FoldStatus.FOLD_STATUS_FOLDED 1).fontSize(11).fontColor(#666).fontFamily(monospace) Text().height(4) Text(display.on(foldStatusChange, (status) {).fontSize(11).fontColor(#0066ff).fontFamily(monospace) Text( // 动态切换布局).fontSize(11).fontColor(#666).fontFamily(monospace) Text(})).fontSize(11).fontColor(#0066ff).fontFamily(monospace) } .width(100%).padding(14).backgroundColor(#1a1a2e).borderRadius(10) .alignItems(HorizontalAlign.Start) } .width(100%).padding(20) .backgroundColor(#ffffff).borderRadius(12) .border({ width: 1, color: #f0f0f0 }) .alignItems(HorizontalAlign.Start) if (this.foldState halfFolded) { Column({ space: 8 }) { Text(⚠️ 半折状态提示).fontSize(14).fontWeight(FontWeight.Bold).fontColor(#cc6600) Text(建议将交互控件放置在下半屏将内容展示放置在上半屏模拟笔记本电脑使用模式。) .fontSize(13).fontColor(#664400).lineHeight(20) } .width(100%).padding(16) .backgroundColor(#fff8e8).borderRadius(12) .border({ width: 1, color: #ffcc66 }) .alignItems(HorizontalAlign.Start) } Column().height(20) } .width(100%).padding({ left: 16, right: 16, top: 16, bottom: 16 }) } .width(100%).height(100%).backgroundColor(#f5f5f5) } build() { Column({ space: 0 }) { // 顶部折叠状态选择器 this.foldStateSelector() // 主内容区根据折叠状态切换布局 if (this.foldState expanded) { // 展开态双栏布局 Row() { Column() { this.articleList() } .width(40%) .height(100%) .border({ width: { right: 1 }, color: #e8e8e8 }) Column() { this.articleDetailPanel() } .layoutWeight(1) .height(100%) } .width(100%) .layoutWeight(1) } else if (this.foldState folded) { // 折叠态单栏列表 Column() { this.articleList() } .width(100%) .layoutWeight(1) } else { // 半折态上下分屏 Column() { // 上半屏内容 Column() { this.articleDetailPanel() } .height(55%) .border({ width: { bottom: 2 }, color: #0066ff }) // 下半屏操作列表 Column() { this.articleList() } .height(45%) } .width(100%) .layoutWeight(1) } } .width(100%) .height(100%) .backgroundColor(#f5f5f5) } }关键知识点1. Builder 内不允许 let/const 声明这是 ArkTS 严格模式的重要限制// 错误Builder 内不能声明变量 Builder articleDetail() { let article this.getSelected() // 编译错误 Text(article.title) } // 正确直接调用方法或访问 State Builder articleDetail() { Text(this.getSelected().title) // 允许方法调用 Text(this.selectedTitle) // 允许访问 State }2. 真机获取折叠状态import display from ohos.display // 一次性获取 let status display.getFoldStatus() // FoldStatus.FOLD_STATUS_FOLDED 1 (折叠) // FoldStatus.FOLD_STATUS_EXPANDED 2 (展开) // FoldStatus.FOLD_STATUS_HALF_FOLDED 3 (半折) // 持续监听 display.on(foldStatusChange, (status: display.FoldStatus) { this.foldState status display.FoldStatus.FOLD_STATUS_EXPANDED ? expanded : folded })3. 三种布局对应三种状态折叠状态布局策略实现方式展开双栏列表 40% 详情 60%Row包裹两个Column折叠单栏全屏列表单独Column半折纵向分割上内容 下操作Column包裹两个固定高度Column4. Navigation 的 Auto 模式更简洁对于普通列表-详情场景NavigationMode.Auto自动根据屏幕宽度在 Stack手机和 Split平板/折叠屏展开态之间切换无需手动判断折叠状态Navigation(this.pathStack) .mode(NavigationMode.Auto) // 宽屏自动双栏窄屏自动单栏这是处理折叠屏最推荐的简洁方式。5. 半折态的 UX 建议半折时设备像一台笔记本折痕以上是“屏幕“折痕以下是“键盘区“。建议上半屏展示内容文章、图片、视频下半屏放置交互控件按钮、输入框、导航小结Builder内不允许let/const变量声明改为直接调用方法或引用State真机通过display.getFoldStatus()获取折叠状态display.on(foldStatusChange)持续监听三种折叠状态对应三种布局展开双栏、折叠单栏、半折上下分屏NavigationMode.Auto是处理折叠屏列表-详情场景的最简洁方案推荐优先使用模拟器无法真实模拟折叠屏但可以通过State foldState切换布局验证 UI 逻辑