第一篇里,我先把“双栏研读台”的第一版双栏骨架搭了起来;第二篇继续顺着“连续浏览”这条线,把左侧目录、右侧详情和相关阅读的连续跳转组织成了一条真正的阅读链。到第三篇,我又遇到了一个更偏产品体验层的问题:如果左侧目录本来就承担“稳定导航”的职责,那右侧内容是不是不一定非要一直整页推进?

这篇我想解决的,就是大屏阅读里另一个非常典型的模式:左侧导航固定,右侧内容区按覆盖方式切换。 换句话说,左边这块始终像目录锚点一样稳定存在,右边则专注承载“我当前正在看什么”。相比第二篇的连续推挤,这一篇更强调“结构稳定”“切换明确”和“返回恢复”。

在这里插入图片描述

一、为什么第三篇要从“连续推进”转向“固定导航 + 覆盖切换”

第二篇写完以后,我专门拿平板连续读了十几篇文章。整体感觉没问题,但有个体验越来越明显:

  • 当左侧列表承担的是“文章入口”时,连续推挤很自然;
  • 但当左侧区域开始承担“分类导航、栏目切换、主入口定位”时,右侧如果还总是一路 push,就会显得有点重。

比如我在“双栏研读台”里,把左侧从单纯文章列表逐步改成下面这种结构:

全部文章
技术解读
设计思考
开发实践
行业观察
产品体验
开源生态

这时候,左侧已经不是“某一篇文章的列表”了,而更像一套稳定导航体系。

用户在左边点“开发实践”,右边出现这一类文章的列表;再点其中一篇文章,右边展示详情;点击返回时,右边应该回到刚才那一组列表状态,而不是把左边也跟着切掉。

我慢慢意识到,第三篇要解决的问题,其实和第二篇不一样:

第二篇的核心是“阅读链不断向前推进”;第三篇的核心是“导航区保持不动,内容区在右侧完成覆盖切换”。

HarmonyOS 当前对平行视界的典型场景描述里,除了左右推挤,还强调了导航固定 + 内容覆盖切换这一类模式。对于阅读、内容浏览、分类检索这类应用来说,这种组织方式往往更稳定,也更符合用户对大屏的直觉预期。

二、先把产品规则说清楚:左侧固定,右侧切换,返回只恢复右侧

第三篇开始以前,我先给“双栏研读台”定了三条明确规则:

规则 1:左侧导航不参与右侧详情的页面切换

也就是说,左边是左边,右边是右边。

无论我在右侧打开多少篇文章、切换多少次详情,左侧导航区域都应该保持原状:

  • 当前栏目还在;
  • 选中的导航项还在;
  • 用户对目录的定位感不能丢。

规则 2:右侧详情以“覆盖方式”打开,而不是替换整个双栏结构

这点非常关键。

如果把整个界面都当成一张页面,每次点文章详情都让整个路由往前走,就很容易把左侧导航一起卷进去。第三篇我更想要的是:

  • 左侧导航像一个固定底座;
  • 右侧内容区像一个可以被覆盖更新的工作区。

规则 3:返回只恢复右侧内容区状态

第三篇的返回不再是“整页退回”。

它更接近:

左侧固定导航不变
右侧从详情返回到列表
或者从新详情返回到旧详情

这种“局部返回”的感觉,是第三篇最核心的体验之一。

三、第三篇的项目目录开始显得更工程化了

因为要支持“固定导航 + 覆盖切换”,我把项目目录继续做了一次拆分。相比第二篇,这一版开始明确区分:导航结构、右侧内容区、覆盖路由、阅读状态。

DualReadDemo
├─ entry
│  ├─ src/main/ets
│  │  ├─ pages
│  │  │  ├─ Index.ets
│  │  │  ├─ ArticleListPage.ets
│  │  │  ├─ ArticleDetailPage.ets
│  │  │  └─ CategoryHomePage.ets
│  │  ├─ components
│  │  │  ├─ FixedNavLayout.ets
│  │  │  ├─ ArticleListItem.ets
│  │  │  ├─ RelatedArticleCard.ets
│  │  │  └─ TagChip.ets
│  │  ├─ route
│  │  │  ├─ OverlayRouteService.ets
│  │  │  ├─ RouteHistoryStore.ets
│  │  │  └─ ReadingSessionStore.ets
│  │  ├─ model
│  │  │  ├─ ArticleItem.ets
│  │  │  └─ NavItem.ets
│  │  ├─ mock
│  │  │  └─ MockData.ets
│  │  └─ common
│  │     └─ Logger.ets
│  └─ resources
└─ module.json5

这一版目录里,我最看重三个文件:

  • FixedNavLayout.ets:左侧固定导航;
  • OverlayRouteService.ets:右侧覆盖切换的路由组织;
  • ReadingSessionStore.ets:记录“我从哪个导航入口来、当前正在读哪篇”。

有了这三个角色以后,整个大屏结构才真正稳下来。

四、Index.ets 的职责变了:它不只是分栏,而是总装配台

第一篇的 Index.ets 更像一个起点页;第二篇里它负责把 NavPathStack 和 RouteService 串起来;第三篇里,它开始承担“总装配”的角色。

我希望它同时表达三层东西:

  1. 左侧导航是固定区;
  2. 右侧主体是内容区;
  3. 内容区之上还可以叠加覆盖层。

所以 Index.ets 里,我会把整体结构组织成:

import { router } from '@kit.ArkUI'
import { FixedNavLayout } from '../components/FixedNavLayout'
import { ArticleListPage } from './ArticleListPage'
import { ArticleDetailPage } from './ArticleDetailPage'
import { RouteContext } from '../route/RouteContext'

@Entry
@Component
struct Index {
  @State private navPathStack: NavPathStack = new NavPathStack()
  @State private selectedNavId: string = 'all'
  @State private overlayVisible: boolean = false
  @State private overlayArticleId: string = ''

  private routeContext: RouteContext = new RouteContext()

  build() {
    Navigation(this.navPathStack) {
      Row() {
        FixedNavLayout({
          selectedId: this.selectedNavId,
          onSelect: (id: string) => {
            this.selectedNavId = id
            this.onNavSelect(id)
          }
        })
          .width('280vp')
          .backgroundColor('#F7F8FA')

        Stack() {
          ArticleListPage({
            navId: this.selectedNavId,
            onOpenArticle: (articleId: string) => {
              this.openOverlayArticle(articleId)
            }
          })

          if (this.overlayVisible) {
            ArticleDetailPage({
              articleId: this.overlayArticleId,
              onClose: () => this.closeOverlay(),
              onOpenRelated: (id: string) => {
                this.openOverlayArticle(id)
              }
            })
          }
        }
        .layoutWeight(1)
      }
      .width('100%')
      .height('100%')
    }
    .title('双栏研读台')
  }
}

和第二篇相比,这里的核心变化不是 Navigation 本身,而是右侧内容区从单一“详情页容器”变成了一个 Stack:

  • 底层是当前栏目下的文章列表;
  • 上层在需要时叠加文章详情覆盖层。

这一步做完以后,“固定导航 + 覆盖切换”就从想法变成真正可落地的页面结构了。

在这里插入图片描述

五、固定导航不要贪多,它的职责只有一个:稳定用户方向感

第三篇里,我刻意把 FixedNavLayout 做得很克制。

因为左侧固定导航不是为了塞很多功能,而是为了持续给用户一个“我现在在哪个栏目里”的方向感。

它承担的职责只有三个:

  • 展示导航项;
  • 记录当前选中项;
  • 把选中变化通知给右侧内容区。

代码结构我大致写成这样:

@Component
export struct FixedNavLayout {
  @Prop selectedId: string = 'all'
  onSelect: (id: string) => void = () => {}

  private navItems: NavItem[] = [
    { id: 'all', title: '全部文章', icon: $r('app.media.ic_all') },
    { id: 'tech', title: '技术解读', icon: $r('app.media.ic_tech') },
    { id: 'design', title: '设计思考', icon: $r('app.media.ic_design') },
    { id: 'practice', title: '开发实践', icon: $r('app.media.ic_practice') },
    { id: 'industry', title: '行业观察', icon: $r('app.media.ic_industry') }
  ]

  build() {
    Column() {
      this.buildHeader()
      List({ space: 6 }) {
        ForEach(this.navItems, (item: NavItem) => {
          ListItem() {
            Row({ space: 12 }) {
              Image(item.icon).width(18).height(18)
              Text(item.title).fontSize(16)
            }
            .width('100%')
            .padding({ left: 16, right: 16, top: 12, bottom: 12 })
            .borderRadius(12)
            .backgroundColor(this.selectedId === item.id ? '#EAF3FF' : '#FFFFFF')
          }
          .onClick(() => this.onSelect(item.id))
        }, (item: NavItem) => item.id)
      }
      .layoutWeight(1)
    }
    .padding(16)
    .height('100%')
  }
}

写到这里时,我最在意的一点是:左侧导航不能因为右侧文章不断切换就闪来闪去。

它应该像书架、目录、索引一样,稳定存在。

六、右侧覆盖切换的关键,不是“能弹出来”,而是“能解释状态”

很多人一提覆盖层,第一反应是 Dialog、半屏页、浮层、抽屉。但第三篇这里的“覆盖切换”其实更接近:

右侧内容区维持主工作面,文章详情在这个工作面之上以覆盖方式展开。

也就是说,它不是一个随便弹一下的轻提示,而是一个真正承担阅读任务的主内容层。

为了把这个关系说清楚,我专门做了一个 OverlayRouteService,把右侧覆盖层的打开、关闭、层级和恢复,都收口在一起。

@Singleton
export class OverlayRouteService {
  private historyStore: RouteHistoryStore = RouteHistoryStore.getInstance()
  private rootArticleId: string = ''
  private currentArticleId: string = ''
  private overlayDepth: number = 0

  public pushOverlay(articleId: string, source: string = 'related') {
    if (this.overlayDepth === 0) {
      this.rootArticleId = this.currentArticleId || articleId
    }

    this.overlayDepth++
    this.currentArticleId = articleId

    this.historyStore.push({
      id: articleId,
      source: source,
      overlayDepth: this.overlayDepth,
      timestamp: Date.now()
    })

    Router.pushUrl({
      url: `/pages/ArticleDetailPage?articleId=${articleId}&overlay=1`
    })

    console.info(`[Overlay] push article=${articleId} depth=${this.overlayDepth}`)
  }

  public popOverlay(): void {
    if (this.overlayDepth > 0) {
      this.overlayDepth--
      this.historyStore.pop()
      console.info(`[Overlay] pop, depth=${this.overlayDepth}`)

      if (this.overlayDepth === 0) {
        this.restoreReadingContext()
      } else {
        Router.back()
      }
    }
  }
}

这段代码的重要性在于,它明确区分了两件事:

  • 当前是不是还处于右侧覆盖层阅读状态;
  • 退回时到底该恢复哪一层内容。

在这里插入图片描述

七、第三篇真正的重点:返回以后,右侧要恢复到“刚才的样子”

第三篇里,如果用户点击一篇文章,再从文章里点相关阅读,然后按返回,最糟糕的情况不是回不去,而是回去以后状态不对。

比如:

  • 回到了错误的文章;
  • 回到了列表,但滚动位置丢了;
  • 左侧导航突然跳回默认栏目;
  • 右侧本来应该是当前栏目下的文章列表,却变成了另一个页面。

所以我给 ReadingSessionStore 增加了几个关键状态:

export class ReadingSessionStore {
  private navId: string = 'all'
  private listScrollOffset: number = 0
  private currentArticleId: string = ''
  private overlayDepth: number = 0

  updateNav(navId: string) {
    this.navId = navId
  }

  updateListScroll(offset: number) {
    this.listScrollOffset = offset
  }

  openArticle(articleId: string, depth: number) {
    this.currentArticleId = articleId
    this.overlayDepth = depth
  }

  clearOverlay() {
    this.overlayDepth = 0
    this.currentArticleId = ''
  }
}

它看起来很普通,但它解决的是第三篇最核心的体验问题:

用户从哪个栏目来、当前右侧打开了哪篇文章、现在覆盖到了第几层、退回以后应该回到什么状态。

如果没有这层状态,第三篇所有“固定导航”和“覆盖切换”都只是表面形式。

八、文章列表页不再只是列表,它要给覆盖层提供“稳定底板”

第三篇里,ArticleListPage 的角色也和前两篇不一样了。

前两篇里它更多负责“点一下,打开详情”;现在它还要承担一个隐藏职责:成为覆盖层下面那块始终可恢复的底板。

所以我会让它主动保存列表状态,比如滚动位置和当前筛选条件。

@Component
export struct ArticleListPage {
  @Prop navId: string = 'all'
  @State keyword: string = ''
  @State scrollOffset: number = 0

  onOpenArticle: (articleId: string) => void = () => {}

  build() {
    Column() {
      this.buildToolbar()
      List({ space: 10 }) {
        ForEach(this.filteredArticles(), (item: ArticleItem) => {
          ListItem() {
            ArticleListItem({ item })
          }
          .onClick(() => {
            this.onOpenArticle(item.id)
          })
        }, (item: ArticleItem) => item.id)
      }
      .onScroll((x: number, y: number) => {
        this.scrollOffset = y
        ReadingSessionStore.getInstance().updateListScroll(y)
      })
      .layoutWeight(1)
    }
  }
}

为什么这一层值得专门写?

因为第三篇的“返回恢复”不是一句口号,而是确实要落到组件状态上。

如果列表页自己都不知道刚才滚到了哪、当前是什么筛选条件,那覆盖层再优雅,返回体验也还是假的。

九、ArticleDetailPage 也要配合“覆盖模式”改写,不然返回逻辑会很别扭

如果说第三篇前半段在解决“外部结构”,那到了 ArticleDetailPage,真正要解决的就是“内部交互”。

因为右侧详情页现在不再是一个孤立页面,它必须知道自己可能处于两种状态:

  1. 普通详情页;
  2. 右侧覆盖层详情页。

这两种状态在 UI 上可能看起来差不多,但交互完全不同。

举个最直接的例子:顶部返回按钮。

  • 如果它是普通详情页,返回可能意味着退出整个详情页面;
  • 如果它是右侧覆盖层,返回只应该关闭当前覆盖层,恢复下面那层内容。

所以我会给 ArticleDetailPage 补一个明确的入参:

@Component
export struct ArticleDetailPage {
  @Prop articleId: string = ''
  @Prop isOverlay: boolean = false

  onClose: () => void = () => {}
  onOpenRelated: (articleId: string) => void = () => {}

  @State article?: ArticleItem

  aboutToAppear() {
    this.article = MockData.getArticleById(this.articleId)
    Logger.info(`[Detail] appear article=${this.articleId}, overlay=${this.isOverlay}`)
  }

  build() {
    Column() {
      this.buildHeader()
      this.buildContent()
      this.buildRelated()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .borderRadius(this.isOverlay ? 20 : 0)
    .shadow(this.isOverlay ? ShadowStyle.OUTER_DEFAULT_MD : ShadowStyle.NONE)
  }
}

然后在标题栏里,返回按钮的行为就能变得非常清楚:

@Builder
private buildHeader() {
  Row({ space: 12 }) {
    Button({ type: ButtonType.Normal }) {
      Image($r('app.media.ic_back')).width(20).height(20)
    }
    .onClick(() => {
      if (this.isOverlay) {
        this.onClose()
      } else {
        router.back()
      }
    })

    Text(this.article?.title ?? '')
      .fontSize(24)
      .fontWeight(FontWeight.Bold)
      .layoutWeight(1)
  }
}

这段代码看上去没多复杂,但它其实是在告诉读者一件很重要的事:

第三篇的“覆盖切换”不是视觉遮盖,而是一种明确的页面工作模式。

十、我还专门处理了一个细节:左侧换栏目时,要不要把右侧覆盖层一起关掉

这个问题在实际体验时非常容易被忽略。

假设我现在在左侧选中了“开发实践”,右侧打开了一篇文章详情。此时如果我再点左侧“行业观察”,右侧应该发生什么?

我最终给 Demo 定的规则是:

  • 左侧栏目一旦变化,右侧当前覆盖层全部关闭;
  • 右侧主内容区切换到新栏目对应的文章列表;
  • 新栏目成为新的阅读起点。

这样做的原因很简单:栏目切换本质上是在改变“工作上下文”。

如果继续保留旧覆盖层,用户很容易看到一种别扭状态:左边已经是“行业观察”,右边却还在读“开发实践”的文章。

所以 onNavSelect() 里,我会把逻辑写得很明确:

private onNavSelect(id: string) {
  this.selectedNavId = id

  ReadingSessionStore.getInstance().updateNav(id)

  if (this.overlayVisible) {
    this.overlayVisible = false
    this.overlayArticleId = ''
    OverlayRouteService.getInstance().reset()
  }

  Logger.info(`[Nav] switch nav=${id}, overlay cleared`)
}

我特别喜欢这段逻辑,因为它把产品规则直接翻译成了代码:

  • 导航切换优先级高于当前详情;
  • 栏目切换意味着重新开始一条阅读路径;
  • 右侧覆盖层只是当前栏目下的局部阅读状态,而不是全局状态。

十一、第三篇其实还顺手完成了一次“局部状态机”设计

写到这里,我越来越觉得第三篇的重点已经不只是 ArkUI 页面搭建了。

因为“左侧固定导航 + 右侧覆盖切换 + 返回恢复”这整套过程,本质上很像一个小型状态机。

我把它抽象成下面这几种状态:

State A:左侧导航 + 右侧文章列表
State B:左侧导航 + 右侧文章详情(覆盖层深度 1)
State C:左侧导航 + 右侧文章详情(覆盖层深度 2+)
State D:左侧导航切换后,右侧重新回到对应栏目列表

它们之间的转移关系其实非常清晰:

A --点击文章--> B
B --点击相关阅读--> C
C --返回--> B
B --返回--> A
A/B/C --切换左侧导航--> D
D ≈ 新的 A

一旦你用这个视角看第三篇,会发现很多看似零散的代码,其实都在服务同一件事:

  • FixedNavLayout 负责触发导航状态变化;
  • ArticleListPage 负责从列表进入覆盖层;
  • OverlayRouteService 负责组织 B/C 两类覆盖层状态;
  • ReadingSessionStore 负责让 A/B/C/D 之间的恢复变得可靠。

我觉得这也是社区文章里特别值得强调的一点:

真正有工程味的 Demo,不是页面越多越厉害,而是状态关系越来越清楚。

十二、纯大屏效果图里,这一篇最该展示的不是详情,而是“结构稳定”

如果只看截图,第三篇最容易被误解成“还是个左右双栏页”。

但我最希望读者看到的是这三个事实:

  1. 左侧导航保持固定,不跟着右侧变;
  2. 右侧当前正在展示详情;
  3. 返回以后,右侧能够回到原来的列表上下文。

所以第三篇的无设备边框大屏图,我特意让“导航稳定感”更突出。左边是清楚的栏目体系,右边是当前打开的文章详情,中间关系非常明确。

在这里插入图片描述

这张图其实就是第三篇最核心的“结果证明”。

十三、类型二综合讲解图,特别适合把第三篇的结构关系讲透

第三篇我继续保留类型二综合讲解图,而且我觉得它比前两篇更必要。

因为“固定导航 + 覆盖切换”这件事,单看某一张 DevEco 截图、某一张运行图,都很难一眼看懂完整关系。

但放到一张综合图里,就很容易讲清楚:

  • 第一块:DevEco 里怎么搭左侧固定导航;
  • 第二块:大屏最终运行效果是什么样;
  • 第三块:路由和交互流程怎么走;
  • 第四块:真机平板上长什么样;
  • 第五块:覆盖切换和返回恢复的关系。

这种图非常适合社区技术文章。它不像营销海报那么花,但信息密度高,而且读者一张图就能建立结构理解。

在这里插入图片描述

十四、我怎么判断第三篇是不是写对了

第三篇的验收标准,我比前两篇更在意“局部正确”。

1)左侧导航必须真正固定

不管我在右侧打开多少篇文章、怎么返回,左侧导航都不能乱跳、不能被重建得像换了一整页。

2)右侧文章详情必须以覆盖方式打开

覆盖层打开时,用户应该明确感受到:

  • 左侧没动;
  • 右侧进入了新的内容状态。

而不是像整页跳转一样把整个界面都带走。

3)返回只恢复右侧,不影响左侧导航上下文

这是第三篇最关键的一条。

我会重点看这条链路:

选择“开发实践”
  ↓
打开某篇文章
  ↓
打开相关阅读
  ↓
返回
  ↓
再返回

这时候,左侧仍然应该停留在“开发实践”,右侧则按顺序恢复到上一层文章、再恢复到文章列表。

4)列表滚动位置和筛选状态不能白丢

如果文章列表滚到一半,用户点了一篇文章,再返回,结果列表从头开始,那第三篇这套覆盖切换就只做到了表面。

5)日志能准确还原右侧覆盖状态变化

我会特别看底部日志:

[Overlay] push article=002 depth=1
[Overlay] push article=005 depth=2
[Overlay] pop depth=1
[Overlay] pop depth=0
[Route] restore root article id=001

这种日志非常有说服力,因为它证明你不是“碰巧切回去了”,而是真正组织好了覆盖层状态。

十五、为了让第三篇更可信,我专门补了一组调试日志与验收用例

我后来发现,像这种“左侧固定、右侧覆盖”的场景,只靠截图其实很难说明问题。因为截图只能告诉别人“看起来是这样”,却不能证明状态流转真的没问题。

所以第三篇我刻意补了一组更工程化的验收用例:

用例 1:默认进入首页,左侧应选中“全部文章”,右侧显示文章列表。
用例 2:点击“开发实践”,左侧高亮切换,右侧列表刷新,覆盖层为空。
用例 3:点击某篇文章,右侧打开详情覆盖层,左侧导航不变。
用例 4:在详情页点击相关阅读,右侧继续覆盖切换,overlayDepth + 1。
用例 5:返回一次,应恢复上一层详情;返回第二次,应恢复文章列表。
用例 6:详情打开时切换左侧栏目,应清空覆盖层并进入新栏目列表。

同时我也会用日志去核对:

[Nav] switch nav=practice
[List] open article=article-003
[Overlay] push article=article-003 depth=1
[Overlay] push article=article-007 depth=2
[Overlay] pop depth=1
[Overlay] pop depth=0
[Nav] switch nav=industry, overlay cleared

这套日志最大的价值,不是为了“好看”,而是为了帮助我确认两件事:

  1. 左侧导航状态和右侧覆盖状态有没有串线;
  2. 返回与栏目切换是否真的遵守了第三篇设定的产品规则。

我越来越觉得,像这种平行视界类文章,适当把测试思路写出来,读者会更容易相信这个 Demo 不是摆拍,而是真跑过、真调过的。

十六、实机上最大的感受,是阅读更像“查资料”,而不是“钻页面”

第三篇在真机平板上跑起来以后,我的感受比模拟器里更明显。

因为在大屏设备上,左侧固定导航真的会给人一种“工作台”的感觉:

  • 左边像目录;
  • 右边像阅读区;
  • 我始终知道自己在什么栏目;
  • 右边可以放心一层层读,不怕迷路。

这和手机上那种“点进去—返回—再点进去”的节奏是很不一样的。

更准确地说,第三篇让“双栏研读台”开始从“一个双栏页面”变成“一个有工作面结构的阅读工具”。

在这里插入图片描述

十七、第三篇解决了什么,也给第四篇留下了什么

如果把前三篇串起来看,我会这样理解:

  • 第一篇解决“能不能先把双栏骨架搭起来”;
  • 第二篇解决“阅读链能不能连续往前走”;
  • 第三篇解决“导航区和内容区能不能真正分工”。

而且从文章组织角度看,第三篇也是一个很好的分界点:从这一篇开始,这个 Demo 不再只是“页面能不能显示”,而是开始进入“交互规则是否完整、状态恢复是否可靠、工程拆分是否清楚”的阶段。

第三篇做到这里,我已经比较满意了,因为:

  • 左侧导航稳定下来;
  • 右侧内容切换方式明确下来;
  • 返回恢复逻辑也开始靠谱。

但它还没解决另外几个非常现实的问题:

  • 不同屏幕宽度下,左侧固定导航到底该占多少比例;
  • 平板和折叠屏展开态,右侧覆盖区宽度要不要一致;
  • 横竖切换时,左侧和右侧状态怎么同步保留;
  • 设备被系统回收以后,整套阅读状态怎么恢复。

这些问题其实都很适合下一篇继续写。

十八、本文小记

如果第二篇更像“继续往里读”,那第三篇更像“开始把工作台真正搭像了”。

这篇最重要的变化,不在于我多写了几个 ArkTS 文件,而在于我终于把这套 Demo 里的导航职责和内容职责分开了:

  • 左侧不再只是顺手放一个列表,而是真正的固定导航区;
  • 右侧也不再只是单纯的详情页,而是支持覆盖打开、返回恢复的内容工作区。

我很喜欢这种变化。因为它让“双栏研读台”更像一个会被真实使用的大屏应用,而不是一个为了演示双栏而硬拼出来的示例。

第三篇做到这里,我觉得这一条产品线已经顺了。

下一篇,我准备继续往更工程化的问题上走:

不同分栏比例、不同设备尺寸和状态同步,如何让“双栏研读台”在平板与折叠屏上都看起来合理?

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐