news 2026/5/12 12:55:10

Flutter 鸿蒙开发:分类数据 API 调用与动态渲染的实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter 鸿蒙开发:分类数据 API 调用与动态渲染的实现

首先,欢迎加入开源鸿蒙跨平台社区: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 } }
核心代码说明
  1. 字段设计:CategoryItem类的字段与接口返回的单条分类数据一一对应,children设为可选参数(List<CategoryItem>?),适配部分分类无children的场景;​
  2. 工厂函数逻辑:formJSON工厂函数接收Map<String, dynamic>类型参数,遍历JSON字段,将其赋值给CategoryItem的对应字段;​
  3. 空值处理:所有字段均添加?? ""默认值,避免接口返回字段为空导致的空指针异常;​
  4. 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()); } }
核心修改点说明
  1. 新增分类数据变量:List<CategoryItem> _categoryList = [];,初始化为空列表,用于存储从API获取的分类数据;​
  2. 初始化调用API:在initState中新增_getCategoryList()调用,确保页面加载时同步获取轮播图和分类数据;​
  3. 新增获取分类数据方法:_getCategoryList()方法通过async/await处理异步请求,调用getCategoryListAPI()获取数据,更新_categoryList并刷新页面;​
  4. 父传子传递数据:修改HmCategory组件调用,添加categoryList: _categoryList,将分类数据传递给分类组件;​
  5. 异常处理优化:给_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), ); }, ), ); } }

核心修改点说明

  1. itemCount:从固定值10改为widget.categoryList.length,让列表长度与真实分类数据数量一致,不出现多余占位或数据遗漏。
  2. 新增final categoryItem = widget.categoryList[index];,获取当前索引对应的真实分类数据对象。
  3. 子组件从单一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首页的分类数据获取与渲染,核心总结如下:

  1. 核心流程:本次开发的核心流程为「了解接口信息 → 构建实体类 → 封装API调用 → 页面获取数据 → 父传子传递数据 → UI动态渲染 → 样式优化」;​
  2. 核心知识点:实体类工厂函数的使用、JSON与实体类的转换、Dio网络请求的复用、父传子数据传递、ListView.builder动态渲染列表、横向滚动配置、UI样式优化(背景、圆角、文本)、异常处理与容错设计;​
  3. 代码规范与复用:复用上一节封装的Dio请求工具和常量类,无需重复开发请求逻辑;单独封装API调用方法和数据获取方法,分离业务逻辑与请求逻辑、UI逻辑,提升代码的可读性、可维护性和可复用性;

注意事项:

  • 实体类字段需与接口返回字段一一对应,务必添加空值处理,避免空指针异常;​
  • 横向滚动的ListView必须固定高度(用SizedBox包裹),否则会出现显示异常;​
  • 网络图片加载需添加errorBuilder,处理图片加载失败的场景;​
  • 异步请求必须添加try/catch异常处理,避免请求失败导致页面崩溃;​
  • 父传子传递数据时,需在子组件的构造函数中声明必填参数,确保数据正常传递。

功能延伸:

  • 添加分类点击事件:点击分类项,跳转到对应分类的详情页面;​
  • 添加加载状态:请求分类数据时,显示加载动画,请求完成后隐藏;​
  • 添加错误提示:请求失败时,显示Toast提示用户“加载分类失败”;​
  • 渲染子分类:若需要展示子分类,可在当前分类项点击后,弹出子分类列表或跳转子分类页面;​
  • 优化图片加载:添加图片缓存,提升图片加载速度,减少网络请求。

至此,电商App首页已实现分类数据获取与渲染。下一节,我们将继续完善首页其他业务组件的开发。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 12:50:46

手把手教你用雯雯的后宫-造相Z-Image生成瑜伽女孩图片

手把手教你用雯雯的后宫-造相Z-Image生成瑜伽女孩图片 你是不是也想过&#xff0c;不用请模特、不用租场地、不用专业摄影棚&#xff0c;就能生成一张自然、真实、富有生活气息的瑜伽女孩图片&#xff1f;今天这篇教程就带你实现这个目标——用“雯雯的后宫-造相Z-Image-瑜伽女…

作者头像 李华
网站建设 2026/5/9 18:50:34

手把手教你用浦语灵笔2.5:图片识别+问答实战教程

手把手教你用浦语灵笔2.5&#xff1a;图片识别问答实战教程 你是不是也试过把一张产品截图发给AI&#xff0c;结果它说“图中有一张桌子”——可那明明是份带公式的财务报表&#xff1f;或者上传孩子作业里的几何题&#xff0c;AI却把坐标轴认成栅栏&#xff1f;我第一次用多模…

作者头像 李华
网站建设 2026/5/11 6:35:14

手把手教你用PP-DocLayoutV3:表格/公式/文本一键分类

手把手教你用PP-DocLayoutV3&#xff1a;表格/公式/文本一键分类 PP-DocLayoutV3 是新一代统一文档布局分析引擎&#xff0c;专为真实场景下的复杂文档解析而生。它不依赖传统矩形框检测&#xff0c;而是采用实例分割技术输出像素级掩码与多点边界框&#xff08;四边形/多边形…

作者头像 李华
网站建设 2026/5/9 19:08:29

MedGemma X-Ray在临床教学中的应用:智能影像分析实战分享

MedGemma X-Ray在临床教学中的应用&#xff1a;智能影像分析实战分享 医学影像学是临床诊断的基石&#xff0c;更是医学生培养过程中最具挑战性的核心课程之一。一张胸部X光片上密布着数十个解剖结构、数百种异常征象&#xff0c;初学者常陷入“看得见却看不懂”的困境——肋骨…

作者头像 李华
网站建设 2026/5/10 7:56:41

5分钟搞定Pi0:通用机器人控制模型部署教程

5分钟搞定Pi0&#xff1a;通用机器人控制模型部署教程 1. 这不是科幻&#xff0c;是今天就能上手的机器人控制 你有没有想过&#xff0c;让机器人看懂你的指令、理解周围环境、再精准执行动作——这个过程其实可以像启动一个网页应用一样简单&#xff1f;Pi0 就是这样一个正在…

作者头像 李华