鸿蒙原生应用 ArkUI 实战:横向滚动胶囊 + 自动续费开关卡 —— 订阅管理页的筛选与开关架构

App 54「订阅服务管理」订阅页(Func1Tab),主题色 #9B51E0 梦幻紫,4 个 Tab 分别为首页(📺)、订阅(📋)、日历(📅)、统计(📊)。本篇基于 54-sub-mgr/entry/src/main/ets/pages/Func1Tab.ets(共 101 行)逐段拆解,附 4 张实机截图。

订阅页整体布局

一、整体结构:三区"Header + 分类 + 列表"布局

订阅页是"分类筛选 + 订阅管理"的组合布局——3 个 @Builder 块:

build() {
  Column() {
    this.Header()
    this.CatRow()
    Scroll() {
      Column({ space: 12 }) {
        this.SubList()
      }
      .width('100%')
      .padding({ left: D.pad, right: D.pad, top: 14, bottom: D.pad + this.safeBottom + 20 })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

3 块结构:Header(单行品牌头)→ CatRow(横向滚动分类胶囊)→ SubList(4 张含开关的订阅管理卡)。订阅页是"管理页"——先用分类胶囊筛选(全部/视频/音乐/工具/云服务),再对每项订阅做管理(查看下次扣款日、切换自动续费开关)。筛选 + 管理是本页核心职责,比首页的"只读总览"多了交互操作。

二、数据模型 + 状态翻转逻辑

interface Sub {
  id: number;
  emoji: string;
  name: string;
  price: number;
  cycle: string;
  nextDate: string;
  autoRenew: boolean;
}
@State subs: Sub[] = [
  { id: 1, emoji: '🎬', name: '视频VIP会员', price: 25, cycle: '月', nextDate: '09-01', autoRenew: true },
  { id: 2, emoji: '🎵', name: '音乐会员', price: 15, cycle: '月', nextDate: '09-05', autoRenew: true },
  { id: 3, emoji: '☁️', name: '云存储200G', price: 6, cycle: '月', nextDate: '09-10', autoRenew: false },
  { id: 4, emoji: '💻', name: '设计软件订阅', price: 88, cycle: '年', nextDate: '2027-01', autoRenew: true }
];

Sub 七字段:比首页多了 nextDate(下次扣款日)和 autoRenew(自动续费开关)。4 项订阅中云存储 autoRenew: false——演示数据刻意展示开关的两种状态。

private toggleRenew(item: Sub): void {
  item.autoRenew = !item.autoRenew;
  this.subs = this.subs.slice();
}

@State 数组更新经典写法:翻转 item.autoRenew 后用 this.subs = this.subs.slice() 浅拷贝触发重渲染——ArkUI 中 @State 数组的原地修改不触发刷新,必须重新赋值引用(slice())才更新 UI。

三、Header:单行标题

Header() {
  Column() {
    Text('我的订阅').fontSize(20).fontWeight(FontWeight.Bold).fontColor(C.text)
      .width('100%').padding({ top: this.safeTop + 10, left: D.pad, right: D.pad, bottom: 10 })
  }.width('100%').backgroundColor(C.card)
}

20 号加粗、safeTop + 10 顶边距、底部仅 10(比首页的 12 更小,因为下方紧跟分类胶囊行)。"我的订阅"是名词型标题(管理页),与首页"订阅管理"(主标题)区分主次。

四、CatRow:横向滚动分类胶囊

CatRow() {
  Scroll() {
    Row({ space: 10 }) {
      ForEach(this.cats, (c: string, idx: number) => {
        Text(c).fontSize(13)
          .fontColor(this.activeCat === idx ? '#FFFFFF' : C.textSub)
          .padding({ left: 14, right: 14, top: 7, bottom: 7 })
          .backgroundColor(this.activeCat === idx ? C.primary : C.card).borderRadius(16)
          .onClick(() => { this.activeCat = idx; })
      }, (c: string) => c)
    }
  }
  .scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
  .padding({ left: D.pad, right: D.pad, top: 8, bottom: 8 }).backgroundColor(C.card)
}

逐点拆解:

  1. 横向滚动容器:Scroll + .scrollable(ScrollDirection.Horizontal)——分类胶囊可横向滑动,scrollBar(BarState.Off) 隐藏滚动条。
  2. 5 分类胶囊:全部/视频/音乐/工具/云服务——默认选中"全部"(activeCat = 0)。
  3. 选中态:activeCat === idx 时紫底白字(C.primary),未选白底灰字(C.card)——与表单卡片的灰底胶囊(App 52/53)不同,这里用"白底 + 紫色选中"保持页面清爽,白底胶囊融入白色 Header 背景。
  4. 圆角 16:比表单胶囊(14)更圆——分类栏的胶囊更"药丸"形,视觉更柔和。

切换视频分类胶囊

五、SubList:自动续费开关订阅卡

SubList() {
  Column({ space: 12 }) {
    ForEach(this.subs, (s: Sub) => {
      Column({ space: 12 }) {
        Row({ space: 12 }) {
          Row() { Text(s.emoji).fontSize(24) }
          .width(48).height(48).backgroundColor(C.primarySoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center)
          Column({ space: 3 }) {
            Text(s.name).fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text)
            Text('¥' + s.price + '/' + s.cycle + ' · 下次扣款 ' + s.nextDate).fontSize(12).fontColor(C.textDim)
          }.alignItems(HorizontalAlign.Start).layoutWeight(1)
        }.width('100%')
        Row() {
          Text('自动续费').fontSize(13).fontColor(C.textSub).layoutWeight(1)
          Toggle({ type: ToggleType.Switch, isOn: s.autoRenew })
            .selectedColor(C.primary)
            .onChange(() => { this.toggleRenew(s); })
        }.width('100%')
      }
      .width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
    }, (s: Sub) => s.id.toString())
  }.width('100%')
}

逐点拆解:

  1. 双行结构:上区(图标 + 名称 + 金额周期 + 下次扣款)、下区(自动续费标签 + Switch 开关)——信息区 + 操作区上下分离,比首页的单行列表多了操作层。
  2. 信息行:48×48 主色软底图标(C.primarySoft 淡紫)+ 14 号名称 + 12 号"¥25/月 · 下次扣款 09-01"——金额周期 + 下次扣款日一段式副文案,扣款日是订阅管理的关键信息。
  3. 自动续费开关:Toggle({ type: ToggleType.Switch, isOn: s.autoRenew }) + .selectedColor(C.primary)——紫色开关(开启时轨道变紫),每张卡独立开关状态。
  4. 开关即改即存:onChange 直接翻转状态,无保存按钮——开关操作无中间态、无需确认,所见即所得,这是管理页区别于"表单录入页"(App 52/53 有保存按钮)的核心差异。

切换云存储自动续费开关

六、跨页数据自洽:订阅管理的全局闭环

  { id: 3, emoji: '☁️', name: '云存储200G', price: 6, cycle: '月', nextDate: '09-10', autoRenew: false },
  private renewDays: number[] = [1, 5, 10, 18];

订阅页"下次扣款 09-01/09-05/09-10"与日历页续费日 [1, 5, 10, 18] 对应——扣款日期一致,日历页的紫色标记 = 订阅页的下次扣款日。订阅页 4 项与首页"4 项"统计一致;关掉续费 = 未来减少一笔支出,统计页趋势随之更新。

订阅列表与交互

七、系列横向对比:管理型列表的开关应用

管理页代表 App开关类型开关范围主题色
卡内开关App 54 订阅每条独立开关4 张卡 4 个开关#9B51E0 梦幻紫
全局开关App 53 保修单一全局开关一个开关管所有#00A862 生态绿
表单开关App 50 评价录入时选择提交时生效#5549A6 靛蓝

差异点:App 54 的"开关即列表管理"——每个订阅条目自带续费开关,列表既是浏览也是管理(不用进详情页就能关续费),"列表 + 开关"一体化是订阅管理的高效形态。App 53 的开关独立成卡(全局提醒),54 的开关嵌入每条卡片(逐项管理)。

八、开发者视角:调试与验证技巧

  1. @State 数组更新:toggleRenew 里 item.autoRenew = !item.autoRenew; this.subs = this.subs.slice();——必须重新赋值数组引用(slice())才能触发 ForEach 重渲染,直接改 item.autoRenew 后不做 slice 的话 UI 不刷新,这是 ArkUI 状态管理的经典坑。
  2. 开关状态持久化:autoRenew 是内存态——应用重启后开关复位,产品化需 Preferences 持久化,并把开关状态同步到服务端/订阅平台。
  3. 分类筛选未实现:activeCat 切换只改选中态,列表未按分类过滤——产品化应在 ForEach 前过滤 subs.filter(s => s.cat === activeCat)。
  4. 嵌套滚动:分类栏横向 Scroll、列表区纵向 Scroll——嵌套滚动时手势判定要小心,当前结构横向/纵向分区明确,无手势冲突。

九、演进方向

方向当前状态产品化演进
分类筛选仅切换选中态subs.filter() 按分类过滤列表
开关持久化@State 内存态Preferences 存储 + 同步订阅平台
扣款提醒仅展示扣款日扣款前 3 天推送提醒
免费试用无标记试用订阅 + 倒计时提醒
取消续费仅翻转开关调用订阅平台取消接口

十、一句话总结

订阅页以"横向滚动分类胶囊 + 自动续费开关卡"完成了订阅的管理闭环。ScrollDirection.Horizontal 横向滚动分类栏(白底胶囊融合背景 + 紫色选中突出)、Toggle Switch 开关嵌入每张卡片(selectedColor 主紫 + onChange 即改即存)、this.subs.slice() 浅拷贝触发重渲染——101 行代码,一个"筛选分类 → 查扣款日 → 切续费"的订阅管理页,也是"列表 + 开关"一体化管理的标准范式。

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

Logo

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

更多推荐