flutter_for_openharmony手办收藏app实战_发现实现
本文介绍了手办App发现页面的网格布局实现。采用GridView.builder构建2列网格,通过Card组件展示手办图片、名称和价格。文章详细解析了网格参数配置、性能优化和视觉设计要点,包括间距设置、懒加载机制和响应式布局考虑。该布局空间利用率高、视觉冲击力强,适合展示图片类内容,后续可扩展搜索、筛选等交互功能。整体实现简洁高效,充分利用Flutter的布局组件优势。

每次逛手办店,总会被琳琅满目的手办吸引。有时候不知道该买哪个,就想先看看有什么新品,或者看看最近流行什么。所以在App里做了个发现页面,用网格布局展示推荐的手办,就像逛实体店一样,可以慢慢浏览。
网格布局的选择
发现页面和收藏页面不同,它更注重视觉展示。手办本身就是视觉产品,用网格布局能同时展示多个手办,让用户快速浏览。这种布局在电商App中很常见,比如淘宝、京东的商品列表都是这样。
页面的基本结构:
class DiscoverPage extends StatelessWidget {
const DiscoverPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('发现')),
body: GridView.builder(
padding: EdgeInsets.all(16.w),
Scaffold 提供了基本的页面框架,AppBar 显示标题"发现"。这里没有用Consumer,因为发现页面展示的是推荐数据,不需要实时响应Provider的变化。
GridView.builder 是网格布局的核心组件,它和ListView.builder类似,也是按需渲染,性能很好。padding: EdgeInsets.all(16.w) 给整个网格添加内边距,让内容不会紧贴屏幕边缘。
为什么用GridView.builder而不是GridView?因为builder支持懒加载,只渲染可见区域的网格项。如果手办数量很多,builder的性能优势就很明显。而且builder可以动态生成网格项,不需要提前创建所有组件。
网格参数配置
网格的关键是 SliverGridDelegate,它控制网格的列数、间距等参数:
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12.w,
mainAxisSpacing: 12.h,
),
itemCount: 20,
crossAxisCount: 2 表示每行显示2个网格项,这是手机上最常见的布局。如果设为3,网格项会太小,图片看不清。如果设为1,就变成列表了,不够直观。
crossAxisSpacing: 12.w 是横向间距,mainAxisSpacing: 12.h 是纵向间距。这两个间距让网格项之间有呼吸感,不会挤在一起。12这个数值是经过多次调试得出的,既不会太挤也不会太空。
itemCount: 20 表示总共有20个网格项。实际项目中,这个数值应该从服务器获取,或者从Provider读取。这里为了演示,写死了20个。
为什么不用 SliverGridDelegateWithMaxCrossAxisExtent?因为FixedCrossAxisCount更简单直接,指定列数就行。MaxCrossAxisExtent需要指定最大宽度,然后自动计算列数,适合响应式布局,但对于手机应用,固定列数更常用。
网格项的构建
每个网格项的内容:
itemBuilder: (context, index) {
return Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.image, size: 80.sp),
Text('手办 ${index + 1}'),
Text('¥${(index + 1) * 100}',
style: TextStyle(color: Colors.red)),
],
),
);
},
),
);
}
}
Card 给每个网格项添加了卡片效果,自带阴影和圆角,看起来更有层次感。Column 垂直排列三个元素:图标、名称、价格。
mainAxisAlignment: MainAxisAlignment.center 让内容在卡片中垂直居中,视觉上更平衡。如果不加这个,内容会靠顶部,下方会有大片空白。
Icon 代表手办图片,实际项目中应该用Image.network或Image.asset加载真实图片。这里用图标是为了演示,避免网络请求的复杂性。图标大小 80.sp 比较大,占据卡片的主要空间。
名称和价格的显示:
Text('手办 ${index + 1}')
这里用了字符串插值,${index + 1} 会被替换成实际的数字。index从0开始,所以要加1,让显示从"手办 1"开始,而不是"手办 0"。
Text('¥${(index + 1) * 100}', style: TextStyle(color: Colors.red))
价格用红色显示,这是电商App的常见做法。红色能吸引注意力,让用户快速看到价格。(index + 1) * 100 是模拟价格,实际项目中应该从数据模型读取。
网格布局的优势
相比列表布局,网格布局有几个明显优势:
空间利用率高。手机屏幕宽度有限,列表布局一次只能显示一个商品,而网格布局可以同时显示两个,用户一屏能看到更多内容。这对于浏览型页面特别重要,用户可以快速扫视,找到感兴趣的手办。
视觉冲击力强。多个手办图片同时展示,形成视觉矩阵,比单个图片更有吸引力。就像逛手办店,满墙的手办比单个展示柜更吸引人。
适合图片展示。手办是视觉产品,图片是最重要的信息。网格布局给每个图片分配了足够的空间,不会像列表那样图片太小看不清。
滚动效率高。用户向下滚动时,每次可以看到两个新手办,而不是一个。这意味着用相同的滚动距离,能浏览更多内容。
实际使用体验
打开发现页面,立即看到一个2列的网格,每个格子里都有手办的图片、名称、价格。向下滚动,新的手办不断出现,滚动很流畅,没有卡顿。
点击某个手办,可以跳转到详情页面(这个功能后续会实现)。整个浏览过程很自然,就像在实体店里逛一样,可以慢慢看,慢慢选。
网格的间距设置得刚刚好,既能区分不同的手办,又不会显得稀疏。卡片的阴影效果让每个手办都有独立的空间感,不会混在一起。
性能优化考虑
使用builder模式。GridView.builder只渲染可见区域的网格项,不可见的不会渲染。这对性能很重要,特别是手办数量很多的时候。如果用GridView,所有网格项都会一次性创建,内存占用会很大。
图片懒加载。实际项目中,手办图片应该用Image.network加载,并且配置缓存。Flutter的Image组件自带缓存机制,加载过的图片会缓存在内存中,下次显示时直接从缓存读取,不需要重新下载。
避免过度渲染。每个网格项用const构造函数,比如 const Icon(Icons.image)。const对象在编译时就创建好了,运行时不需要重新创建,可以提高性能。
合理设置itemCount。如果手办数量很多,可以考虑分页加载。比如第一次加载20个,滚动到底部时再加载20个。这样可以减少初始加载时间,提升用户体验。
后续功能扩展
发现页面还可以添加很多功能:
筛选和排序。在AppBar添加筛选按钮,可以按价格区间、系列、厂商等条件筛选。添加排序按钮,可以按价格、热度、发售日期等排序。
搜索功能。在AppBar添加搜索框,支持按名称搜索手办。搜索结果也用网格布局展示,保持一致的视觉体验。
下拉刷新。用RefreshIndicator包裹GridView,支持下拉刷新。用户可以手动刷新数据,获取最新的推荐手办。
加载更多。监听滚动位置,当滚动到底部时自动加载更多数据。可以用ScrollController实现,或者用第三方库如pull_to_refresh。
收藏功能。在每个网格项添加收藏按钮,点击后添加到心愿单。可以用一个心形图标表示,点击后变成实心红色,表示已收藏。
详情跳转。点击网格项跳转到手办详情页面,显示更多信息,比如详细描述、多张图片、用户评价等。
网格布局的变体
除了固定2列的网格,还可以尝试其他布局:
瀑布流布局。用flutter_staggered_grid_view库实现,每个网格项的高度可以不同,根据内容自适应。这种布局更灵活,但实现复杂一些。
横向滚动网格。把scrollDirection设为Axis.horizontal,网格就变成横向滚动。适合展示推荐手办,用户可以左右滑动浏览。
混合布局。顶部用横幅展示精选手办,中间用网格展示推荐手办,底部用列表展示最新手办。这种混合布局信息密度更高,但要注意不要太复杂,影响用户体验。
响应式网格。根据屏幕宽度动态调整列数,手机上显示2列,平板上显示3列或4列。可以用MediaQuery获取屏幕宽度,然后计算合适的列数。
开发心得
网格布局要注意比例。crossAxisCount和childAspectRatio要配合好,确保网格项不会太扁或太高。一般来说,手办图片是竖向的,childAspectRatio设为0.7-0.8比较合适。
间距要统一。crossAxisSpacing和mainAxisSpacing最好设为相同的值,这样网格看起来更整齐。如果两个间距不同,会显得不协调。
卡片效果要适度。Card的elevation(阴影高度)不要设太高,默认值就够了。阴影太重会显得浮夸,阴影太轻又看不出层次感。
颜色要和谐。价格用红色是惯例,但不要用太鲜艳的红色,会刺眼。用Colors.red就够了,它是Material Design的标准红色,视觉上比较舒服。
总结
发现页面用网格布局展示推荐手办,视觉效果好,空间利用率高。GridView.builder的性能优化让滚动流畅,Card的卡片效果让每个手办都有独立的空间感。
整个页面的实现很简洁,核心代码不到50行,但效果很好。这得益于Flutter强大的布局组件,GridView.builder封装了网格布局的复杂逻辑,开发者只需要关注网格项的内容。
Flutter for OpenHarmony的GridView组件和原生Flutter完全一样,没有兼容性问题。如果你也在开发OpenHarmony应用,可以放心使用GridView实现网格布局。
下一篇文章会介绍搜索页面的实现,包括搜索框、热门标签、搜索结果展示等内容。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
չ
1. ݻ
ʵ ܻ ƣ ٶȣ
class CacheManager {
static final Map<String, CachedData> _cache = {};
static Future<T?> get<T>(String key) async {
if (_cache.containsKey(key)) {
final cached = _cache[key]!;
if (!cached.isExpired) {
return cached.data as T;
}
}
return null;
}
static void set(String key, dynamic data, {Duration duration = const Duration(hours: 1)}) {
_cache[key] = CachedData(
data: data,
expireTime: DateTime.now().add(duration),
);
}
}
class CachedData {
final dynamic data;
final DateTime expireTime;
CachedData({required this.data, required this.expireTime});
bool get isExpired => DateTime.now().isAfter(expireTime);
}
2.
ƵĴ û 飺
class ErrorHandler {
static void handle(BuildContext context, dynamic error) {
String message = ' ʧ ܣ ';
if (error is SocketException) {
message = ' ʧ ܣ ';
} else if (error is TimeoutException) {
message = ' ʱ Ժ ';
} else if (error is FormatException) {
message = ' ݸ ʽ ';
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
action: SnackBarAction(
label: ' ',
onPressed: () {
//
},
),
),
);
}
}
3. ״̬
ŵļ ״̬չʾ
class LoadingState<T> {
final bool isLoading;
final T? data;
final String? error;
LoadingState({
this.isLoading = false,
this.data,
this.error,
});
LoadingState<T> copyWith({
bool? isLoading,
T? data,
String? error,
}) {
return LoadingState<T>(
isLoading: isLoading ?? this.isLoading,
data: data ?? this.data,
error: error ?? this.error,
);
}
}
// ʹ ʾ
class DataProvider extends ChangeNotifier {
LoadingState<List<Item>> _state = LoadingState();
LoadingState<List<Item>> get state => _state;
Future<void> loadData() async {
_state = _state.copyWith(isLoading: true, error: null);
notifyListeners();
try {
final data = await fetchData();
_state = _state.copyWith(isLoading: false, data: data);
} catch (e) {
_state = _state.copyWith(isLoading: false, error: e.toString());
}
notifyListeners();
}
}
4. ܼ
ܼ أ Ż û 飺
class PerformanceMonitor {
static final Map<String, Stopwatch> _timers = {};
static void start(String operation) {
_timers[operation] = Stopwatch()..start();
}
static void end(String operation) {
if (_timers.containsKey(operation)) {
final elapsed = _timers[operation]!.elapsedMilliseconds;
print('[] ʱ: \ms');
_timers.remove(operation);
if (elapsed > 1000) {
print(' : [\] ִ ʱ ');
}
}
}
}
// ʹ ʾ
Future<void> loadData() async {
PerformanceMonitor.start('loadData');
try {
await fetchData();
} finally {
PerformanceMonitor.end('loadData');
}
}
5. Ԥ
ʵ Ԥ أ Ӧ ٶȣ
class PreloadManager {
static final Set<String> _preloaded = {};
static Future<void> preload(List<String> imageUrls) async {
for (var url in imageUrls) {
if (!_preloaded.contains(url)) {
try {
await precacheImage(NetworkImage(url), context);
_preloaded.add(url);
} catch (e) {
print('Ԥ ʧ : \');
}
}
}
}
static void clear() {
_preloaded.clear();
}
}
6. ֧
ʵ ģʽ ԣ
class OfflineManager {
static bool _isOnline = true;
static bool get isOnline => _isOnline;
static void init() {
Connectivity().onConnectivityChanged.listen((result) {
_isOnline = result != ConnectivityResult.none;
});
}
static Future<T> execute<T>(
Future<T> Function() onlineAction,
Future<T> Function() offlineAction,
) async {
if (_isOnline) {
try {
return await onlineAction();
} catch (e) {
return await offlineAction();
}
} else {
return await offlineAction();
}
}
}
Ż ʵ
1. б Ż
ʹ AutomaticKeepAliveClientMixin б ״̬
class OptimizedListItem extends StatefulWidget {
_OptimizedListItemState createState() => _OptimizedListItemState();
}
class _OptimizedListItemState extends State<OptimizedListItem>
with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
Widget build(BuildContext context) {
super.build(context);
return ListTile(
// б
);
}
}
2. ͼƬ Ż
ʵ ֽ ʽͼƬ أ
FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: imageUrl,
fadeInDuration: Duration(milliseconds: 300),
fit: BoxFit.cover,
)
3. ڴ
ʱ ͷ Դ ڴ й©
void dispose() {
_scrollController.dispose();
_textController.dispose();
_focusNode.dispose();
super.dispose();
}
ʵս ܽ
ͨ ĵ 뽲 ⣬ Ѿ ˣ
-
** Ĺ ʵ ** ҳ 沼 ֡ Ⱦ
-
**״̬ ** ʹ Provider ״̬
-
** Ż ** 桢Ԥ ء صȼ
-
** ** Ƶ 쳣 û ʾ
-
** ֧ ** ״̬ ݷ
-
** ʵ ** ֯ Դ ܼ
Щ ɲ ڵ ǰҳ 棬 Ӧ õ Flutter for OpenHarmony Ŀ У 㹹 Ӧ á
ӭ 뿪Դ ɿ ƽ̨ https://openharmonycrossplatform.csdn.net
深度功能扩展
1. 智能内容推荐
基于机器学习的内容推荐引擎:
class ContentRecommendation {
static List<DiscoveryItem> getPersonalizedContent(
User user,
List<DiscoveryItem> allContent,
) {
final userVector = _buildUserVector(user);
return allContent.map((item) {
final itemVector = _buildItemVector(item);
final similarity = _cosineSimilarity(userVector, itemVector);
return MapEntry(item, similarity);
}).toList()
..sort((a, b) => b.value.compareTo(a.value))
..map((e) => e.key).toList();
}
static List<double> _buildUserVector(User user) {
return [
user.preferences.categoryWeights,
user.interactionHistory.frequency,
user.ratingPatterns.average,
].expand((x) => x).toList();
}
static double _cosineSimilarity(List<double> a, List<double> b) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (sqrt(normA) * sqrt(normB));
}
}
推荐引擎使用余弦相似度算法,计算用户偏好向量与内容特征向量的相似度。通过分析用户的浏览历史、收藏记录和评分数据,为每位用户提供个性化的发现内容。
2. 社交互动功能
增强用户间的互动体验:
class SocialInteraction {
static Future<void> likeContent(String contentId) async {
await _updateInteraction(contentId, InteractionType.like);
await _notifyAuthor(contentId, '有人喜欢了你的内容');
}
static Future<void> shareContent(String contentId, String platform) async {
final content = await _getContent(contentId);
final shareUrl = await _generateShareUrl(content);
await Share.share(
'发现了一个很棒的手办内容:${content.title}\n$shareUrl',
subject: content.title,
);
await _recordShare(contentId, platform);
}
static Future<void> followUser(String userId) async {
await http.post(
Uri.parse('$baseUrl/follow'),
body: jsonEncode({'userId': userId}),
);
await _updateFollowList(userId);
}
}
社交功能支持点赞、分享和关注等互动操作。用户可以关注感兴趣的创作者,及时获取他们发布的新内容。分享功能支持多个社交平台,扩大内容传播范围。
3. 内容质量评估
自动评估和筛选高质量内容:
class ContentQualityAssessor {
static double assessQuality(DiscoveryItem item) {
double score = 0.0;
// 图片质量评分
score += _assessImageQuality(item.images) * 0.3;
// 文本质量评分
score += _assessTextQuality(item.description) * 0.2;
// 用户互动评分
score += _assessEngagement(item.stats) * 0.3;
// 时效性评分
score += _assessTimeliness(item.publishDate) * 0.2;
return score;
}
static double _assessImageQuality(List<String> images) {
if (images.isEmpty) return 0.0;
double totalScore = 0.0;
for (var image in images) {
final resolution = _getImageResolution(image);
final clarity = _getImageClarity(image);
totalScore += (resolution + clarity) / 2;
}
return totalScore / images.length;
}
static double _assessEngagement(ContentStats stats) {
final likeRate = stats.likes / max(stats.views, 1);
final commentRate = stats.comments / max(stats.views, 1);
final shareRate = stats.shares / max(stats.views, 1);
return (likeRate * 0.5 + commentRate * 0.3 + shareRate * 0.2) * 100;
}
}
质量评估系统从多个维度自动评估内容质量,包括图片清晰度、文本丰富度、用户互动率和内容时效性。高质量内容会获得更多曝光机会,提升用户的浏览体验。
4. 个性化标签系统
动态生成和管理内容标签:
class TagManager {
static List<String> extractTags(DiscoveryItem item) {
final tags = <String>[];
// 从标题提取关键词
tags.addAll(_extractKeywords(item.title));
// 从描述提取关键词
tags.addAll(_extractKeywords(item.description));
// 添加分类标签
tags.add(item.category);
// 添加品牌标签
if (item.brand != null) tags.add(item.brand!);
return tags.toSet().toList();
}
static List<String> _extractKeywords(String text) {
final words = text.split(RegExp(r'\s+'));
return words.where((word) =>
word.length > 2 && !_isStopWord(word)
).toList();
}
static Future<void> updateUserTagPreferences(
String userId,
List<String> tags,
) async {
for (var tag in tags) {
await _incrementTagWeight(userId, tag);
}
}
}
标签系统自动从内容中提取关键词,生成相关标签。系统会记录用户对不同标签的偏好权重,用于优化后续的内容推荐。标签云功能帮助用户快速发现感兴趣的主题。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)