前面我们拆了"维系"和"档案"两个页面。这篇来看第三个 Tab——记录

"维系"页是混合拼图,"档案"页是单一卡片重复,而"记录"页介于两者之间:有一个筛选栏 + 同一种卡片的纵向列表。但它的卡片内部结构是三个页面中最复杂的——左侧图标、右侧双行信息、底部详情文字,三段式布局。


一、页面全貌

记录

代码骨架:

@Builder
InteractionTab() {
  Scroll() {
    Column() {
      Row() { Text('互动记录')  Blank()  Text('+ 记录') }
      Text('礼物、宴请、帮助、关怀都值得被记住')
      Row() { 筛选药丸 × 5 }
      ForEach(this.records, (item: InteractionRecord) => {
        this.InteractionRecordCard(item)
      }, ...)
    }
    .width('100%')
    .padding({ left: 24, right: 24, top: 0, bottom: 140 })
  }
  .scrollBar(BarState.Off)
  .height('100%')
  .width('100%')
  .backgroundColor('#F9F7F2')
}

外层结构和前两个页面完全一致,不再重复。这篇重点讲三个东西:标题行的操作入口、筛选栏、记录卡片。


二、标题行:和"档案"页如出一辙

Row() {
  Text('互动记录')
    .fontSize(26)
    .fontWeight(FontWeight.Bold)
    .fontColor('#2C2723')
  Blank()
  Text('+ 记录')
    .fontSize(15)
    .fontColor('#8B7355')
}
.width('100%')
.alignItems(VerticalAlign.Center)
.margin({ top: 20, bottom: 6 })
.padding({ left: 2, right: 2 })

和"档案"页的标题行结构完全相同:Row + Blank() + 右侧操作文字。alignItems(VerticalAlign.Center) 让大小号文字垂直居中。

2.1 "+ 记录"目前是纯文本

当前右侧的 + 记录 只是一个 Text,品牌色 #8B7355,没有背景、没有圆角、没有点击态。它视觉上能被识别为可操作元素,全靠颜色和 + 符号的暗示。


三、筛选栏:水平排列的“药丸”

Row() {
  this.InteractionFilterPill('全部', true)
  this.InteractionFilterPill('礼物往来', false)
  this.InteractionFilterPill('宴请聚会', false)
  this.InteractionFilterPill('帮助支持', false)
  this.InteractionFilterPill('日常关怀', false)
}
.width('100%')
.margin({ bottom: 18 })

5 个筛选药丸水平排列在一个 Row 里。

3.1 药丸组件

@Builder
InteractionFilterPill(text: string, selected: boolean) {
  Text(text)
    .fontSize(13)
    .fontWeight(selected ? FontWeight.Medium : FontWeight.Normal)
    .fontColor(selected ? '#FFFFFF' : '#8A8076')
    .padding({ left: 14, right: 14, top: 8, bottom: 8 })
    .borderRadius(16)
    .backgroundColor(selected ? '#3D3632' : '#FFFFFF')
    .margin({ right: 10 })
}

选中态和未选中态的对比:

状态 字色 背景 字重
选中 #FFFFFF #3D3632 深棕 Medium
未选中 #8A8076 #FFFFFF Normal

深底白字 vs 白底灰字,对比非常强烈,用户一眼就能看出当前选中的是哪个。

3.2 为什么不用 Scroll 包裹

5 个药丸在窄屏上可能超出宽度。当前代码没有用 Scroll 包裹筛选栏,而是直接放在 Row 里。

如果后续筛选项增多,可以改成:

Scroll() {
  Row() {
    this.InteractionFilterPill('全部', true)
    this.InteractionFilterPill('礼物往来', false)
    // ...
  }
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)

和"维系"页的横向提醒卡片同理——水平内容超出时,加一层横向 Scroll 即可。

3.3 margin({ right: 10 })

.margin({ right: 10 })

药丸之间靠 right: 10 分隔。只设右边距不设左边距,第一个药丸左边不需要间距,最后一个右边距会被父容器的 padding 吸收。


四、InteractionRecordCard:三段式卡片

这是"记录"页的核心组件。每张卡片分三段:图标行标题行详情文字

三段式卡片

@Builder
InteractionRecordCard(item: InteractionRecord) {
  Column() {
    // 图标 + 标题行
    Row() {
      Column() { ... }   // 左侧图标
      Column() { ... }   // 右侧信息
      .layoutWeight(1)
      .margin({ left: 14 })
    }
    .width('100%')

    // 详情文字
    Text(item.detail)
      .fontSize(14)
      .lineHeight(21)
      .fontColor('#8A8076')
      .width('100%')
      .margin({ top: 14 })
  }
  .alignItems(HorizontalAlign.Start)
  .padding(20)
  .borderRadius(20)
  .backgroundColor('#FFFFFF')
  .shadow({ radius: 12, color: '#1A000006', offsetY: 3 })
  .margin({ bottom: 12 })
}

接下来逐层拆开。


五、左侧图标:44×44 圆角方块

Column() {
  Text(item.title === '送出礼物' ? '↗' : item.title === '收到帮助' ? '↓' : '♡')
    .fontSize(18)
    .fontColor(item.title === '送出礼物' ? '#C4956A' : item.title === '收到帮助' ? '#7A9B8E' : '#B8929E')
}
.width(44)
.height(44)
.borderRadius(14)
.justifyContent(FlexAlign.Center)
.backgroundColor('#FAF7F2')

5.1 不是圆形,是圆角方块

.borderRadius(14)

44vp 的宽高配 14vp 的圆角,形成的是圆角方块,不是正圆。这和"档案"页的 56×56 正圆头像(borderRadius 28)形成区分——图标是功能性的,用方块;头像是身份性的,用圆形。

5.2 图标和颜色随类型变化

类型 图标 图标色 含义
送出礼物 #C4956A 金棕 向外给出
收到帮助 #7A9B8E 绿 向内收到
日常关怀 #B8929E 温情连接

三种类型三种颜色,用户扫一眼左侧图标就能区分记录的性质。

5.3 浅底背景

.backgroundColor('#FAF7F2')

#FAF7F2 比卡片背景 #FFFFFF 略暖,比页面背景 #F9F7F2 略白。这个微妙的色差让图标区域从卡片中"浮"出来,但不会太突兀。


六、右侧信息:双行布局

右侧是一个 Column,包含两行:

Column() {
  // 第一行:标题 + 金额
  Row() {
    Text(item.title)
      .fontSize(17)
      .fontWeight(FontWeight.Bold)
      .fontColor('#2C2723')
      .layoutWeight(1)
    // 金额或"低成本"标签
  }

  // 第二行:对象 + 日期
  Text(`${item.target} · ${item.date}`)
    .fontSize(13)
    .fontColor('#B5ADA2')
    .margin({ top: 5 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 14 })

6.1 第一行的两种结尾

第一行右侧根据是否有金额,展示两种不同内容:

有金额时:

if (item.amount.length > 0) {
  Text(item.amount)
    .fontSize(15)
    .fontWeight(FontWeight.Medium)
    .fontColor('#8B7355')
}

金额用品牌色 #8B7355,15 号字 Medium 字重,比标题稍小但足够醒目。

无金额时:

else {
  Text('低成本')
    .fontSize(12)
    .fontColor('#A0988C')
    .padding({ left: 8, right: 8, top: 3, bottom: 3 })
    .borderRadius(8)
    .backgroundColor('#F5F0EA')
}

"低成本"做成一个小标签——12 号字、浅底 #F5F0EA、8vp 圆角。和"档案"页的特征标签风格一致,但尺寸更小,因为它是补充信息而不是核心信息。

6.2 标题用 layoutWeight(1)

Text(item.title)
  .layoutWeight(1)

标题占据剩余宽度,金额/标签靠右。如果标题很长,会被截断而不是把金额挤出去。

6.3 第二行:中圆点分隔

Text(`${item.target} · ${item.date}`)

和"档案"页的亲密度行一样,用 · 分隔两个字段。#B5ADA2 浅灰色,视觉上退到第二层。


七、详情文字

Text(item.detail)
  .fontSize(14)
  .lineHeight(21)
  .fontColor('#8A8076')
  .width('100%')
  .margin({ top: 14 })

详情文字和图标行之间用 margin({ top: 14 }) 分隔。没有用 Divider,因为卡片内容不多,留白比分隔线更轻盈。

lineHeight(21) 配合 fontSize(14),行间距约 7vp,阅读舒适。


八、卡片外壳

.alignItems(HorizontalAlign.Start)
.padding(20)
.borderRadius(20)
.backgroundColor('#FFFFFF')
.shadow({ radius: 12, color: '#1A000006', offsetY: 3 })
.margin({ bottom: 12 })
属性 和"档案"页对比
padding 20 22(记录页稍小)
borderRadius 20 22(记录页稍小)
shadow radius 12 14(记录页稍轻)

记录卡片比档案卡片略小略轻,因为记录列表通常条目更多,卡片太大会让页面显得空旷。


九、总结

这篇我们拆解了"记录"页面的 UI 布局,核心要点:

  1. 筛选栏:深底/白底药丸切换,选中态对比强烈,后续可加横向 Scroll 适配窄屏。
  2. 三段式卡片:图标行 + 标题行 + 详情文字,信息层次清晰。
  3. 左侧图标:44×44 圆角方块(非圆形),图标和颜色随类型变化。
  4. 标题行双结尾:有金额显示数字,无金额显示"低成本"标签,用 layoutWeight(1) 保证标题不挤压金额。
  5. "+ 记录"纯文本:当前靠颜色暗示可点击,后续可加背景做成药丸按钮或轻量按钮。
  6. 卡片尺寸介于提醒和档案之间:padding 20、borderRadius 20,比小卡片大、比大卡片小,适配列表条目较多的场景。
Logo

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

更多推荐