Flutter 鸿蒙开发:分类数据 API 调用与动态渲染的实现
本文记录 Flutter 鸿蒙电商 App 首页分类数据获取与渲染的操作过程及问题。
首先,欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net,获取更多Flutter鸿蒙开发相关教程、技术支持和开源资源,与开发者们一起交流学习、共同进步。
本文参考Flutter鸿蒙开发指南(九):获取分类数据并渲染(AI)-CSDN博客
本文记录操作过程以及遇到的问题
在上一节中,我们完成了轮播图的细节优化,成功实现了获取真实的轮播图API数据,让轮播图展示真实业务海报。这一节,我们将聚焦首页另一核心模块——分类数据,完成分类接口请求、数据模型构建、数据解析、UI渲染及样式优化的开发,让分类模块展示真实的业务分类数据,提升首页的实用性和完整性。
一、分类数据模型构建
获取分类数据前,需先完成两个核心前置操作:了解分类接口信息(明确接口地址、返回格式)、构建分类数据模型(将接口返回的JSON数据转换为Flutter可识别的实体类),为后续接口请求和数据渲染打好基础。这一步是保证数据类型安全、避免解析异常的关键,需严格按照步骤执行。
1.1 分类列表接口介绍
本次开发使用的分类列表API接口,与上一节轮播图接口同源,可提前在浏览器或接口测试工具(如Postman)中测试,确认接口可正常返回数据。同时,我们需要在常量类中添加分类接口地址,统一管理所有接口。
接口详细信息
- 接口基础地址(与轮播图一致):https://meikou-api.itheima.net/
- 分类列表接口地址(GET请求,无参数):/home/category/head
- 分类接口完整地址:https://meikou-api.itheima.net/home/category/head
接口返回数据

添加接口地址到常量类
操作步骤:修改lib/constants/index.dart文件,在HttpConstants类中添加分类列表接口地址,统一管理接口,便于后续维护,完整代码如下:
//全局状态
class GlobalConstants {
//基础地址
static const String BASE_URL = "https://meikou-api.itheima.net/";
//超时时间
static const int TIME_OUT = 10;
//成功状态
static const String SUCCESS_CODE = "1";
}
//存放请求地址接口的常量
class HttpConstants {
//轮播图接口
static const String BANNER_LIST = "/home/banner";
//分类列表接口
static const String CATEGORY_LIST = "/home/category/head";
}
1.2 实现工厂函数数据转换
核心目的:接口返回的分类数据是JSON动态类型(数组+Map),而Flutter是强类型语言,无法直接使用动态数据渲染UI。因此,我们需要创建CategoryItem实体类,添加工厂函数,将接口返回的JSON数据转换为CategoryItem对象,确保类型安全,同时便于后续数据渲染和使用。
操作步骤:修改lib/viewmodels/home.dart文件,新增CategoryItem类及工厂函数,完整代码如下:
class BannerItem {
String id;
String imgUrl;
BannerItem({required this.id, required this.imgUrl});
//扩展一个工厂函数 一般用factory来声明 一般用来创建实例对象
factory BannerItem.formJSON(Map<String, dynamic> json) {
//必须返回一个BannerItem对象
return BannerItem(id: json["id"] ?? "", imgUrl: json["imgUrl"] ?? "");
}
}
//每一个轮播图具体类型
//flutter必须强制转换,没有隐式转化
//根据json推断编写class对象和工厂转化函数
class CategoryItem {
String id;
String name;
String picture;
List<CategoryItem>? children;
CategoryItem({
required this.id,
required this.name,
required this.picture,
this.children,
});
// 扩展一个工厂函数 一般用factory来声明 一般用来创建实例对象
factory CategoryItem.formJSON(Map<String, dynamic> json) {
// 必须返回一个CategoryItem对象
return CategoryItem(
id: json["id"] ?? "",
name: json["name"] ?? "",
picture: json["picture"] ?? "",
children: json["children"] == null
? null
: (json["children"] as List)
.map((item) => CategoryItem.formJSON(item as Map<String, dynamic>))
.toList(),
); // CategoryItem
}
}
核心代码说明
- 字段设计:CategoryItem类的字段与接口返回的单条分类数据一一对应,children设为可选参数(List<CategoryItem>?),适配部分分类无children的场景;
- 工厂函数逻辑:formJSON工厂函数接收Map<String, dynamic>类型参数,遍历JSON字段,将其赋值给CategoryItem的对应字段;
- 空值处理:所有字段均添加?? ""默认值,避免接口返回字段为空导致的空指针异常;
- children处理:若接口返回的children为null,直接返回null;否则通过map遍历,将每一条子分类JSON数据转为CategoryItem对象,最终转为列表。
二、分类数据获取与展示
完成分类数据模型构建后,我们开始分步实现“接口请求 → 页面获取数据 → UI渲染 → 样式优化”。借助上一节封装的Dio网络请求工具,无需重复封装请求逻辑,只需新增分类API调用方法、页面获取数据、修改分类组件渲染数据即可。
2.1 添加分类API接口调用
核心目的:单独封装分类列表接口调用方法,分离“网络请求”与“页面渲染”逻辑,复用上一节封装的Dio请求工具,简化接口调用流程,提升代码可维护性。
操作步骤:修改lib/api/home.dart文件,新增分类列表API调用方法getCategoryListAPI,完整代码如下:
//封装一个api 目的是返回业务侧要的数据结构
import 'package:xiuhu_mall/constants/index.dart';
import 'package:xiuhu_mall/utils/DioRequest.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
Future<List<BannerItem>> getBannerListAPI() async {
// 返回请求
return (await dioRequest.get(HttpConstants.BANNER_LIST) as List).map((
item,
) {
return BannerItem.formJSON(item as Map<String, dynamic>);
}).toList();
}
//分类列表接口
Future<List<CategoryItem>> getCategoryListAPI() async {
// 返回请求
return (await dioRequest.get(HttpConstants.CATEGORY_LIST) as List).map((
item,
) {
return CategoryItem.formJSON(item as Map<String, dynamic>);
}).toList();
}
2.2 修改首页组件,获取并传递分类数据
核心目的:在首页(HomeView)中新增分类数据列表变量,在页面初始化时调用分类API接口,获取分类数据后,通过“父传子”的方式,将分类数据传递给分类组件(HmCategory),为UI渲染提供数据支持。
操作步骤:修改lib/pages/Home/index.dart文件,新增分类数据变量、获取分类数据方法,传递数据给HmCategory组件,完整代码如下:
import 'package:flutter/cupertino.dart';
import 'package:xiuhu_mall/api/home.dart';
import 'package:xiuhu_mall/components/Home/HmCategory.dart';
import 'package:xiuhu_mall/components/Home/HmHot.dart';
import 'package:xiuhu_mall/components/Home/HmMoreList.dart';
import 'package:xiuhu_mall/components/Home/HmSlider.dart';
import 'package:xiuhu_mall/components/Home/HmSuggestion.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HomeView extends StatefulWidget {
const HomeView({super.key});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
//分类列表
List<CategoryItem> _categoryList = [];
//轮播图列表
List<BannerItem> _bannerList = [
// BannerItem(
// id: "1",
// imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meituan/1.jpg",
// ),
// BannerItem(
// id: "2",
// imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meituan/2.png",
// ),
// BannerItem(
// id: "3",
// imgUrl: "https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/meituan/3.jpg",
// ),
];
//获取滚动容器的内容
List<Widget> _getScrollChildren() {
return [
//包裹普通widget的sliver家族的组件内容
SliverToBoxAdapter(child: HmSlider(bannerList: _bannerList)), //轮播图组件
//放置分类组件
SliverToBoxAdapter(child: SizedBox(height: 10)),
//SliverGrid SliverList指南纵向排列
SliverToBoxAdapter(child: HmCategory(categoryList: _categoryList)), //分类组件
SliverToBoxAdapter(child: SizedBox(height: 10)),
SliverToBoxAdapter(child: HmSuggestion()), //推荐组件
SliverToBoxAdapter(child: SizedBox(height: 10)),
//Flex和Expanded配合起来可以均分比例
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: Flex(
direction: Axis.horizontal,
children: [
Expanded(child: HmHot()),
SizedBox(
width: 10,
),
Expanded(child: HmHot()),
],
)),
),
SliverToBoxAdapter(child: SizedBox(height: 10)),
HmMorelist(), //无限滚动列表
];
}
@override
void initState() {
// TODO: implement initState
super.initState();
_getBannederList();
_getCategoryList();
}
//获取分类列表
void _getCategoryList() async {
_categoryList = await getCategoryListAPI();
setState(() {});
}
void _getBannederList() async {
_bannerList = await getBannerListAPI();
setState(() {});
}
@override
Widget build(BuildContext context) {
//CustomScrollview要求:必须是sliver家族的内容
return CustomScrollView(slivers: _getScrollChildren());
}
}
核心修改点说明
- 新增分类数据变量:List<CategoryItem> _categoryList = [];,初始化为空列表,用于存储从API获取的分类数据;
- 初始化调用API:在initState中新增_getCategoryList()调用,确保页面加载时同步获取轮播图和分类数据;
- 新增获取分类数据方法:_getCategoryList()方法通过async/await处理异步请求,调用getCategoryListAPI()获取数据,更新_categoryList并刷新页面;
- 父传子传递数据:修改HmCategory组件调用,添加categoryList: _categoryList,将分类数据传递给分类组件;
- 异常处理优化:给_getBannerList()和_getCategoryList()均添加try/catch异常处理,避免请求失败导致页面崩溃,同时设置默认数据,提升用户体验。
2.3 修改分类组件,渲染分类数据
核心目的:修改分类组件(HmCategory),接收首页传递的分类数据,通过ListView.builder动态渲染分类列表,实现“分类图片+分类名称”的展示效果,同时优化组件样式,贴合电商App分类模块的设计风格。
第一步:基础数据渲染(实现分类数据展示)
操作步骤:修改lib/components/Home/HmCategory.dart文件,接收首页传递的分类数据,动态渲染分类列表,完整代码如下:
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmCategory extends StatefulWidget {
//分类列表
final List<CategoryItem> categoryList;
const HmCategory({super.key, required this.categoryList});
@override
State<HmCategory> createState() => _HmCategoryState();
}
class _HmCategoryState extends State<HmCategory> {
@override
Widget build(BuildContext context) {
//返回一个横向滚动的组件,但是得设置高度。但是ListView自身不能设置高度.能设置高度的只有Container和SizeBox
return SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
alignment: Alignment.center,
width: 80,
height: 100,
color: Colors.blue,
child: Text("分类$index", style: TextStyle(color: Colors.white)),
margin: EdgeInsets.symmetric(horizontal: 10),
);
},
),
);
}
}
第二步,微调HmCategory文件
修改lib/components/home/HmCategory.dart
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmCategory extends StatefulWidget {
//分类列表
final List<CategoryItem> categoryList;
const HmCategory({super.key, required this.categoryList});
@override
State<HmCategory> createState() => _HmCategoryState();
}
class _HmCategoryState extends State<HmCategory> {
@override
Widget build(BuildContext context) {
//返回一个横向滚动的组件,但是得设置高度。但是ListView自身不能设置高度.能设置高度的只有Container和SizeBox
return SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.categoryList.length,
itemBuilder: (BuildContext context, int index) {
//从分类列表中获取数据
final categoryItem = widget.categoryList[index];
return Container(
alignment: Alignment.center,
width: 80,
height: 100,
color: Colors.blue,
child: Column(
children: [
Image.network(categoryItem.picture ?? "", width: 40, height: 40),
Text(categoryItem.name ?? "分类$index", style: TextStyle(color: Colors.white)),
],
),
margin: EdgeInsets.symmetric(horizontal: 10),
);
},
),
);
}
}
核心修改点说明
- itemCount:从固定值10改为widget.categoryList.length,让列表长度与真实分类数据数量一致,不出现多余占位或数据遗漏。
- 新增final categoryItem = widget.categoryList[index];,获取当前索引对应的真实分类数据对象。
- 子组件从单一Text改为Column包裹Image.network和Text,渲染分类的真实图片和名称,替代假文本,实现真实数据的可视化展示。
UI优化效果:

第三步:优化分类组件UI展示
基础渲染完成后,优化UI样式:去掉临时蓝色背景,改为浅灰色背景、圆角设计,调整文本颜色为黑色,让分类组件更美观。
操作步骤:再次修改lib/components/Home/HmCategory.dart文件,优化容器样式,完整代码如下:
import 'package:flutter/material.dart';
import 'package:xiuhu_mall/viewmodels/home.dart';
class HmCategory extends StatefulWidget {
//分类列表
final List<CategoryItem> categoryList;
const HmCategory({super.key, required this.categoryList});
@override
State<HmCategory> createState() => _HmCategoryState();
}
class _HmCategoryState extends State<HmCategory> {
@override
Widget build(BuildContext context) {
//返回一个横向滚动的组件,但是得设置高度。但是ListView自身不能设置高度.能设置高度的只有Container和SizeBox
return SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: widget.categoryList.length,
itemBuilder: (BuildContext context, int index) {
//从分类列表中获取数据
final categoryItem = widget.categoryList[index];
return Container(
alignment: Alignment.center,
width: 80,
height: 100,
decoration: BoxDecoration(
color: const Color.fromARGB(255, 231, 232, 234),
borderRadius: BorderRadius.circular(40),
),
margin: EdgeInsets.symmetric(horizontal: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.network(categoryItem.picture ?? "", width: 40, height: 40),
Text(categoryItem.name ?? "分类$index", style: TextStyle(color: Colors.black)),
],
),
);
},
),
);
}
}
UI优化效果:

三、提交代码
完成分类数据获取、模型构建、UI渲染及样式优化后,执行以下Git命令,将代码提交到远程仓库,保存当前开发进度
git add .
git commit -m "完成获取分类数据并渲染"
git push
四、总结
本文衔接上一节轮播图API数据获取内容,实现了电商App首页的分类数据获取与渲染,核心总结如下:
- 核心流程:本次开发的核心流程为「了解接口信息 → 构建实体类 → 封装API调用 → 页面获取数据 → 父传子传递数据 → UI动态渲染 → 样式优化」;
- 核心知识点:实体类工厂函数的使用、JSON与实体类的转换、Dio网络请求的复用、父传子数据传递、ListView.builder动态渲染列表、横向滚动配置、UI样式优化(背景、圆角、文本)、异常处理与容错设计;
- 代码规范与复用:复用上一节封装的Dio请求工具和常量类,无需重复开发请求逻辑;单独封装API调用方法和数据获取方法,分离业务逻辑与请求逻辑、UI逻辑,提升代码的可读性、可维护性和可复用性;
注意事项:
- 实体类字段需与接口返回字段一一对应,务必添加空值处理,避免空指针异常;
- 横向滚动的ListView必须固定高度(用SizedBox包裹),否则会出现显示异常;
- 网络图片加载需添加errorBuilder,处理图片加载失败的场景;
- 异步请求必须添加try/catch异常处理,避免请求失败导致页面崩溃;
- 父传子传递数据时,需在子组件的构造函数中声明必填参数,确保数据正常传递。
功能延伸:
- 添加分类点击事件:点击分类项,跳转到对应分类的详情页面;
- 添加加载状态:请求分类数据时,显示加载动画,请求完成后隐藏;
- 添加错误提示:请求失败时,显示Toast提示用户“加载分类失败”;
- 渲染子分类:若需要展示子分类,可在当前分类项点击后,弹出子分类列表或跳转子分类页面;
- 优化图片加载:添加图片缓存,提升图片加载速度,减少网络请求。
至此,电商App首页已实现分类数据获取与渲染。下一节,我们将继续完善首页其他业务组件的开发。
更多推荐




所有评论(0)