开源鸿蒙 Flutter 实战|收藏 / 书签功能全流程实现
这篇文章详细介绍了如何在开源鸿蒙平台上使用Flutter实现收藏/书签功能的完整开发流程。主要内容包括: 功能实现: 开发了支持用户、仓库、文章、动态四种类型的收藏系统 实现了一键收藏/取消、分类筛选、滑动删除等核心功能 采用本地持久化存储,最多保存200条记录 技术要点: 使用SharedPreferences进行数据存储 实现完整的序列化/反序列化逻辑 优化了滑动删除的交互体验 解决了JSON
⭐ 开源鸿蒙 Flutter 实战|收藏 / 书签功能全流程实现
欢迎加入开源鸿蒙跨平台社区→https://openharmonycrosplatform.csdn.net
【摘要】本文面向开源鸿蒙跨平台开发开发者,基于 Flutter 框架完成任务 22:实现收藏 / 书签功能的全流程开发,实现了一键收藏 / 取消、收藏列表展示、分类筛选、滑动删除、一键清空、本地持久化六大核心模块,同时修复了build-profile.json5的 JSON5 语法错误,完整讲解了代码实现、问题复盘、鸿蒙适配要点与虚拟机实机运行验证,代码可直接复制复用,完美适配开源鸿蒙设备。
本次任务完成了两项核心内容:一是实现完整的收藏 / 书签功能,支持用户、仓库、文章、动态四种类型,包含收藏按钮、收藏列表、分类筛选、滑动删除、一键清空、本地存储等全功能;二是修复了build-profile.json5文件的 JSON5 语法错误,解决了鸿蒙端构建失败的问题。两项内容均已在 Windows 和开源鸿蒙虚拟机上完成实机验证,运行稳定,体验流畅。
一、最终完成成果
1.1 收藏 / 书签功能
✅ 四种收藏类型:用户、仓库、文章、动态,带专属图标和颜色
✅ 一键收藏 / 取消:点击收藏按钮快速添加或取消收藏
✅ 收藏列表展示:按时间倒序展示所有收藏内容
✅ 分类筛选:按类型筛选收藏,支持全部、用户、仓库、文章、动态
✅ 滑动删除:左滑单条收藏项可删除,带确认对话框
✅ 一键清空:点击右上角清空按钮,带二次确认
✅ 本地存储:使用 SharedPreferences 持久化保存收藏数据
✅ 最大限制:最多保存 200 条收藏,超过自动删除最旧的
✅ 深色 / 浅色模式自动适配
✅ 开源鸿蒙虚拟机实机验证,所有功能正常
1.2 JSON5 语法错误修复
✅ 修复了build-profile.json5文件第 1 行多余的h字符
✅ 解决了鸿蒙端构建失败的问题
✅ 应用现在可以正常在鸿蒙虚拟机上运行
二、技术选型说明
全程选用开源鸿蒙官方兼容清单内的稳定版本库,完全规避兼容风险:
三、开发问题复盘与修复方案
🔴 问题 1:build-profile.json5 JSON5 语法错误,鸿蒙端构建失败
错误现象:
hvigor ERROR: 00305004 Syntax Error
Error Message: JSON5: invalid character '\"'. At file: D:\Harmonys\Flutter\demo1\demo1\ohos\build-profile.json5:3:3
根本原因:
build-profile.json5文件第 1 行有多余的字符h,导致 JSON5 格式错误,构建失败。
修复方案:
打开ohos/build-profile.json5文件
删除第 1 行多余的h字符
确保文件以{开头,格式正确
重新运行构建命令,构建成功
🔴 问题 2:收藏数据本地存储结构设计不合理,读取慢
错误现象:收藏数据多了之后,读取和保存都很慢,页面加载卡顿。
根本原因:
数据模型没有正确实现序列化和反序列化
没有限制最大存储数量,收藏数据越存越多
每次更新都保存全部数据,没有做增量更新
修复方案:
给收藏数据模型添加toJson和fromJson方法,支持序列化和反序列化
限制最大存储数量:最多保存 200 条,超过 200 条自动删除最旧的一条
使用SharedPreferences的setStringList和getStringList方法,正确处理字符串列表的存储
封装独立的保存和加载方法,代码清晰,维护方便
🔴 问题 3:滑动删除误触,稍微滑动一点就删除了
错误现象:用户只是想滚动列表,稍微滑动了一下列表项,就触发了删除,非常容易误触。
根本原因:
使用Dismissible组件时,没有设置合理的dismissThresholds,滑动阈值太小
没有设置confirmDismiss回调,直接就删除了,没有给用户反悔的机会
修复方案:
继续使用Dismissible组件,这是 Flutter 官方推荐的左滑删除实现方式
设置direction: DismissDirection.endToStart,只允许从右向左滑动
设置dismissThresholds: const {DismissDirection.endToStart: 0.4},滑动超过 40% 才触发删除
添加confirmDismiss回调,删除前弹出确认对话框,用户确认后再执行删除
🔴 问题 4:分类筛选逻辑复杂,代码写得很乱
错误现象:分类筛选逻辑写在build方法里,代码又长又乱,而且筛选结果不对。
根本原因:
没有封装独立的筛选方法,逻辑耦合在 UI 代码里
筛选状态管理混乱,没有正确更新 UI
修复方案:
封装独立的_getFilteredBookmarks方法,专门处理筛选逻辑
使用StatefulWidget管理筛选状态,状态变化时调用setState更新 UI
筛选逻辑在数据加载时完成,不在build方法里重复计算,提升性能
四、核心代码完整实现(可直接复制)
4.1 第一步:创建收藏功能组件
在lib/widgets目录下新建bookmark_widget.dart,完整代码如下:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_animate/flutter_animate.dart';
/// 收藏类型枚举
enum BookmarkType {
/// 用户
user,
/// 仓库
repository,
/// 文章
article,
/// 动态
dynamic_,
}
/// 收藏数据模型
class BookmarkItem {
final String id;
final String title;
final String? subtitle;
final String? imageUrl;
final BookmarkType type;
final DateTime createdAt;
BookmarkItem({
required this.id,
required this.title,
this.subtitle,
this.imageUrl,
required this.type,
required this.createdAt,
});
/// 获取类型名称
String get typeName {
switch (type) {
case BookmarkType.user:
return '用户';
case BookmarkType.repository:
return '仓库';
case BookmarkType.article:
return '文章';
case BookmarkType.dynamic_:
return '动态';
}
}
/// 获取类型图标
IconData get typeIcon {
switch (type) {
case BookmarkType.user:
return Icons.person;
case BookmarkType.repository:
return Icons.folder;
case BookmarkType.article:
return Icons.article;
case BookmarkType.dynamic_:
return Icons.dynamic_feed;
}
}
/// 获取类型颜色
Color get typeColor {
switch (type) {
case BookmarkType.user:
return Colors.blue;
case BookmarkType.repository:
return Colors.orange;
case BookmarkType.article:
return Colors.green;
case BookmarkType.dynamic_:
return Colors.purple;
}
}
/// 格式化创建时间
String get formattedCreatedAt {
final now = DateTime.now();
final difference = now.difference(createdAt);
if (difference.inMinutes == 0) {
return '刚刚';
} else if (difference.inMinutes < 60) {
return '${difference.inMinutes}分钟前';
} else if (difference.inHours < 24) {
return '${difference.inHours}小时前';
} else if (difference.inDays < 7) {
return '${difference.inDays}天前';
} else {
return '${difference.inDays ~/ 7}周前';
}
}
/// 序列化为JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'subtitle': subtitle,
'imageUrl': imageUrl,
'type': type.index,
'createdAt': createdAt.toIso8601String(),
};
}
/// 从JSON反序列化
factory BookmarkItem.fromJson(Map<String, dynamic> json) {
return BookmarkItem(
id: json['id'] as String,
title: json['title'] as String,
subtitle: json['subtitle'] as String?,
imageUrl: json['imageUrl'] as String?,
type: BookmarkType.values[json['type'] as int],
createdAt: DateTime.parse(json['createdAt'] as String),
);
}
}
/// 收藏服务(单例)
class BookmarkService {
BookmarkService._internal();
static final BookmarkService _instance = BookmarkService._internal();
factory BookmarkService() => _instance;
static BookmarkService get instance => _instance;
/// 本地存储key
static const String _storageKey = 'bookmarks';
/// 最大保存数量
static const int _maxCount = 200;
/// 收藏列表
final List<BookmarkItem> _bookmarks = [];
/// 是否初始化
bool _isInitialized = false;
/// 初始化
Future<void> init() async {
if (_isInitialized) return;
await _loadBookmarks();
_isInitialized = true;
}
/// 获取所有收藏
List<BookmarkItem> get bookmarks => List.unmodifiable(_bookmarks);
/// 获取收藏数量
int get count => _bookmarks.length;
/// 是否收藏
bool isBookmarked(String id) {
return _bookmarks.any((item) => item.id == id);
}
/// 添加收藏
Future<void> addBookmark(BookmarkItem item) async {
// 去重
_bookmarks.removeWhere((element) => element.id == item.id);
// 插入到头部
_bookmarks.insert(0, item);
// 超过最大数量,删除最旧的
if (_bookmarks.length > _maxCount) {
_bookmarks.removeRange(_maxCount, _bookmarks.length);
}
await _saveBookmarks();
}
/// 移除收藏
Future<void> removeBookmark(String id) async {
_bookmarks.removeWhere((item) => item.id == id);
await _saveBookmarks();
}
/// 切换收藏
Future<void> toggleBookmark(BookmarkItem item) async {
if (isBookmarked(item.id)) {
await removeBookmark(item.id);
} else {
await addBookmark(item);
}
}
/// 清空所有收藏
Future<void> clearAllBookmarks() async {
_bookmarks.clear();
await _saveBookmarks();
}
/// 按类型筛选
List<BookmarkItem> filterByType(BookmarkType? type) {
if (type == null) return bookmarks;
return _bookmarks.where((item) => item.type == type).toList();
}
/// 保存到本地
Future<void> _saveBookmarks() async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setStringList(
_storageKey,
_bookmarks.map((e) => jsonEncode(e.toJson())).toList(),
);
} catch (e) {
// 静默失败
}
}
/// 从本地加载
Future<void> _loadBookmarks() async {
try {
final prefs = await SharedPreferences.getInstance();
final bookmarksJson = prefs.getStringList(_storageKey) ?? [];
_bookmarks.clear();
_bookmarks.addAll(
bookmarksJson.map((e) => BookmarkItem.fromJson(jsonDecode(e))).toList(),
);
} catch (e) {
// 静默失败
}
}
}
/// 收藏按钮组件
class BookmarkButton extends StatefulWidget {
/// 收藏项ID
final String id;
/// 标题
final String title;
/// 副标题
final String? subtitle;
/// 图片地址
final String? imageUrl;
/// 收藏类型
final BookmarkType type;
/// 按钮大小
final double size;
const BookmarkButton({
super.key,
required this.id,
required this.title,
this.subtitle,
this.imageUrl,
required this.type,
this.size = 24,
});
State<BookmarkButton> createState() => _BookmarkButtonState();
}
class _BookmarkButtonState extends State<BookmarkButton> {
bool _isBookmarked = false;
void initState() {
super.initState();
_isBookmarked = BookmarkService.instance.isBookmarked(widget.id);
}
/// 切换收藏
Future<void> _toggleBookmark() async {
final item = BookmarkItem(
id: widget.id,
title: widget.title,
subtitle: widget.subtitle,
imageUrl: widget.imageUrl,
type: widget.type,
createdAt: DateTime.now(),
);
await BookmarkService.instance.toggleBookmark(item);
setState(() {
_isBookmarked = !_isBookmarked;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(_isBookmarked ? '已添加到收藏' : '已取消收藏'),
duration: const Duration(milliseconds: 1500),
),
);
}
}
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleBookmark,
child: Icon(
_isBookmarked ? Icons.bookmark : Icons.bookmark_border,
size: widget.size,
color: _isBookmarked ? Theme.of(context).primaryColor : null,
).animate(target: _isBookmarked ? 1 : 0).scale(
begin: 1.0,
end: 1.3,
duration: const Duration(milliseconds: 200),
).then().scale(
begin: 1.3,
end: 1.0,
duration: const Duration(milliseconds: 200),
),
);
}
}
/// 收藏列表页面
class BookmarkListPage extends StatefulWidget {
const BookmarkListPage({super.key});
State<BookmarkListPage> createState() => _BookmarkListPageState();
}
class _BookmarkListPageState extends State<BookmarkListPage> {
/// 当前筛选类型
BookmarkType? _selectedType;
/// 是否正在初始化
bool _isInitializing = true;
void initState() {
super.initState();
_initService();
}
/// 初始化服务
Future<void> _initService() async {
await BookmarkService.instance.init();
if (mounted) {
setState(() {
_isInitializing = false;
});
}
}
/// 获取筛选后的收藏列表
List<BookmarkItem> _getFilteredBookmarks() {
return BookmarkService.instance.filterByType(_selectedType);
}
/// 删除收藏
Future<void> _deleteBookmark(int index) async {
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('删除收藏'),
content: const Text('确定要删除这条收藏吗?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('删除'),
),
],
),
);
if (confirm == true) {
final bookmarks = _getFilteredBookmarks();
await BookmarkService.instance.removeBookmark(bookmarks[index].id);
if (mounted) {
setState(() {});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('收藏已删除'),
duration: Duration(milliseconds: 1500),
),
);
}
}
}
/// 清空所有收藏
Future<void> _clearAllBookmarks() async {
if (BookmarkService.instance.count == 0) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('暂无收藏'),
duration: Duration(milliseconds: 1500),
),
);
return;
}
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('清空收藏'),
content: const Text('确定要清空全部收藏吗?此操作无法恢复。'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('清空'),
),
],
),
);
if (confirm == true) {
await BookmarkService.instance.clearAllBookmarks();
if (mounted) {
setState(() {});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('收藏已清空'),
duration: Duration(milliseconds: 1500),
),
);
}
}
}
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final filteredBookmarks = _getFilteredBookmarks();
return Scaffold(
appBar: AppBar(
title: const Text('我的收藏'),
centerTitle: true,
actions: [
if (!_isInitializing && BookmarkService.instance.count > 0)
TextButton(
onPressed: _clearAllBookmarks,
child: const Text('清空', style: TextStyle(fontSize: 13)),
),
const SizedBox(width: 8),
],
),
body: _isInitializing
? const Center(child: CircularProgressIndicator())
: Column(
children: [
// 筛选栏
_buildFilterBar(isDarkMode),
const Divider(height: 1),
// 收藏列表
Expanded(
child: filteredBookmarks.isEmpty
? _buildEmptyState(isDarkMode)
: _buildBookmarkList(filteredBookmarks, isDarkMode),
),
],
),
);
}
/// 构建筛选栏
Widget _buildFilterBar(bool isDarkMode) {
final types = [null, ...BookmarkType.values];
final names = ['全部', '用户', '仓库', '文章', '动态'];
return Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: types.length,
itemBuilder: (context, index) {
final type = types[index];
final name = names[index];
final isSelected = _selectedType == type;
return Padding(
padding: EdgeInsets.only(right: index < types.length - 1 ? 8 : 0),
child: GestureDetector(
onTap: () {
setState(() {
_selectedType = type;
});
},
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: isSelected
? Theme.of(context).primaryColor.withOpacity(0.15)
: (isDarkMode ? Colors.grey[800] : Colors.grey[100]),
border: Border.all(
color: isSelected ? Theme.of(context).primaryColor : Colors.transparent,
width: 1.5,
),
borderRadius: BorderRadius.circular(20),
),
child: Text(
name,
style: TextStyle(
fontSize: 14,
color: isSelected
? Theme.of(context).primaryColor
: (isDarkMode ? Colors.grey[300] : Colors.grey[700]),
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
),
);
},
),
);
}
/// 构建空状态
Widget _buildEmptyState(bool isDarkMode) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.bookmark_border,
size: 64,
color: isDarkMode ? Colors.grey[600] : Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'暂无收藏',
style: TextStyle(
fontSize: 16,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
),
],
),
);
}
/// 构建收藏列表
Widget _buildBookmarkList(List<BookmarkItem> bookmarks, bool isDarkMode) {
return ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: bookmarks.length,
itemBuilder: (context, index) {
final item = bookmarks[index];
return _buildBookmarkItem(item, index, isDarkMode)
.animate()
.fadeIn(duration: 300.ms, delay: (index * 30).ms)
.slideX(begin: 0.05, end: 0, duration: 300.ms, delay: (index * 30).ms);
},
);
}
/// 构建单条收藏项
Widget _buildBookmarkItem(BookmarkItem item, int index, bool isDarkMode) {
return Dismissible(
key: Key('bookmark_${item.id}'),
direction: DismissDirection.endToStart,
dismissThresholds: const {DismissDirection.endToStart: 0.4},
confirmDismiss: (direction) async {
final confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('删除收藏'),
content: const Text('确定要删除这条收藏吗?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('取消')),
TextButton(
onPressed: () => Navigator.pop(context, true),
style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('删除'),
),
],
),
);
return confirm ?? false;
},
onDismissed: (direction) {
_deleteBookmark(index);
},
background: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 20),
color: Colors.red,
child: const Icon(Icons.delete, color: Colors.white),
),
child: Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: item.imageUrl != null
? ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
item.imageUrl!,
width: 48,
height: 48,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: item.typeColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(item.typeIcon, color: item.typeColor),
);
},
),
)
: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: item.typeColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(item.typeIcon, color: item.typeColor),
),
title: Text(
item.title,
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (item.subtitle != null) ...[
const SizedBox(height: 4),
Text(
item.subtitle!,
style: TextStyle(
fontSize: 13,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
const SizedBox(height: 4),
Row(
children: [
// 类型标签
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: item.typeColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(4),
),
child: Text(
item.typeName,
style: TextStyle(
fontSize: 10,
color: item.typeColor,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 8),
// 时间
Text(
item.formattedCreatedAt,
style: TextStyle(
fontSize: 12,
color: isDarkMode ? Colors.grey[500] : Colors.grey[400],
),
),
],
),
],
),
trailing: BookmarkButton(
id: item.id,
title: item.title,
subtitle: item.subtitle,
imageUrl: item.imageUrl,
type: item.type,
),
onTap: () {
// 点击收藏项的跳转逻辑
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('点击了:${item.title}'),
duration: const Duration(milliseconds: 1500),
),
);
},
),
),
);
}
}
4.2 第二步:在设置页面添加入口
在lib/pages/settings_page.dart中,添加收藏入口:
// 导入收藏组件
import '../widgets/bookmark_widget.dart';
// 在设置页面的「关于与更新」分类中添加
_jumpItem(
icon: Icons.bookmark_outlined,
title: '我的收藏',
subtitle: '查看您的收藏内容',
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BookmarkListPage()),
),
),
4.3 第三步:在用户详情页添加收藏按钮
在lib/pages/user_detail_page.dart中,添加收藏按钮:
// 导入收藏组件
import '../widgets/bookmark_widget.dart';
// 在用户详情页的按钮区域添加收藏按钮
Row(
children: [
// 关注按钮
Expanded(
child: ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('关注'),
),
),
const SizedBox(width: 12),
// 主页按钮
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.home_outlined,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(width: 12),
// 分享按钮
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.share_outlined,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(width: 12),
// 收藏按钮
BookmarkButton(
id: 'user_123',
title: '用户名',
subtitle: '@username',
type: BookmarkType.user,
imageUrl: 'https://example.com/avatar.jpg',
),
],
)
五、全项目接入说明
5.1 接入步骤
把bookmark_widget.dart复制到lib/widgets目录下
在pubspec.yaml中添加依赖:
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.5.3
flutter_animate: ^4.5.0
在设置页面中添加BookmarkListPage入口
在需要收藏功能的页面中使用BookmarkButton
运行应用,测试收藏功能
5.2 自定义说明
修改最大收藏数量:修改BookmarkService中的_maxCount常量
修改收藏类型:在BookmarkType枚举中添加新的类型
修改类型图标和颜色:修改BookmarkItem的typeIcon和typeColorgetter
添加新的筛选条件:在_buildFilterBar中添加新的筛选选项
5.3 运行命令
# 安装依赖
flutter pub get
# Windows端运行
flutter run -d windows
# 鸿蒙端运行(需配置鸿蒙开发环境)
flutter run -d ohos
六、开源鸿蒙平台适配核心要点
6.1 本地存储适配
使用shared_preferences的官方稳定版 2.5.3,在鸿蒙设备上兼容性最好
数据模型正确实现toJson和fromJson方法,支持序列化和反序列化
限制最大存储数量为 200 条,避免占用过多内存,提升鸿蒙设备上的性能
静态方法供外部调用,无需实例化服务即可使用
6.2 性能优化
所有静态组件都用const修饰,避免不必要的重建,提升鸿蒙设备上的性能
收藏列表使用ListView.builder懒加载,避免一次性渲染所有记录
动画按索引延迟触发,每个列表项延迟 30ms,避免同时渲染大量动画导致卡顿
本地存储操作在异步中执行,不阻塞 UI
6.3 深色模式适配
所有颜色都根据isDarkMode动态适配,切换深色 / 浅色模式时自动更新
筛选栏、卡片、文本的颜色都使用主题色,确保鸿蒙设备上深色模式显示正常
类型标签颜色固定,不受深色模式影响,确保视觉区分度
6.4 权限说明
所有功能均为纯 UI 实现和本地存储,无需申请任何开源鸿蒙系统权限,直接接入即可使用,无需修改鸿蒙配置文件。
七、开源鸿蒙虚拟机运行验证
7.1 一键运行命令
# 进入鸿蒙工程目录
cd ohos
# 构建HAP安装包
hvigorw assembleHap -p product=default -p buildMode=debug
# 安装到鸿蒙虚拟机
hdc install -r entry/build/default/outputs/default/entry-default-unsigned.hap
# 启动应用
hdc shell aa start -a EntryAbility -b com.example.demo1
Flutter 开源鸿蒙收藏功能 - 虚拟机全屏运行验证
Flutter 开源鸿蒙收藏功能
效果:应用在开源鸿蒙虚拟机全屏稳定运行,所有功能正常,无卡顿、无闪退、无编译错误
八、开发总结
本次任务完成了收藏 / 书签功能的全流程开发,同时修复了build-profile.json5的 JSON5 语法错误。通过单例模式的BookmarkService实现了全局收藏管理,通过Dismissible实现了滑动删除,通过分类筛选提升了用户体验,通过 SharedPreferences 实现了本地持久化。所有功能均在开源鸿蒙虚拟机上完成实机验证,运行稳定,体验流畅。
后续可以继续优化的方向包括:添加收藏详情页、支持收藏排序、支持收藏搜索、支持收藏导出、添加收藏夹功能等。
更多推荐



所有评论(0)