前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
功能概述
本次开发的是首页的推荐列表模块,用于展示商品推荐内容。
-
接口地址 :
GET /home/recommend -
返回数据类型 :商品详情项列表(
List<GoodDetailItem>) -
默认加载:10条数据
-
展示形式:网格布局,展示商品图片、名称和价格
一、添加接口常量
添加请求地址接口的常量
Dart
// 推荐列表
static const String RECOMMEND_LIST = "/home/recommend";
lib/constants/index.dart代码
Dart
//全局状态
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";
//特惠推荐地址
static const String PRODUCT_LIST = "/hot/preference";
// 热榜推荐地址
static const String IN_VOGUE_LIST = "/hot/inVogue";
// 一站式推荐地址
static const String ONE_STOP_LIST = "/hot/oneStop";
// 推荐列表
static const String RECOMMEND_LIST = "/home/recommend";
}
二、定义列表项数据类型
在 lib/viewmodels/home.dart 中新增 GoodDetailItem 类,继承自 GoodsItem,扩展 payCount 字段:
Dart
class GoodDetailItem extends GoodsItem {
int payCount = 0;
/// 商品详情项
GoodDetailItem({
required super.id,
required super.name,
required super.price,
required super.picture,
required super.orderNum,
required this.payCount,
}) : super(desc: "");
// 转化方法
factory GoodDetailItem.formJSON(Map<String, dynamic> json) {
return GoodDetailItem(
id: json["id"]?.toString() ?? "",
name: json["name"]?.toString() ?? "",
price: json["price"]?.toString() ?? "",
picture: json["picture"]?.toString() ?? "",
orderNum: int.tryParse(json["orderNum"]?.toString() ?? "0") ?? 0,
payCount: int.tryParse(json["payCount"]?.toString() ?? "0") ?? 0,
);
}
}
lib/viewmodels/home.dart代码:
Dart
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"] ?? "");
}
}
// 商品项
class GoodsItem {
String id;
String name;
String? desc;
String price;
String picture;
int orderNum;
GoodsItem({
required this.id,
required this.name,
this.desc,
required this.price,
required this.picture,
required this.orderNum,
});
factory GoodsItem.fromJSON(Map<String, dynamic> json) {
return GoodsItem(
id: json["id"] ?? "",
name: json["name"] ?? "",
desc: json["desc"],
price: json["price"] ?? "",
picture: json["picture"] ?? "",
orderNum: json["orderNum"] ?? 0,
);
}
}
// 商品列表
class GoodsItems {
int counts;
int pageSize;
int pages;
int page;
List<GoodsItem> items;
GoodsItems({
required this.counts,
required this.pageSize,
required this.pages,
required this.page,
required this.items,
});
factory GoodsItems.fromJSON(Map<String, dynamic> json) {
return GoodsItems(
counts: json["counts"] ?? 0,
pageSize: json["pageSize"] ?? 0,
pages: json["pages"] ?? 0,
page: json["page"] ?? 0,
items: (json["items"] as List?)
?.map((item) => GoodsItem.fromJSON(item as Map<String, dynamic>))
.toList() ?? [],
);
}
}
// 子类型
class SubType {
String id;
String title;
GoodsItems goodsItems;
SubType({
required this.id,
required this.title,
required this.goodsItems,
});
factory SubType.fromJSON(Map<String, dynamic> json) {
return SubType(
id: json["id"] ?? "",
title: json["title"] ?? "",
goodsItems: GoodsItems.fromJSON(json["goodsItems"] ?? {}),
);
}
}
// 特惠推荐结果
class SpecialOfferResult {
String id;
String title;
List<SubType> subTypes;
SpecialOfferResult({
required this.id,
required this.title,
required this.subTypes,
});
factory SpecialOfferResult.fromJSON(Map<String, dynamic> json) {
return SpecialOfferResult(
id: json["id"] ?? "",
title: json["title"] ?? "",
subTypes: (json["subTypes"] as List?)
?.map((item) => SubType.fromJSON(item as Map<String, dynamic>))
.toList() ?? [],
);
}
}
//每一个轮播图具体类型
//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
}
}
class GoodDetailItem extends GoodsItem {
int payCount = 0;
/// 商品详情项
GoodDetailItem({
required super.id,
required super.name,
required super.price,
required super.picture,
required super.orderNum,
required this.payCount,
}) : super(desc: "");
// 转化方法
factory GoodDetailItem.formJSON(Map<String, dynamic> json) {
return GoodDetailItem(
id: json["id"]?.toString() ?? "",
name: json["name"]?.toString() ?? "",
price: json["price"]?.toString() ?? "",
picture: json["picture"]?.toString() ?? "",
orderNum: int.tryParse(json["orderNum"]?.toString() ?? "0") ?? 0,
payCount: int.tryParse(json["payCount"]?.toString() ?? "0") ?? 0,
);
}
}
三、封装API请求
在 lib/api/home.dart 中添加推荐列表的请求方法,支持传入参数:
Dart
// 推荐列表
Future<List<GoodDetailItem>> getRecommendListAPI(
Map<String, dynamic> params,
) async {
// 返回请求
return ((await dioRequest.get(HttpConstants.RECOMMEND_LIST, params: params))
as List)
.map((item) {
return GoodDetailItem.formJSON(item as Map<String, dynamic>);
})
.toList();
}
lib/api/home.dart完整代码
Dart
//封装一个api 目的是返回业务侧要的数据结构
import 'package:qing_mall/constants/index.dart';
import 'package:qing_mall/utils/DioRequest.dart';
import 'package:qing_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();
}
//特惠推荐地址
Future<SpecialOfferResult> getProductListAPI() async {
// 返回请求
return SpecialOfferResult.fromJSON(
await dioRequest.get(HttpConstants.PRODUCT_LIST));
}
// 热榜推荐
Future<SpecialOfferResult> getInVogueListAPI() async {
// 返回请求
return SpecialOfferResult.fromJSON(
await dioRequest.get(HttpConstants.IN_VOGUE_LIST),
);
}
// 一站式推荐
Future<SpecialOfferResult> getOneStopListAPI() async {
// 返回请求
return SpecialOfferResult.fromJSON(
await dioRequest.get(HttpConstants.ONE_STOP_LIST),
);
}
// 推荐列表
Future<List<GoodDetailItem>> getRecommendListAPI(
Map<String, dynamic> params,
) async {
// 返回请求
return ((await dioRequest.get(HttpConstants.RECOMMEND_LIST, params: params))
as List)
.map((item) {
return GoodDetailItem.formJSON(item as Map<String, dynamic>);
})
.toList();
}
四、页面数据初始化
在首页视图lib/pages/Home/index.dart中添加状态变量
Dart
HmMoreList(recommendList: _recommendList), // 无限滚动列表
添加获取数据方法
Dart
// 获取推荐列表
void _getRecommendList() async {
_recommendList = await getRecommendListAPI({"limit": 10});
setState(() {});
}
在initState中调用
Dart
@override
void initState() {
super.initState();
_getBannederList();
_getCategoryList();
_getProductList();
_getInVogueList();
_getOneStopList();
_getRecommendList(); // 新增
}
报错是因为还没实现对应的结构,接下来我们实现对应的结构。

五、实现无限滚动列表组件
修改lib/components/Home/HmMoreList.dart代码:
Dart
import 'package:flutter/material.dart';
import 'package:qing_mall/viewmodels/home.dart';
class HmMoreList extends StatefulWidget {
final List<GoodDetailItem> recommendList;
const HmMoreList({super.key, required this.recommendList});
@override
State<HmMoreList> createState() => _HmMoreListState();
}
class _HmMoreListState extends State<HmMoreList> {
@override
Widget build(BuildContext context) {
//必须是Sliver家族的组件
return SliverGrid.builder(
gridDelegate:
//网格是两列
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.75,
),
itemCount: widget.recommendList.length,
itemBuilder: (BuildContext context, int index) {
final item = widget.recommendList[index];
return Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
item.picture,
width: double.infinity,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
"lib/assets/goods_avatar.png",
width: double.infinity,
fit: BoxFit.cover,
);
},
),
),
),
SizedBox(height: 8),
Text(
item.name,
style: TextStyle(fontSize: 14),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4),
Text(
"¥${item.price}",
style: TextStyle(
fontSize: 16,
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
],
),
);
},
);
}
}
六、运行效果
推荐列表以网格形式展示在首页底部,默认加载10条商品数据,包含:
-
商品图片(支持加载失败占位图)
-
商品名称(最多显示两行)
-
商品价格(红色加粗显示)
运行效果如下:

目前,最多只能加载出来10条数据。因为我们写死了只能加载10条,所以我们在下一篇文章加入上拉加载,让他能继续加载更多数据。
最后别忘记了提交到AtomGit代码托管平台
