鸿蒙Flutter实战:24小时新建标签提示组件
用户打开备忘录列表,看到 30 条记录,哪些是刚刚创建的?如果没有任何视觉区分,新老数据混在一起,用户容易迷失。

前言
用户打开备忘录列表,看到 30 条记录,哪些是刚刚创建的?如果没有任何视觉区分,新老数据混在一起,用户容易迷失。
一个简洁而有效的方案是:24 小时内创建的条目右侧渲染一个 “NEW” 角标。这个微交互不占额外空间,不需要用户做任何操作,信息传达却非常清晰——“这条是新的,你刚创建不久”。
本文将拆解这个"24 小时新建标签"的完整实现,包括时间差计算、视觉设计和条件渲染逻辑。
项目仓库:todo_flutter_harmony
需求细化
- 时间窗口:从当前时间往前推 24 小时,在这个窗口内的条目标记为"新"
- 视觉样式:一个圆角小标签,显示 “NEW” 文字,颜色醒目但不喧宾夺主
- 自动过期:24 小时后自动消失,无需手动清除
核心逻辑:时间差计算
Dart 的 DateTime 提供了直观的 difference 方法:
bool isNew(DateTime createdAt) {
final now = DateTime.now();
final difference = now.difference(createdAt);
return difference.inHours < 24;
}
difference 返回一个 Duration 对象,提供了多个单位的访问器:
final duration = DateTime.now().difference(createdAt);
duration.inDays; // 天数差
duration.inHours; // 小时差(绝对值,忽略分钟)
duration.inMinutes; // 分钟差
duration.inSeconds; // 秒数差
注意:inHours 返回的是整数小时(向下取整),所以 difference.inHours < 24 意味着从创建时间到现在的完整小时数小于 24,即创建时间不超过 24 小时。
精确时间差判断
如果需求改为「在创建后的未来 24 小时内显示,之后自动消失」,还需要处理一个边界情况:创建时间在未来(比如设备时间被篡改)。更健壮的写法:
bool isNew(DateTime createdAt) {
final now = DateTime.now();
// 防御:如果创建时间在未来,不算"新"(可能是异常数据)
if (createdAt.isAfter(now)) return false;
// 24 小时内的才是"新"
return now.difference(createdAt).inHours < 24;
}
视觉组件:NewBadge
class NewBadge extends StatelessWidget {
const NewBadge({super.key});
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFF4DB6AC),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'NEW',
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w800,
letterSpacing: 0.5,
),
),
);
}
}
设计考量:
fontSize: 10:小而精,不抢占标题的视觉权重fontWeight: w800:超粗体,在小字号下保持可读性letterSpacing: 0.5:微字间距,让 “NEW” 三个字母不挤在一起borderRadius: 4:小圆角,呼应 Material 3 设计语言- 颜色 #4DB6AC:与主主题的种子色一致
在备忘录卡片中使用
最典型的使用场景是在 MemoCard 的标题右侧:
class MemoCard extends StatelessWidget {
final Memo memo;
const MemoCard({super.key, required this.memo});
Widget build(BuildContext context) {
return Card(
elevation: 1,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行:标题 + 可选 NEW 标签
Row(
children: [
Flexible(
child: Text(
memo.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (_isNew(memo.createdAt)) ...[
const SizedBox(width: 6),
const NewBadge(),
],
],
),
const SizedBox(height: 6),
// 内容预览
Text(
memo.content,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
height: 1.4,
),
),
],
),
),
],
),
),
);
}
bool _isNew(DateTime createdAt) {
final now = DateTime.now();
if (createdAt.isAfter(now)) return false;
return now.difference(createdAt).inHours < 24;
}
}
优化:避免在 build 中调用 DateTime.now()
直接在 build 方法中调用 DateTime.now() 有一个问题:widget 每次重建都会重新计算,但这个结果只有到下一小时才可能变化。
对于简单的场景这不是性能瓶颈。但如果列表很长(100+ 条),可以在 Provider 层面做一次判断,然后把结果缓存在内存中:
class MemoProvider extends ChangeNotifier {
List<Memo> _memos = [];
List<Memo> get memos => _memos;
// 缓存 NEW 标记结果,避免在 build 中重复计算
final Map<int, bool> _newCache = {};
bool isNew(Memo memo) {
return _newCache.putIfAbsent(memo.id!, () {
final diff = DateTime.now().difference(memo.createdAt);
return diff.inHours < 24;
});
}
void loadMemos() async {
_memos = await DatabaseHelper.instance.getAllMemos();
_newCache.clear(); // 重新加载时清空缓存
notifyListeners();
}
}
然后在 MemoCard 中用 context.watch<MemoProvider>().isNew(memo) 替代本地计算。这样每当 notifyListeners() 触发时,DateTime.now() 只会在 Provider 层执行一次。
进阶:相对时间显示
“NEW” 标签适合 24 小时内的新条目。过了 24 小时后,可以显示相对时间,让用户知道这条备忘录是"多久之前"创建的:
String _formatRelativeTime(DateTime createdAt) {
final diff = DateTime.now().difference(createdAt);
if (diff.inMinutes < 1) return '刚刚';
if (diff.inMinutes < 60) return '${diff.inMinutes}分钟前';
if (diff.inHours < 24) return '${diff.inHours}小时前';
if (diff.inDays < 7) return '${diff.inDays}天前';
if (diff.inDays < 30) return '${(diff.inDays / 7).floor()}周前';
return DateFormat('yyyy-MM-dd').format(createdAt);
}
结合显示逻辑:
Widget _buildTimeAgo(DateTime createdAt) {
final isNew = DateTime.now().difference(createdAt).inHours < 24;
final relativeTime = _formatRelativeTime(createdAt);
return Row(
children: [
if (isNew) const NewBadge(),
if (isNew) const SizedBox(width: 6),
Text(
relativeTime,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade400,
),
),
],
);
}
效果:
- 5 分钟前创建的:
[NEW] 5分钟前 - 昨天创建的:
1天前(NEW 标签已消失) - 一周前创建的:
1周前
鸿蒙兼容性
整个组件仅依赖 DateTime.now() 和 Duration 这些 Dart 核心库的类型,与平台完全无关。Container + BorderRadius 的样式渲染在 Flutter 渲染管线中统一处理。
唯一需要注意的是设备时间准确性——如果用户设备时间不准确(比如手动调到了未来),“NEW” 标签可能永远不会显示或永远不会消失。但实际上这是所有平台共有的问题,不算是鸿蒙特有问题。
总结
24 小时 NEW 标签的实现可以用五行代码概括核心逻辑:
if (DateTime.now().difference(item.createdAt).inHours < 24) {
return const NewBadge();
}
加上视觉设计(Container + Text 的组合),总共不到 30 行代码。它虽小,却是用户体验中"润物细无声"的那一类——用户可能不会主动注意到它的存在,但它让新数据一目了然,降低了认知负载。
完整项目代码见:todo_flutter_harmony
更多推荐



所有评论(0)