【OpenHarmony】Flutter鸿蒙实战:从零实现分类数据获取与UI渲染
本文将带你一步步完成Flutter鸿蒙应用中分类模块的开发,实现从API数据获取到最终UI展示的全流程。
·



本文将带你一步步完成Flutter鸿蒙应用中分类模块的开发,实现从API数据获取到最终UI展示的全流程。
开发准备
在开始编码之前,请确保已完成以下准备工作:
- 已配置好Flutter鸿蒙开发环境
- 安装Dio网络请求
一、项目架构设计
1.1 整体开发思路
分类功能的开发可以拆解为以下几个核心步骤:
API接口定义 → 数据模型构建 → 网络请求封装 → UI组件开发 → 数据联调
1.2 技术选型说明
| 技术组件 | 用途 | 选择理由 |
|---|---|---|
| Dio | HTTP网络请求 | 功能强大、易于使用、支持拦截器 |
| Factory模式 | 数据模型转换 | 优雅处理JSON到Dart对象的转换 |
| ListView.builder | 列表渲染 | 高性能的懒加载渲染方案 |
| CustomScrollView | 滚动容器 | 支持Sliver家族组件的混合滚动 |
二、数据模型层实现
2.1 API接口配置
首先,我们需要将项目中用到的常量和接口地址进行统一管理。
文件路径: lib/constants/index.dart
/// 全局配置常量
class AppConfig {
// API基础地址
static const String apiBaseUrl = "https://meikou-api.itheima.net/";
// 网络请求超时时间(秒)
static const int requestTimeout = 10;
// 业务成功状态码
static const String successCode = "1";
}
/// API接口地址常量
class ApiEndpoints {
// 首页轮播图接口
static const String bannerList = "/home/banner";
// 首页分类接口
static const String categoryList = "/home/category/head";
}
2.2 分类数据模型
分类接口返回的是嵌套的JSON结构,包含父级分类和子级分类。我们需要设计一个支持递归结构的数据模型。
文件路径: lib/viewmodels/home.dart
/// 轮播图数据模型
class BannerItem {
final String id;
final String imgUrl;
BannerItem({
required this.id,
required this.imgUrl,
});
/// JSON转对象工厂方法
factory BannerItem.fromJson(Map<String, dynamic> json) {
return BannerItem(
id: json["id"] ?? "",
imgUrl: json["imgUrl"] ?? "",
);
}
}
/// 分类数据模型(支持树形结构)
class CategoryItem {
final String id;
final String name;
final String? picture;
final List<CategoryItem>? children;
CategoryItem({
required this.id,
required this.name,
this.picture,
this.children,
});
/// 从JSON创建分类对象(支持递归处理子分类)
factory CategoryItem.fromJson(Map<String, dynamic> json) {
// 处理子分类列表
List<CategoryItem>? parseChildren(dynamic childrenData) {
if (childrenData == null) return null;
return (childrenData as List)
.map((item) => CategoryItem.fromJson(item as Map<String, dynamic>))
.toList();
}
return CategoryItem(
id: json["id"] ?? "",
name: json["name"] ?? "",
picture: json["picture"],
children: parseChildren(json["children"]),
);
}
}
代码解析
上述数据模型的核心要点:
- 使用
factory关键字:声明工厂构造函数,用于实现对象创建逻辑 - 空安全处理:使用
??运算符提供默认值,避免空指针异常 - 递归结构:
children字段类型为List<CategoryItem>?,支持多层嵌套 - 类型转换:使用
as关键字进行显式类型转换
三、网络层封装
3.1 Dio基础配置
文件路径: lib/utils/http_client.dart
import 'package:dio/dio.dart';
import '../constants/index.dart';
class HttpClient {
static final HttpClient _instance = HttpClient._internal();
factory HttpClient() => _instance;
late Dio _dio;
HttpClient._internal() {
_dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
connectTimeout: Duration(seconds: AppConfig.requestTimeout),
receiveTimeout: Duration(seconds: AppConfig.requestTimeout),
));
// 添加拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
print('请求: ${options.method} ${options.uri}');
return handler.next(options);
},
onResponse: (response, handler) {
print('响应: ${response.statusCode}');
return handler.next(response);
},
onError: (error, handler) {
print('错误: ${error.message}');
return handler.next(error);
},
));
}
Future<Response> get(String path, {Map<String, dynamic>? queryParameters}) {
return _dio.get(path, queryParameters: queryParameters);
}
}
3.2 API服务层
文件路径: lib/api/home.dart
import 'package:qing_mall/viewmodels/home.dart';
import 'package:qing_mall/utils/http_client.dart';
import 'dart:convert';
/// 获取轮播图列表
Future<List<BannerItem>> getBannerListAPI() async {
try {
final response = await HttpClient().get(ApiEndpoints.bannerList);
if (response.data['code'] == AppConfig.successCode) {
final List<dynamic> dataList = response.data['result'];
return dataList.map((e) => BannerItem.fromJson(e)).toList();
}
return [];
} catch (e) {
print('获取轮播图失败: $e');
return [];
}
}
/// 获取分类列表数据
///
/// 返回值:分类对象列表,失败时返回空列表
Future<List<CategoryItem>> getCategoryListAPI() async {
try {
final response = await HttpClient().get(ApiEndpoints.categoryList);
// 判断业务状态码
if (response.data['code'] == AppConfig.successCode) {
final List<dynamic> dataList = response.data['result'];
// 批量转换为对象列表
return dataList
.map((item) => CategoryItem.fromJson(item as Map<String, dynamic>))
.toList();
}
return [];
} catch (e) {
print('获取分类列表异常: $e');
return [];
}
}
四、UI组件开发
4.1 首页数据管理
文件路径: lib/pages/Home/index.dart
import 'package:flutter/cupertino.dart';
import 'package:qing_mall/api/home.dart';
import 'package:qing_mall/components/Home/HmCategory.dart';
import 'package:qing_mall/components/Home/HmHot.dart';
import 'package:qing_mall/components/Home/HmMoreList.dart';
import 'package:qing_mall/components/Home/HmSlider.dart';
import 'package:qing_mall/components/Home/HmSuggestion.dart';
import 'package:qing_mall/viewmodels/home.dart';
class HomeView extends StatefulWidget {
const HomeView({super.key});
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
// ========== 数据状态管理 ==========
/// 分类列表数据
List<CategoryItem> _categoryList = [];
/// 轮播图列表数据
List<BannerItem> _bannerList = [];
// ========== 生命周期方法 ==========
void initState() {
super.initState();
_loadInitialData();
}
// ========== 数据加载方法 ==========
/// 加载首页初始数据
void _loadInitialData() {
_fetchBannerData();
_fetchCategoryData();
}
/// 获取轮播图数据
void _fetchBannerData() async {
final result = await getBannerListAPI();
if (mounted) {
setState(() {
_bannerList = result;
});
}
}
/// 获取分类数据
void _fetchCategoryData() async {
final result = await getCategoryListAPI();
if (mounted) {
setState(() {
_categoryList = result;
});
}
}
// ========== UI构建方法 ==========
/// 构建滚动视图的子组件列表
List<Widget> _buildScrollChildren() {
return [
// 轮播图区域
SliverToBoxAdapter(child: HmSlider(bannerList: _bannerList)),
// 间距
SliverToBoxAdapter(child: SizedBox(height: 10)),
// 分类导航区域
SliverToBoxAdapter(
child: HmCategory(categoryList: _categoryList),
),
// 间距
SliverToBoxAdapter(child: SizedBox(height: 10)),
// 推荐搜索词
SliverToBoxAdapter(child: HmSuggestion()),
SliverToBoxAdapter(child: SizedBox(height: 10)),
// 热门商品(双列布局)
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Row(
children: [
Expanded(child: HmHot()),
SizedBox(width: 10),
Expanded(child: HmHot()),
],
),
),
),
SliverToBoxAdapter(child: SizedBox(height: 10)),
// 猜你喜欢无限列表
HmMorelist(),
];
}
Widget build(BuildContext context) {
return CustomScrollView(
slivers: _buildScrollChildren(),
);
}
}
4.2 分类组件实现
文件路径: lib/components/Home/HmCategory.dart
初始版本
先创建一个基础的分类组件框架:
import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/home.dart';
class HmCategory extends StatefulWidget {
final List<CategoryItem> categoryList;
const HmCategory({
super.key,
required this.categoryList,
});
State<HmCategory> createState() => _HmCategoryState();
}
class _HmCategoryState extends State<HmCategory> {
Widget build(BuildContext context) {
return SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.categoryList.length,
itemBuilder: (BuildContext context, int index) {
final category = widget.categoryList[index];
return Container(
width: 80,
height: 100,
margin: EdgeInsets.symmetric(horizontal: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.network(
category.picture ?? "",
width: 40,
height: 40,
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.category, size: 40);
},
),
SizedBox(height: 8),
Text(
category.name,
style: TextStyle(fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
);
},
),
);
}
}
优化版本
对UI进行美化处理:
import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/home.dart';
class HmCategory extends StatefulWidget {
final List<CategoryItem> categoryList;
const HmCategory({
super.key,
required this.categoryList,
});
State<HmCategory> createState() => _HmCategoryState();
}
class _HmCategoryState extends State<HmCategory> {
Widget build(BuildContext context) {
return SizedBox(
height: 110,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.symmetric(horizontal: 16),
itemCount: widget.categoryList.length,
itemBuilder: (BuildContext context, int index) {
final category = widget.categoryList[index];
return _buildCategoryItem(category);
},
),
);
}
/// 构建单个分类项
Widget _buildCategoryItem(CategoryItem category) {
return Container(
width: 75,
margin: EdgeInsets.only(right: 12),
decoration: BoxDecoration(
color: Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 分类图标
Container(
width: 45,
height: 45,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: ClipOval(
child: Image.network(
category.picture ?? "",
width: 45,
height: 45,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.image_not_supported,
size: 24,
color: Colors.grey[400],
);
},
),
),
),
SizedBox(height: 8),
// 分类名称
Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Text(
category.name,
style: TextStyle(
fontSize: 12,
color: Color(0xFF333333),
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
],
),
);
}
}

五、开发技巧与注意事项
5.1 常见问题处理
问题1:网络图片加载失败
Image.network(
url,
errorBuilder: (context, error, stackTrace) {
return Placeholder(); // 显示占位图
},
)
问题2:ListView高度设置问题
// ListView不能直接设置高度,需要包裹SizedBox
SizedBox(
height: 100,
child: ListView(...),
)
问题3:状态更新后UI不刷新
// 确保在setState中更新数据
setState(() {
_categoryList = newData;
});
5.2 性能优化建议
- 图片缓存:使用
cached_network_image库缓存网络图片 - 列表优化:使用
ListView.builder而非ListView构造大量子项 - 防抖处理:网络请求时添加防抖,避免重复调用
六、项目提交与版本管理
6.1 代码提交
开发完成后,使用Git进行版本管理:
# 添加所有变更文件
git add .
# 提交代码
git commit -m "feat: 完成分类数据获取与UI渲染功能
- 新增CategoryItem数据模型,支持树形结构
- 实现分类API接口调用
- 开发HmCategory分类组件
- 优化分类UI展示效果"
# 推送到远程仓库
git push
七、总结与展望
7.1 核心知识点回顾
本文完成了Flutter鸿蒙应用中分类模块的开发,主要涉及以下技术点:
- 数据建模:使用Factory模式处理JSON到Dart对象的转换
- 网络请求:Dio库的封装与使用
- 状态管理:通过setState更新UI状态
- 组件开发:ListView.builder实现横向滚动列表
- UI设计:Container、Decoration等实现圆角卡片样式
7.2 后续优化方向
| 优化项 | 说明 |
|---|---|
| 点击交互 | 添加分类项点击事件,跳转到对应商品列表 |
| 下拉刷新 | 实现分类数据的下拉刷新功能 |
| 骨架屏 | 数据加载时显示骨架屏,提升体验 |
| 动画效果 | 添加分类项的点击和滚动动画 |
| 数据缓存 | 使用本地缓存减少网络请求 |
7.3 学习资源推荐
结语
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)