在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

鸿蒙 Next 搭子雷达 App 开发实战:兴趣匹配 + 活动标签 + 在线状态

作者:duluo
SDK 版本:HarmonyOS API 24 (Next)
开发工具:DevEco Studio
语言框架:ArkTS + ArkUI
字数:约 9600 字


目录

  1. 引言
  2. 产品概念与搭子模型
  3. 两 Tab 架构设计
  4. 首页雷达与标签筛选
  5. 搭子卡片组件
  6. 在线状态系统
  7. 详情弹窗与组队流程
  8. 社交类 App 设计模式
  9. 编译错误全记录
  10. 第三十八款 App 全景回顾
  11. 结语

1. 引言

1.1 搭子文化的兴起

“搭子”——这是一个在年轻人中流行的词,指的是"一起做某件事的临时伙伴"。饭搭子(一起吃饭)、球搭子(一起运动)、学习搭子(一起自习)、旅游搭子(一起旅行)。

搭子与传统朋友的区别在于:搭子是功能性的,朋友是情感性的。 你不需要和搭子有深厚的感情基础,只需要有一个共同的活动目标。吃完饭可以各回各家,打完球可以不再联系——没有社交压力。

"搭子雷达"App 解决的问题很简单:当你突然想吃饭/运动/学习却找不到人时,快速发现附近也在找搭子的人。

1.2 本 App 的社交定位

本 App 是系列中第五款社交/平台类 App:

App 连接方式 目标
23 情绪漂流瓶 匿名倾诉 情绪释放
29 反向导师 技能教学 知识传递
35 二手流转 物品交易 物品循环
36 共享工具库 工具共享 邻里互助
38 搭子雷达 兴趣匹配 临时社交

1.3 三十八款 App 全景

App 数量:    38
代码总行数:  ~20,000 行
编译错误数:  ~315 个
博客总字数:  ~380,000 字
技术博客数:  38 篇

2. 产品概念与搭子模型

2.1 功能需求

用户故事 1:我想找个人今晚一起吃饭
用户故事 2:我想按活动类型筛选搭子
用户故事 3:我想知道对方是否在线
用户故事 4:我想联系搭子表达组队意向

功能清单:
├── F1: 搭子列表(10 位搭子)
├── F2: 7 个活动标签筛选
├── F3: 搭子卡片(头像/年龄/活动/时间/距离/状态)
├── F4: 在线/忙碌状态标识
├── F5: 详情弹窗
├── F6: 组队操作
├── F7: 已组队列表
└── F8: 在线人数统计

2.2 搭子数据模型

interface Partner {
  id: number;
  name: string;       // 昵称
  emoji: string;      // 头像
  age: number;        // 年龄
  activity: string;   // 找搭子描述
  time: string;       // 时间
  dist: string;       // 距离
  desc: string;       // 详细介绍
  tag: string;        // 活动分类
  status: string;     // 在线状态
}

10 个字段,比标准平台类模型多了 activitytimestatus 三个字段。activitytime 是搭子的核心信息——“找什么搭子"和"什么时候”。

2.3 10 位搭子的覆盖

活动类型 搭子数 时间分布
吃饭 1 今晚
运动 3 明天/每天/周末
学习 1 今晚
娱乐 2 周五/晚上
社交 2 今天/每天
户外 1 下周末

运动类最多(3 位),反映了"运动需要搭子"的真实需求——一个人跑步、打球、骑行确实不如有伴容易坚持。


3. 两 Tab 架构设计

3.1 Tab 配置

build() {
  Stack() {
    Column().backgroundColor(C.bg)
    Column() {
      this.buildHeader()
      if (this.activeTab === 0) this.buildRadarTab()
      else this.buildMyTab()
      this.buildTabBar()
    }
    if (this.showDetail) this.buildDetailOverlay()
  }
}
Tab 图标 功能
0 📡 雷达 — 浏览搭子列表
1 🤝 搭子 — 已组队列表

3.2 顶部栏在线统计

Text('附近 ' + this.getOnlineCount() + ' 人在线')

getOnlineCount() 遍历所有搭子,统计 status 为 ‘在线’ 的人数。这个数字在运行时不会变化(因为数据是静态的),但它传达了一个信息——“这个 App 有人用”。


4. 首页雷达与标签筛选

4.1 7 个活动标签

const TAGS: string[] = ['全部', '吃饭', '运动', '学习', '娱乐', '社交', '户外'];

7 个标签覆盖了最常见的搭子类型。标签数量比"共享工具库"(4 个)和"二手流转"(5 个)都多,反映了搭子活动的多样性。

4.2 筛选逻辑

与之前的 App 完全相同——selectedTag 控制筛选,getFiltered() 返回当前分类的数据。这个方法在 ForEach 和详情弹窗两处被调用。


5. 搭子卡片组件

5.1 卡片布局

┌──────────────────────────────────┐
│  👩 小陈 · 28岁        🟢 在线  │
│  找饭搭子                        │
│  ⏰ 今晚6点    📍 500m           │
│                                  │
│  想去新开的日料店尝尝…           │
│                                  │
│  吃饭                💬 组队     │
└──────────────────────────────────┘

五行信息:头像 + 年龄 + 状态(第一行)→ 活动说明(第二行)→ 时间 + 距离(第三行)→ 描述(第四行)→ 分类标签 + 组队按钮(第五行)。

5.2 状态指示器

Text('🟢').fontSize(12)
  .fontColor(p.status === '在线' ? C.accent : C.textMuted)
Text(p.status).fontSize(10).fontColor(C.textMuted)

绿色圆点 + "在线"文字;灰色圆点 + "忙碌中"文字。使用 emoji 圆点(🟢)替代自定义图标,减少了代码量。

5.3 忙碌状态的处理

if (p.status === '在线') {
  // 显示组队按钮
  Text(this.isConnected(p.id) ? '✅ 已联系' : '💬 组队')
} else {
  Text('⏳ 忙碌中').fontSize(13).fontColor(C.textMuted)
}

忙碌状态的搭子不显示组队按钮,只显示灰色"⏳ 忙碌中"文字。


6. 在线状态系统

6.1 状态数据

10 位搭子中,8 位"在线",2 位"忙碌"(小李和小马)。

在线比例 80%,传递了"这个 App 很活跃"的信号。如果所有搭子都在线,显得不真实;如果大部分都不在线,用户会失去兴趣。80% 的在线率是一个经过设计的平衡点。

6.2 在线统计

getOnlineCount(): number {
  let c = 0;
  for (const p of PARTNERS) { if (p.status === '在线') c++; }
  return c;
}

在顶部栏实时显示在线人数。虽然数据是静态的,但展示这个数字本身就能传达"有人在用"的感觉——对社交类 App 来说,这是最重要的信息。


7. 详情弹窗与组队流程

7.1 详情弹窗

┌──────────────────────────────┐
│         👩                    │
│     小陈 · 28岁               │
│     28岁 · 找饭搭子           │
│  ─────────────────────────  │
│  ⏰ 今晚6点    📍 500m        │
│                              │
│  📝 详情                      │
│  想去新开的日料店尝尝…        │
│                              │
│      💬 组队                 │
└──────────────────────────────┘

弹窗展示完整信息:头像(64sp)→ 姓名年龄 → 活动说明 → 分割线 → 时间距离 → 详细介绍 → 组队按钮。

7.2 组队流程

connectPartner(id: number): void {
  this.connected = [id, ...this.connected];
  promptAction.showToast({ message: '🤝 组队成功!快去打个招呼吧' });
}

3 行代码完成组队操作。Toast 提示"快去打个招呼吧"——这是在引导用户的下一步行动。


8. 社交类 App 设计模式

8.1 五款社交类 App 的共性

从 App 23 到 App 38,五款社交类 App 共享以下模式:

模式一:两数组状态

@State interested: number[] = [];  // 或 requests / connected / borrowed

用一维数组存储"已操作"的 ID,用 indexOf() 查询状态。

模式二:卡片 + 详情弹窗
所有社交类 App 都采用"卡片列表 + 点击弹窗查看详情"的模式。卡片展示概要信息,弹窗展示完整信息。

模式三:两 Tab 布局
浏览 Tab + 我的 Tab。浏览 Tab 负责发现,我的 Tab 负责管理。

8.2 五款社交类 App 的差异

App 操作动词 状态数组 特殊元素
23 漂流瓶 捞取/回信 isReplied 随机匹配
29 导师 申请 requests 导师角色
35 二手 联系 interested 价格标签
36 工具 借出/归还 borrowed 归还功能
38 搭子 组队 connected 在线状态

8.3 社交类 App 的最小可行模型

经过 5 款社交类 App 的实践,总结出社交类 App 的最小可行模型:

1 个数组(已操作 ID 列表)
2 个 Tab(浏览 + 我的)
3 个核心方法(isXxx、doXxx、getFiltered)

用这个模型,可以在 250-300 行代码内实现一款完整的社交类 App。


9. 编译错误全记录

9.1 错误概览

本 App 实现 0 个编译错误——系列第四次零错误。

前三次零错误:App 31(慢病管理)、App 33(相亲防骗)、App 37(旧衣盲盒)。

9.2 零错误率趋势

前 10 款:  0/10 = 0%
11-20 款:  0/10 = 0%
21-30 款:  0/10 = 0%
31-38 款:  4/8 = 50%

最近 8 款 App 中 4 款零错误,零错误率 50%。趋势明显——零错误已经成为"新常态"。


10. 第三十八款 App 全景回顾

10.1 数据总览

指标 数值
代码行数 274 行
编译错误数 0 个
@State 变量 4 个
@Builder 方法 6 个
搭子数量 10 位
活动标签 7 个
Tab 数 2 个
弹窗数 1 个
外部依赖 0 个

10.2 社交类 App 代码量对比

App 23: 情绪漂流瓶   447 行
App 29: 反向导师     373 行
App 35: 二手流转     266 行
App 36: 共享工具库   295 行
App 38: 搭子雷达     274 行

社交类 App 的平均代码量约 331 行,呈下降趋势。


11. 结语

11.1 搭子的意义

搭子不是朋友,但比陌生人近一步。它是一种"低承诺、低成本"的社交方式——不需要维护长期关系,不需要考虑对方感受,只需要在共同活动中享受陪伴。

对于在大城市生活的年轻人来说,搭子是一种"刚刚好"的社交距离:不是一个人(孤独),也不是谈恋爱(沉重),只是一起吃顿饭、跑个步、看场电影。

11.2 第四次零错误

第四次零错误的意义:零错误已经成为常规,不再是新闻。

从第一次零错误的"激动"到第四次零错误的"平淡",这个过程本身就是技术成长的体现。当你的代码第一次零错误构建时,你会觉得是运气。当第四次发生时,你会觉得"本来就该这样"。

11.3 给开发者的建议

  1. 社交类 App 用两数组状态就够了——不需要复杂的状态管理库
  2. 在线状态是社交 App 的启动信号——显示"XX 人在线"比显示"XX 件物品"更能促使用户行动
  3. 38 款 App 证明了 ArkUI 的成熟度——从白噪音到搭子雷达,ArkUI 都做得来

11.4 致谢

38 款 App、38 篇博客。感谢每一位读到这里的读者。

现在,打开 DevEco Studio,去创造属于你自己的 App 吧。


附录 A:核心代码速查

在线统计

getOnlineCount(): number {
  let c = 0;
  for (const p of PARTNERS) { if (p.status === '在线') c++; }
  return c;
}

组队操作

connectPartner(id: number): void {
  this.connected = [id, ...this.connected];
  promptAction.showToast({ message: '🤝 组队成功!' });
}

分类筛选

getFiltered(): Partner[] {
  if (this.selectedTag === 0) return PARTNERS;
  const result: Partner[] = [];
  for (const p of PARTNERS) { if (p.tag === TAGS[this.selectedTag]) result.push(p); }
  return result;
}

Logo

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

更多推荐