鸿蒙原生应用 ArkUI 声明式实战:通讯录我的页 —— 渐变卡 + 悬浮统计 + Toggle 隐私卡组合技
App 46「班级通讯录」我的页(ProfileTab),主题色
#0984E3蓝色(blue),4 个 Tab 分别为首页(🏠)、通讯录(👥)、班级(📋)、我的(👤)。我的页采用"UserCard + 统计行 + 隐私卡 + 菜单 + 关于"五区布局——蓝色渐变 UserCard(📇 64×64 半透明名片头像 + "通讯录达人"18 号加粗 + “计科2022级 · 张娜”)+三列悬浮统计卡(45 班级成员 / 12 常用联系人 / 8 最近通话,负 margin 上浮)+隐私设置卡(“公开手机号” + “开启后班级同学可查看你的号码” + Toggle 开关,蓝色 selectedColor)+5 行菜单(👤 个人资料 / 📞 通话记录 / 🔒 隐私设置 / 💬 消息通知 / ⚙️ 通用设置)+底部关于区(班级通讯录 Version 1.0.0)。本篇基于46-class-contacts/entry/src/main/ets/pages/ProfileTab.ets(共 105 行)逐段拆解,附 4 张实机截图。
一、整体结构:五区"UserCard + 统计 + 隐私 + 菜单 + 关于"布局
我的页是五个 @Builder 块纵向堆叠的"个人中心"标准范式:
build() { Column() { Scroll() { Column() { this.UserCard() this.StatRow() this.PrivacyCard() this.MenuList() this.About() } .width('100%') .padding({ bottom: D.pad + this.safeBottom + 20 }) } .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top) } .width('100%').height('100%').backgroundColor(C.bg) }5 块结构:
- UserCard— 蓝色渐变品牌区,展示通讯录达人身份与班级身份
- StatRow— 三列悬浮统计卡(45 班级成员 / 12 常用联系人 / 8 最近通话)
- PrivacyCard—系列首个隐私开关卡(公开手机号 Toggle)
- MenuList— 5 行功能菜单
- About— 底部版本信息收尾
与 App 39-45 我的页同构的"头图 + 悬浮卡 + 菜单 + 版本"范式 +系列首次的隐私开关卡——蓝色主题、通讯录业务指标、隐私可见性设置是本页的三大特色。
项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub
二、UserCard:蓝色渐变头图 + 📇 名片头像
UserCard() { Row({ space: 14 }) { Row() { Text('📇').fontSize(32) } .width(64).height(64).backgroundColor('#33FFFFFF').borderRadius(32).justifyContent(FlexAlign.Center) Column({ space: 5 }) { Text('通讯录达人').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF') Text('计科2022级 · 张娜').fontSize(12).fontColor('#FFFFFF').opacity(0.85) }.alignItems(HorizontalAlign.Start).layoutWeight(1) } .width('100%') .padding({ top: this.safeTop + 30, left: D.pad, right: D.pad, bottom: 44 }) .linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] }) }技术拆解:
- 渐变头图:
linearGradient从#0984E3蓝渐变到#4DA3F0浅蓝——与首页班级卡同款蓝色渐变,品牌统一。 - 📇 名片头像:64×64 半透明圆底 + 32 号 📇——与 Tab 图标(🏠 不同)、副标题(📇)呼应,"名片/通讯录"的符号语义。
- 昵称"通讯录达人":达人定位——“联系人多的达人”(45 人班级 + 12 常用联系人),通讯录产品的"人脉达人"人设。
- 副标题含姓名:“计科2022级 · 张娜”——首次在个人中心展示本人姓名(张娜 = 通讯录页第一位成员),班级 + 姓名双重身份,用户在自己的通讯录主页"实名可见"。
- 底部 44 padding + 安全区:为悬浮统计卡预留空间、顶部避让状态栏。
三、StatRow:三列悬浮统计卡
StatRow() { Row() { ForEach(this.stats, (s: StatItem) => { Column({ space: 4 }) { Text(s.value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(C.text) Text(s.label).fontSize(11).fontColor(C.textDim) }.layoutWeight(1) }, (s: StatItem) => s.label) } .width('92%').padding({ top: 14, bottom: 14 }) .backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }).margin({ top: -24 }) }统计数据(15-19 行):45 班级成员 / 12 常用联系人 / 8 最近通话——三列,指标完全贴合通讯录业务:“班级成员”(班级规模)、“常用联系人”(个人联系圈)、“最近通话”(近期互动)——规模 + 人脉 + 活动的三元统计是通讯录产品的标准数据模型。
技术要点:
- 负 margin 悬浮:
.margin({ top: -24 })上移叠加在渐变区下缘——系列标志性手法。 - 三列等分:三个 Column
layoutWeight(1)自动三等分,18 号数字 + 11 号标签。 - 92% 宽度:左右留 4% 呼吸边距。
- key:
s.label(班级成员/常用联系人/最近通话,天然唯一)。
数据自洽:45 班级成员 ↔ 首页"45 名成员" ↔ 班级页"总人数 45";12 常用联系人 ↔ 通讯录页展示成员(5 位)+ 首页核心角色(3 位)+ 其他常用;8 最近通话 ↔ 📞 呼叫动作的累计——数字处处有出处。
四、PrivacyCard:隐私设置卡(系列首次隐私开关)
PrivacyCard() { Row() { Column({ space: 2 }) { Text('公开手机号').fontSize(14).fontColor(C.text) Text('开启后班级同学可查看你的号码').fontSize(11).fontColor(C.textDim) }.alignItems(HorizontalAlign.Start).layoutWeight(1) Toggle({ type: ToggleType.Switch, isOn: this.publicPhone }) .selectedColor(C.primary) .onChange((v: boolean) => { this.publicPhone = v; }) } .width('92%').padding(16).margin({ top: 14 }).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }技术拆解:
- 左文右开关:左侧双行文字(14 号"公开手机号" + 11 号说明"开启后班级同学可查看你的号码"),
layoutWeight(1)撑开,右侧Toggle开关。 - Toggle 开关:
Toggle({ type: ToggleType.Switch, isOn: this.publicPhone })+selectedColor(C.primary)蓝色开启态 +onChange写入@State publicPhone——与 App 42 树洞投稿"匿名开关"同款组件,但语义不同:树洞控"是否匿名"、本页控"号码是否公开"。 - 默认开启:
publicPhone: boolean = true——通讯录的默认逻辑是"号码公开"(班级通讯录的用途就是互查号码),用户可手动关闭。 - 隐私说明文案:“开启后班级同学可查看你的号码”——一句话说清开关的后果,用户基于明确信息做隐私决策(可信隐私设计的核心)。
- 隐私与呼叫的联动:关闭后他人通讯录中不显示号码(📞 呼叫不可用或转站内消息)——开关状态驱动全 App 的可见性。
五、MenuList:5 行菜单 + Divider 条件分隔
MenuList() { Column() { ForEach(this.menus, (m: MenuItem, idx: number) => { Row({ space: 12 }) { Text(m.emoji).fontSize(18) Text(m.title).fontSize(14).fontColor(C.text).layoutWeight(1) Text('>').fontSize(14).fontColor(C.textDim) } .width('100%').padding({ left: 14, right: 14, top: 14, bottom: 14 }) .onClick(() => { promptAction.showToast({ message: m.title }); }) if (idx < this.menus.length - 1) { Divider().color(C.stroke).strokeWidth(0.5) } }, (m: MenuItem) => m.title) } .width('92%').margin({ top: 14 }).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }) }5 个菜单项(22-28 行):👤 个人资料 / 📞 通话记录 / 🔒 隐私设置 / 💬 消息通知 / ⚙️ 通用设置。
菜单语义分析:
- 👤 个人资料:自己的资料编辑(姓名/学号/头像/手机号)——对应 UserCard 的身份信息。
- 📞 通话记录:呼叫历史(谁、何时、时长)——对应统计"8 最近通话"。
- 🔒 隐私设置:隐私开关的完整页(公开手机号/在线状态可见性/位置)——PrivacyCard 是概览、本菜单是完整设置。
- 💬 消息通知:公告/消息推送开关——对应班级公告的推送触达。
- ⚙️ 通用设置:关于、缓存、账号等。
实现细节:三栏行(emoji + 标题 + “>”)、ForEachindex 判断最后一行不加 Divider、整行可点 Toast、92% 宽卡片——系列标准菜单卡。margin({ top: 14 })与隐私卡保持间距。
六、About:版本信息收尾
About() { Column({ space: 4 }) { Text('班级通讯录').fontSize(12).fontColor(C.textDim) Text('Version 1.0.0').fontSize(11).fontColor(C.textDim).opacity(0.7) }.width('100%').margin({ top: 24 }) }12 号浅灰应用名 + 11 号更浅版本号(opacity(0.7)),margin({ top: 24 })与菜单卡拉开距离——系列统一收尾。
七、跨页数据自洽:我的页 = 通讯录达人档案
我的页每个元素都能在其他页面找到呼应:
- “张娜”↔ 通讯录页第一位成员(👨🎓 张娜)——本人即通讯录中的一员,实名个人中心(不同于匿名 App)。
- “计科2022级”↔ 首页班级卡 ↔ 班级页统计卡——班级名三页统一。
- “45 班级成员”↔ 首页"45 名成员" ↔ 班级页"总人数 45"——45 三处一致。
- "公开手机号"开关↔ 通讯录的 📞 呼叫——开关状态决定他人能否呼叫本人。
- "📞 通话记录"菜单↔ 首页/通讯录的 📞 呼叫动作——呼叫历史在此沉淀。
八、实机截图与交互演示
本节结合 4 张实机截图,逐张还原我的页的视觉效果与交互过程。
1. 我的页首屏:蓝色渐变 + 统计 + 隐私卡
第一张截图是我的页默认首屏:顶部蓝色渐变 UserCard——📇 半透明名片头像、右侧"通讯录达人"加粗白字与"计科2022级 · 张娜"(姓名首次出现在个人中心);下方白色统计卡以负边距悬浮在渐变区下缘,三列数字(45 班级成员 / 12 常用联系人 / 8 最近通话)等分排布;再往下是隐私设置卡"公开手机号"(蓝色开启 Toggle + 说明"开启后班级同学可查看你的号码")与菜单第一行"👤 个人资料"。蓝色渐变 + 隐私开关是本页的两大特色。
2. 点击"个人资料":弹出 Toast
第二张截图是点击菜单第一行"👤 个人资料"后的反馈:弹出"个人资料"Toast。菜单整行可点(emoji、标题、箭头任意位置均触发),"个人资料"是本人信息的编辑入口——对应 UserCard 的"计科2022级 · 张娜"身份信息。
3. 点击"通话记录":弹出 Toast
第三张截图是点击菜单第二行"📞 通话记录"后的反馈:弹出"通话记录"Toast。"通话记录"对应统计"8 最近通话"的明细——与首页/通讯录的 📞 呼叫动作衔接,呼叫历史(谁、何时、时长)在此查看,是通讯录"联系闭环"的记录层。
4. 我的页完整视图:隐私卡 + 菜单 + 版本
第四张截图是我的页完整视图:隐私设置卡(公开手机号 Toggle)、五个菜单(👤 个人资料 / 📞 通话记录 / 🔒 隐私设置 / 💬 消息通知 / ⚙️ 通用设置)、底部"班级通讯录 / Version 1.0.0"全部可见。从渐变头图到版本号,五区块在单屏内完整呈现。
九、扩展思考:通讯录的隐私与信任设计
隐私开关卡是通讯录产品的核心信任设计,完整体系值得展开:
- 号码可见性的粒度:公开手机号(当前开关)之上可细分"仅班委可见/全班可见/仅老师可见"——按角色分级控制,隐私粒度更精细。
- 在线状态的隐私:在线状态是个人数据(谁看到你在线)——隐私设置应有"在线状态可见性"开关(隐身模式),与号码可见性并列。
- 位置与呼叫权限:若支持"附近同学"或呼叫,需系统权限(电话/位置)——权限申请需说明用途(“用于拨打同学电话”),符合最小化原则。
- 隐私的默认值:号码默认公开(通讯录用途)但应有明显提醒(首次开启的弹窗说明)——默认公开 + 明示告知是通讯录隐私的平衡。
- 被遗忘权:转班/毕业时可"退出班级通讯录"(数据移除)——通讯录个人数据的删除权,产品合规底线。
十、扩展思考:通讯录产品的呼叫与消息能力
📞 呼叫是通讯录的核心动作,完整的能力矩阵值得展开:
- 呼叫能力:📞 调起系统拨号(
@ohos.telephony)拨打成员号码——号码来自通讯录数据(受隐私开关控制),呼叫前确认号码可见。 - 通话记录:我的页"📞 通话记录"展示呼叫历史(对方/时间/时长)——
@ohos.telephony的通话记录读取或本地记录。 - 消息能力:通讯录可扩展"站内消息"(私聊同学,App 38 交流页模式)——隐私关闭号码时,消息成为替代联系方式。
- 班级群聊:首页快捷入口"💬 群聊"——班级全员群(公告的讨论区),与通讯录成员体系打通。
- 呼叫与状态的联动:在线状态(绿点)提示"此刻可联系"、离线提示"最后在线时间"——状态驱动呼叫决策,减少无效呼叫。
十一、开发者视角:我的页的调试与验证技巧
- 负 margin 叠层:统计卡
margin({ top: -24 })悬浮,构建顺序(UserCard 先、StatRow 后)决定 StatRow 在上层——调整构建顺序需配合zIndex(系列同款坑)。 - Toggle 状态与全局联动:
publicPhone开关状态应影响通讯录的号码可见性(他人视角)——Demo 开关独立,产品化需全局状态(AppStorage)驱动,验证开关与呼叫可用性的联动。 - 统计数字的跨页核对:45/12/8 与首页(45 名成员)、班级页(45 人)、通讯录页(5 位展示)的核对——"数字处处有出处"的系列标准。
- 姓名的一致性:"张娜"在 UserCard 与通讯录页第一位成员——修改任一页需同步(同一用户数据源)。
- 菜单 key 长期方案:新增菜单项改用 id 作 key(当前
m.title5 项内唯一)——系列标准建议。
十二、FAQ 与一句话总结
Q1:为什么我的页首次显示真实姓名(张娜)?
通讯录是"实名产品"(班级成员互查联系方式需要实名)——与 App 41/42 的匿名不同,通讯录的核心是"真实可靠的联系",实名是产品的信任基础,UserCard"计科2022级 · 张娜"即实名身份。
Q2:"公开手机号"开关是做什么的?
控制自己的手机号对班级同学的可见性——开启(默认)他人可在通讯录看到并呼叫号码、关闭则隐藏号码(仅站内消息)——隐私开关让用户掌握自己信息的可见权,是通讯录的信任设计核心。
Q3:45 班级成员和通讯录的 5 位成员什么关系?
45 是全班总数(班级页统计 + 首页规模),5 位是通讯录页当前展示的成员样例——"展示样例 vs 总人数"的口径与系列惯例一致,真实产品滚动展示全部 45 人。
Q4:关闭公开手机号后还能被呼叫吗?
不能直接呼叫(号码不可见)——他人可通过站内消息联系你;产品化后"隐私开关 → 号码可见性 → 呼叫可用性"三级联动,关闭即保护。
一句话总结:我的页以 105 行代码完成了通讯录达人的个人中心——蓝色渐变头图配 📇 名片头像、"计科2022级 · 张娜"的实名身份、“45 成员 / 12 联系人 / 8 通话"的人脉统计、首个隐私开关卡(公开手机号 Toggle)、5 菜单覆盖"资料/通话/隐私/消息/设置”。实名身份 + 隐私可见性控制是本页的双核,让通讯录"可靠联系"与"隐私可控"两大诉求在个人中心得到平衡。
十三、系列横向对比:隐私设置的三级形态
App 46 的隐私开关卡是系列隐私设计的第三级,与前代对比可看清演进:
| 隐私形态 | 代表 | 控制对象 | 默认值 |
|---|---|---|---|
| 匿名开关 | App 42 树洞 | 发布是否匿名 | 默认匿名 |
| 匿名声明 | App 41 表白墙 | 常驻匿名承诺 | 无条件匿名 |
| 可见性开关 | App 46 通讯录 | 号码对同学可见 | 默认公开 |
演进逻辑:树洞的隐私是"隐藏身份"(匿名,防止被认出)、通讯录的隐私是"控制可见"(号码,选择谁能看到)——匿名是"我不说我是谁"、可见性是"我的信息对谁开放",两类隐私语义不同:匿名产品保护"表达者身份"、实名产品保护"信息可见范围"。App 46 的"公开手机号"开关是系列首次"可见性控制"——用户掌控自己信息的开放程度,这是实名通讯录的信任设计核心(与 App 42 的匿名开关并列,构成隐私设计的两个方向:隐藏 vs 开放)。
开关的默认值哲学:树洞默认匿名(匿名是产品灵魂,无需选择)、通讯录默认公开(通讯录的用途是互查号码,公开是默认)——默认值反映产品核心价值:树洞卖"匿名安全感"所以默认匿、通讯录卖"可靠联系"所以默认公开,两个开关的默认取向都服务于产品定位。
十四、扩展思考:通讯录的完整产品能力矩阵
我的页之外,通讯录产品的能力矩阵值得完整勾勒:
- 联系层:呼叫(📞 拨号)、消息(站内私聊)、群聊(班级群)——联系是通讯录的"出口",三种联系方式覆盖"电话/私聊/群聊"。
- 组织层:班级统计(45 人)、公告(事务通知)、相册(集体记忆)——组织是通讯录的"内容",班级页承载。
- 身份层:个人资料(姓名/学号/号码)、在线状态(此刻可联系)、隐私开关(信息可见性)——身份是通讯录的"底座",我的页承载。
- 数据层:通话记录(呼叫历史)、常用联系人(高频圈)、最近通话(近期互动)——数据是通讯录的"足迹",我的页统计承载。
- 联动闭环:联系(呼叫)→ 记录(通话记录)→ 沉淀(常用联系人)——通讯录的三层能力形成"联系 → 记录 → 沉淀"的完整闭环,我的页正是这个闭环的终端。
十五、开发者视角:我的页的调试与验证技巧
- 负 margin 叠层:统计卡
margin({ top: -24 })悬浮,构建顺序(UserCard 先、StatRow 后)决定 StatRow 在上层——调整构建顺序需配合zIndex(系列同款坑)。 - Toggle 状态与全局联动:
publicPhone开关状态应影响通讯录的号码可见性(他人视角)——Demo 开关独立,产品化需全局状态(AppStorage)驱动,验证开关与呼叫可用性的联动。 - 统计数字的跨页核对:45/12/8 与首页(45 名成员)、班级页(45 人)、通讯录页(5 位展示)的核对——"数字处处有出处"的系列标准。
- 姓名的一致性:"张娜"在 UserCard 与通讯录页第一位成员——修改任一页需同步(同一用户数据源)。
- 菜单 key 长期方案:新增菜单项改用 id 作 key(当前
m.title5 项内唯一)——系列标准建议。 - Toggle 的语义色:
selectedColor(C.primary)蓝色开启态——与树洞(紫)、本页(蓝)的开关色调跟随主题色,验证主题色变化时开关同步(系列主题色驱动的惯例)。
十六、FAQ 与一句话总结
Q6:隐私开关影响哪些地方?
"公开手机号"关闭后——通讯录成员详情不显示号码、首页/通讯录的 📞 呼叫不可用(转站内消息)、他人无法直接拨号——一处开关、全链路生效,产品化用全局状态驱动。
Q7:我的页为什么展示真实姓名而不是昵称?
通讯录是实名产品——班级互查联系方式的前提是实名可信,UserCard"计科2022级 · 张娜"即实名身份(与匿名 App 41/42 形成对比);若用户想隐藏姓名,可设"昵称显示"(实名数据、昵称展示)。
Q8:最近通话 8 次是怎么统计的?
📞 呼叫动作的累计(每次呼叫 +1)——产品化后从通话记录表统计"近 30 天呼叫次数",与"📞 通话记录"菜单明细联动。
Q9:常用联系人 12 人是自动的还是手动的?
可自动(按呼叫频率 Top12)+ 手动置顶——自动学习 + 手动控制结合,我的页"12 常用联系人"是常用圈的数据沉淀。
Q10:本页与系列其他 App 个人中心的隐私设计有什么不同?
其他 App 的隐私在"设置"菜单里(App 41/42 的隐私设置项),本页把隐私独立成卡(PrivacyCard 置顶展示)——因为"号码可见性"是通讯录的高频隐私(用户常调),需要常驻可见;低相关性的隐私进菜单,高频隐私独立成卡,隐私的层级跟随使用频率。这些 FAQ 与前面的拆解共同构成本页的完整解析。