flutter_for_openharmony手办收藏app实战_厂商列表实现
Flutter for OpenHarmony 厂商列表页面实现 本文介绍了在手办收藏应用中实现厂商列表页面的关键技术和设计思路。主要内容包括: 业务价值分析:厂商列表具备品牌筛选、产品统计、详情跳转等功能 页面架构:采用StatelessWidget和无状态设计,确保组件职责单一 核心实现: 使用ListView.builder高效渲染列表 通过Card和ListTile构建美观的UI卡片 包含

手办收藏爱好者都知道,不同厂商的产品风格、品质、价格定位各不相同。Good Smile Company 以粘土人系列闻名,Alter 则以高端比例手办著称,Bandai 的万代系列覆盖众多动漫 IP。因此,在手办收藏应用中,厂商列表页面是帮助用户快速筛选、浏览特定品牌产品的关键功能。本文将深入剖析如何在 Flutter for OpenHarmony 项目中实现一个实用的厂商列表页面。
一、厂商列表的业务价值
在手办收藏场景中,厂商列表不仅是简单的品牌展示,更承载着以下核心功能:
- 品牌筛选:用户可以快速找到自己喜欢的厂商,查看该厂商的所有产品
- 产品数量统计:直观展示每个厂商的产品规模,帮助用户了解品牌活跃度
- 跳转详情:点击厂商后进入专属页面,查看该厂商的详细介绍、热门产品等
- 收藏管理:用户可以关注特定厂商,当该厂商发布新品时收到通知
这些需求决定了厂商列表页面需要信息密度适中、交互流畅、扩展性强。
二、页面架构设计
1. 无状态组件的选择
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class ManufacturersPage extends StatelessWidget {
const ManufacturersPage({super.key});
采用 StatelessWidget 是因为厂商数据相对稳定,不需要频繁更新。即使后续需要添加搜索、排序等功能,也可以通过 Provider 或 GetX 进行状态管理,而非在页面内部维护状态。这种设计符合 Flutter 的最佳实践,保持组件职责单一。
2. 数据源定义
Widget build(BuildContext context) {
final manufacturers = ['Good Smile Company', 'Kotobukiya', 'Alter', 'Max Factory', 'Bandai', 'Aniplex'];
当前使用硬编码数组模拟数据,实际项目中应从以下来源获取:
- 本地数据库:使用 sqflite 存储厂商信息,支持离线访问
- 远程 API:从服务器获取最新的厂商列表,包含 logo、简介等详细信息
- 混合模式:本地缓存 + 远程更新,兼顾速度与实时性
这里选择的 6 个厂商都是手办行业的知名品牌,覆盖了不同价位和风格,具有代表性。
三、列表渲染的核心逻辑
1. Scaffold 基础结构
return Scaffold(
appBar: AppBar(title: const Text('厂商列表')),
顶部导航栏显示"厂商列表"标题,让用户明确当前所在页面。在完整版本中,可以在 AppBar 右侧添加搜索图标,点击后展开搜索框,支持按厂商名称快速定位。
2. ListView.builder 高效渲染
body: ListView.builder(
itemCount: manufacturers.length,
itemBuilder: (context, index) {
为什么选择 builder 模式?
- 性能优化:只渲染可见区域的列表项,当厂商数量达到几十个甚至上百个时,性能优势明显
- 动态扩展:厂商数量不固定,builder 能灵活适应数据变化
- 内存友好:在 OpenHarmony 设备上,避免一次性创建大量 Widget 导致内存峰值
3. Card 卡片样式
return Card(
margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
使用 Card 组件的优势:
- 视觉分隔:自带阴影效果,让每个厂商条目独立清晰
- 点击反馈:Card 内部的 ListTile 自带水波纹效果,提升交互体验
- 间距控制:通过
margin设置水平 16 像素、垂直 8 像素的间距,避免列表过于紧凑
这里的 .w 和 .h 是 ScreenUtil 提供的响应式单位,确保在不同屏幕尺寸的 OpenHarmony 设备上都能保持合适的间距比例。
四、ListTile 内容布局详解
1. leading 头像设计
child: ListTile(
leading: CircleAvatar(
child: Text(manufacturers[index][0]),
),
CircleAvatar 的妙用:
- 显示厂商名称的首字母,如"G"代表 Good Smile Company
- 当没有厂商 logo 图片时,首字母是很好的替代方案
- 圆形头像符合现代 UI 设计规范,视觉上更柔和
在实际项目中,可以这样优化:
leading: CircleAvatar(
backgroundImage: manufacturer.logoUrl != null
? NetworkImage(manufacturer.logoUrl!)
: null,
child: manufacturer.logoUrl == null
? Text(manufacturer.name[0])
: null,
),
优先显示真实 logo,无图片时才显示首字母。
2. title 厂商名称
title: Text(manufacturers[index]),
直接显示厂商全称,字体大小使用系统默认值(通常为 16sp),确保可读性。如果厂商名称过长(如"Good Smile Company"),ListTile 会自动处理文本溢出,显示省略号。
3. subtitle 产品数量统计
subtitle: Text('产品数量: ${(index + 1) * 100}'),
数量计算逻辑:
- 当前使用
(index + 1) * 100模拟数据,第一个厂商显示 100,第二个显示 200,以此类推 - 实际项目中应从数据库查询:
SELECT COUNT(*) FROM figures WHERE manufacturer_id = ? - 这个数字能帮助用户快速判断厂商的产品丰富度
可以进一步优化显示格式:
subtitle: Text('产品数量: ${manufacturer.productCount} | 关注: ${manufacturer.followersCount}'),
同时显示产品数量和关注人数,信息更全面。
4. trailing 箭头图标
trailing: const Icon(Icons.arrow_forward_ios, size: 16),
右侧箭头的作用:
- 视觉提示:告诉用户这是一个可点击的条目
- 统一规范:符合移动端列表页的通用设计模式
- 尺寸控制:16 像素的箭头不会过于显眼,保持页面平衡
5. onTap 点击事件
onTap: () {},
当前为空实现,实际项目中应跳转到厂商详情页:
onTap: () {
Get.to(() => ManufacturerDetailPage(
manufacturerId: manufacturers[index].id,
manufacturerName: manufacturers[index],
));
},
使用 GetX 的路由管理,传递厂商 ID 和名称参数。
五、数据模型设计
为了支持更复杂的业务逻辑,应定义厂商数据模型:
class Manufacturer {
final String id;
final String name;
final String? logoUrl;
final int productCount;
final int followersCount;
final String description;
final String country;
Manufacturer({
required this.id,
required this.name,
this.logoUrl,
required this.productCount,
required this.followersCount,
required this.description,
required this.country,
});
factory Manufacturer.fromJson(Map<String, dynamic> json) {
return Manufacturer(
id: json['id'],
name: json['name'],
logoUrl: json['logo_url'],
productCount: json['product_count'],
followersCount: json['followers_count'],
description: json['description'],
country: json['country'],
);
}
}
这样可以存储更多厂商信息,如国家、简介等,为详情页提供数据支持。
六、状态管理集成
1. 创建 ManufacturerProvider
class ManufacturerProvider extends ChangeNotifier {
List<Manufacturer> _manufacturers = [];
bool _isLoading = false;
List<Manufacturer> get manufacturers => _manufacturers;
bool get isLoading => _isLoading;
Future<void> fetchManufacturers() async {
_isLoading = true;
notifyListeners();
try {
final response = await http.get(Uri.parse('$apiBaseUrl/manufacturers'));
final List<dynamic> data = json.decode(response.body);
_manufacturers = data.map((json) => Manufacturer.fromJson(json)).toList();
} catch (e) {
print('加载厂商列表失败: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
List<Manufacturer> searchManufacturers(String keyword) {
return _manufacturers.where((m) =>
m.name.toLowerCase().contains(keyword.toLowerCase())
).toList();
}
}
通过 Provider 管理厂商数据,支持加载、搜索等操作。
2. 页面改造
body: Consumer<ManufacturerProvider>(
builder: (context, provider, child) {
if (provider.isLoading) {
return Center(child: CircularProgressIndicator());
}
if (provider.manufacturers.isEmpty) {
return Center(child: Text('暂无厂商数据'));
}
return ListView.builder(
itemCount: provider.manufacturers.length,
itemBuilder: (context, index) {
final manufacturer = provider.manufacturers[index];
return Card(
// 使用真实数据渲染
);
},
);
},
),
通过 Consumer 监听数据变化,自动刷新列表。
七、搜索功能实现
在 AppBar 中添加搜索框:
appBar: AppBar(
title: _isSearching
? TextField(
autofocus: true,
decoration: InputDecoration(
hintText: '搜索厂商...',
border: InputBorder.none,
),
onChanged: (value) {
provider.searchManufacturers(value);
},
)
: Text('厂商列表'),
actions: [
IconButton(
icon: Icon(_isSearching ? Icons.close : Icons.search),
onPressed: () {
setState(() {
_isSearching = !_isSearching;
});
},
),
],
),
点击搜索图标后,标题栏变为输入框,实时过滤厂商列表。
八、OpenHarmony 适配要点
1. 屏幕适配
使用 ScreenUtil 确保在不同设备上显示一致:
ScreenUtil.init(
context,
designSize: Size(375, 812),
minTextAdapt: true,
);
设计稿基于 375x812 尺寸,ScreenUtil 会自动缩放。
2. 网络请求优化
OpenHarmony 设备可能处于弱网环境,需要添加超时和重试机制:
final response = await http.get(
Uri.parse('$apiBaseUrl/manufacturers'),
).timeout(Duration(seconds: 10), onTimeout: () {
throw TimeoutException('请求超时');
});
超时后提示用户重试,避免长时间等待。
九、高级功能扩展
1. 厂商详情页跳转
实现完整的跳转逻辑:
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ManufacturerDetailPage(
manufacturer: manufacturers[index],
),
),
);
}
在详情页中展示厂商的完整信息:
class ManufacturerDetailPage extends StatelessWidget {
final Manufacturer manufacturer;
const ManufacturerDetailPage({required this.manufacturer});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(manufacturer.name)),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (manufacturer.logoUrl != null)
Center(
child: Image.network(
manufacturer.logoUrl!,
height: 100.h,
),
),
SizedBox(height: 16.h),
Text(
manufacturer.description,
style: TextStyle(fontSize: 14.sp),
),
SizedBox(height: 16.h),
Text(
'产品数量: ${manufacturer.productCount}',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
],
),
),
);
}
}
2. 排序功能
添加按产品数量、关注人数排序:
enum SortType { name, productCount, followersCount }
class ManufacturerProvider extends ChangeNotifier {
SortType _sortType = SortType.name;
void setSortType(SortType type) {
_sortType = type;
_sortManufacturers();
notifyListeners();
}
void _sortManufacturers() {
switch (_sortType) {
case SortType.name:
_manufacturers.sort((a, b) => a.name.compareTo(b.name));
break;
case SortType.productCount:
_manufacturers.sort((a, b) => b.productCount.compareTo(a.productCount));
break;
case SortType.followersCount:
_manufacturers.sort((a, b) => b.followersCount.compareTo(a.followersCount));
break;
}
}
}
在 AppBar 添加排序菜单:
actions: [
PopupMenuButton<SortType>(
onSelected: (type) => provider.setSortType(type),
itemBuilder: (context) => [
PopupMenuItem(
value: SortType.name,
child: Text('按名称'),
),
PopupMenuItem(
value: SortType.productCount,
child: Text('按产品数量'),
),
PopupMenuItem(
value: SortType.followersCount,
child: Text('按关注人数'),
),
],
),
],
3. 关注功能
实现厂商关注/取消关注:
class ManufacturerProvider extends ChangeNotifier {
Set<String> _followedManufacturers = {};
bool isFollowed(String manufacturerId) {
return _followedManufacturers.contains(manufacturerId);
}
Future<void> toggleFollow(String manufacturerId) async {
if (_followedManufacturers.contains(manufacturerId)) {
_followedManufacturers.remove(manufacturerId);
await _unfollowManufacturer(manufacturerId);
} else {
_followedManufacturers.add(manufacturerId);
await _followManufacturer(manufacturerId);
}
notifyListeners();
}
Future<void> _followManufacturer(String id) async {
await http.post(Uri.parse('$apiBaseUrl/manufacturers/$id/follow'));
}
Future<void> _unfollowManufacturer(String id) async {
await http.delete(Uri.parse('$apiBaseUrl/manufacturers/$id/follow'));
}
}
在列表项中添加关注按钮:
ListTile(
leading: CircleAvatar(child: Text(manufacturer.name[0])),
title: Text(manufacturer.name),
subtitle: Text('产品数量: ${manufacturer.productCount}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(
provider.isFollowed(manufacturer.id)
? Icons.favorite
: Icons.favorite_border,
color: provider.isFollowed(manufacturer.id)
? Colors.red
: Colors.grey,
),
onPressed: () => provider.toggleFollow(manufacturer.id),
),
Icon(Icons.arrow_forward_ios, size: 16),
],
),
onTap: () => _navigateToDetail(manufacturer),
)
4. 下拉刷新
添加下拉刷新功能:
RefreshIndicator(
onRefresh: () async {
await provider.fetchManufacturers();
},
child: ListView.builder(
itemCount: provider.manufacturers.length,
itemBuilder: (context, index) {
// 列表项
},
),
)
5. 分组显示
按国家或地区分组显示厂商:
Map<String, List<Manufacturer>> groupByCountry(List<Manufacturer> manufacturers) {
final grouped = <String, List<Manufacturer>>{};
for (var manufacturer in manufacturers) {
if (!grouped.containsKey(manufacturer.country)) {
grouped[manufacturer.country] = [];
}
grouped[manufacturer.country]!.add(manufacturer);
}
return grouped;
}
使用分组数据渲染列表:
ListView(
children: groupedManufacturers.entries.map((entry) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(16.w),
child: Text(
entry.key,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
),
...entry.value.map((manufacturer) => _buildManufacturerTile(manufacturer)),
],
);
}).toList(),
)
十、性能优化技巧
1. 图片缓存
使用 CachedNetworkImage 缓存厂商 logo:
leading: CachedNetworkImage(
imageUrl: manufacturer.logoUrl ?? '',
imageBuilder: (context, imageProvider) => CircleAvatar(
backgroundImage: imageProvider,
),
placeholder: (context, url) => CircleAvatar(
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) => CircleAvatar(
child: Text(manufacturer.name[0]),
),
)
2. 懒加载
实现分页加载,避免一次性加载过多数据:
class ManufacturerProvider extends ChangeNotifier {
int _page = 1;
bool _hasMore = true;
Future<void> loadMore() async {
if (!_hasMore || _isLoading) return;
_isLoading = true;
notifyListeners();
try {
final response = await http.get(
Uri.parse('$apiBaseUrl/manufacturers?page=$_page&limit=20'),
);
final List<dynamic> data = json.decode(response.body);
if (data.isEmpty) {
_hasMore = false;
} else {
_manufacturers.addAll(
data.map((json) => Manufacturer.fromJson(json)).toList(),
);
_page++;
}
} finally {
_isLoading = false;
notifyListeners();
}
}
}
监听滚动位置,触底时加载更多:
ScrollController _scrollController = ScrollController();
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
provider.loadMore();
}
});
}
3. 本地缓存
使用 sqflite 缓存厂商数据,支持离线访问:
Future<void> cacheManufacturers(List<Manufacturer> manufacturers) async {
final db = await database;
await db.delete('manufacturers');
for (var manufacturer in manufacturers) {
await db.insert('manufacturers', manufacturer.toJson());
}
}
Future<List<Manufacturer>> loadCachedManufacturers() async {
final db = await database;
final maps = await db.query('manufacturers');
return maps.map((map) => Manufacturer.fromJson(map)).toList();
}
十一、实战经验总结
厂商列表页面的实现要点:
- 数据结构:定义完善的 Manufacturer 模型,支持扩展
- 性能优化:使用 ListView.builder 按需渲染,避免卡顿
- 交互设计:点击跳转、搜索过滤等功能提升用户体验
- 状态管理:通过 Provider 统一管理数据,降低耦合度
- 高级功能:排序、关注、分组等功能提升用户体验
- 性能优化:图片缓存、懒加载、本地缓存确保流畅运行
通过本文的实战讲解,你已经掌握了在 Flutter for OpenHarmony 项目中构建厂商列表的核心技巧。在后续开发中,可以继续扩展排序、筛选、关注等功能,让厂商列表成为用户探索手办世界的重要入口。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)