继续接着上一次Flutter项目实战之女装商城------商品分类模块实现之数据模型、model测试、分类Provide、编写一级分类界面、首页分类导航处理、编写二级分类界面的分类模块往下编写,先来回忆一下目前分类的样子:
接下来则需要来处理分类的商品列表的显示了。
分类商品数据处理:
数据准备:
node中准备分类商品数据:
先回到node接口工程:
其工程目录结构在Visual Studio Code这里回顾一下:
然后这里的数据接口准备基本跟之前的雷同,这里就拷贝一下之前的category.js:
其主要是修改以下几个点,结构完全不需要动:
整个代码如下:
const express = require("express");
const router = express();
const config = require("./config");
const url = "http://" + config.IP + ":" + config.PORT + "/images/goods/";
router.post("/",(req,res) => {
var categoryGoods = {
"code":"0",
"message":"success",
"data":[
{
"name": "法国代购新款江疏影同款翻领修身中长裙春夏印花连衣裙",
"image": url + "001/cover.jpg",
"presentPrice": 98.88,
"goodsId": "001",
"oriPrice": 108.88
}, {
"name": "柔美而精致~高贵而优雅~圆领金银丝春季毛衣羊毛开衫女短款白外套",
"image": url + "002/cover.jpg",
"presentPrice": 229.90,
"goodsId": "002",
"oriPrice": 320.99
}, {
"name": "明星同款高端西服2019春装新款韩版英伦风短款格子小西装女外套潮",
"image": url + "003/cover.jpg",
"presentPrice": 318.88,
"goodsId": "003",
"oriPrice": 388.88
}, {
"name": "复古廓形机车进口绵羊皮衣真皮外套女E142",
"image": url + "004/cover.jpg",
"presentPrice": 238.99,
"goodsId": "004",
"oriPrice": 248.99
}, {
"name": "单排扣高腰牛仔裤女春夏薄款紧身弹力小脚裤显瘦百搭网红浅色长裤",
"image": url + "005/cover.jpg",
"presentPrice": 588.99,
"goodsId": "005",
"oriPrice": 888.88
}, {
"name": "MIUCO女装夏季重工星星烫钻圆领短袖宽松显瘦百搭T恤上衣k",
"image": url + "006/cover.jpg",
"presentPrice": 1028.88,
"goodsId": "006",
"oriPrice": 1888.88
},
{
"name": "春夏一步裙包臀裙开叉弹力修身显瘦短裙黑色高腰职业半身裙",
"image": url + "007/cover.jpg",
"presentPrice": 2388.66,
"goodsId": "007",
"oriPrice": 2888.88
}, {
"name": "夏季新款短袖圆领紧身小黑超短裙开叉包臀性感连衣裙夜店女装",
"image": url + "008/cover.jpg",
"presentPrice": 666.88,
"goodsId": "008",
"oriPrice": 888.88
},
]
};
res.send(categoryGoods);
});
module.exports = router;
接着再添加一下路由的路径:
测试数据:
为了方便在浏览器中请求测试,这里先将post改为get:
然后重启一下node,在浏览器访问一下该地址:
另外确保里面的图片地址访问都正常:
如果图片访问不了,记得要确保这块的ip地址必须是你本机当前ip:
毕竟本地ip是很容易进行变化的,最后别忘了将get还原成post哟~~
数据获取:
逻辑编写:
接下来则来回到Flutter工程进行分类商品的数据获取,这里打算先在这块进行测试:
也就是点击一级分类时,默认应该显示二级分类的“全部”对吧?很明显这个方法需要接收两个参数的,一个是一级分类id:
另一个就是二级分类id:
所以,定义如下:
接下来则来发起请求,而请求时先来准备一下入参:
接下来则就是发起请求,由于之前已经对请求做了封装:
所以这块代码比较简单,如下:
运行测试:
接下来运行看一下,其分类商品数据能否正常请求下来:
而对于二级分类的点击,咱们也可以加上这次请求逻辑了:
另外有一个细节可以优化一下,就是对于点击一级分类时,其不知道二级分类的id,此时咱们在调用时还是传了一个二级参数,只是传了个null对吧:
很显然这种传参是木有意义的,此时就可以将这两参数定义成可选的,如下:
这样咱们在调用时就可以针对性的进行参数选择,只传必要的:
所以,正好借此可以复习一下可选参数的知识。
数据模型:
目前咱们的接口请求都正常了之后,接下来就需要将json进行model的转换了。
1、新建model:
这块的定义跟之前分类的model差不多,比较简单,就不过多说明了,将代码贴出来,基本Model的定义都是一种套路:
class CategoryGoodsListModel{
String code;
String message;
List<CategoryListData> data;
CategoryGoodsListModel({this.code,this.message,this.data});
CategoryGoodsListModel.fromJson(Map<String,dynamic> json){
code = json['code'];
message = json['message'];
if(json['data'] != null){
data = new List<CategoryListData>();
json['data'].forEach((v){
data.add(new CategoryListData.fromJson(v));
});
}
}
Map<String,dynamic> toJson(){
final Map<String,dynamic> data = new Map<String,dynamic>();
data['code'] = this.code;
data['message'] = this.message;
if(this.data != null){
data['data'] = this.data.map((v) => v.toJson()).toList();
}
return data;
}
}
class CategoryListData{
String image;
double oriPrice;
double presentPrice;
String name;
String goodsId;
CategoryListData({this.image,this.oriPrice,this.presentPrice,this.name,this.goodsId});
CategoryListData.fromJson(Map<String,dynamic> json){
image = json['image'];
oriPrice = json['oriPrice'];
presentPrice = json['presentPrice'];
name = json['name'];
goodsId = json['goodsId'];
}
Map<String,dynamic> toJson(){
final Map<String,dynamic> data = new Map<String,dynamic>();
data['image'] = this.image;
data['oriPrice'] = this.oriPrice;
data['presentPrice'] = this.presentPrice;
data['name'] = this.name;
data['goodsId'] = this.goodsId;
return data;
}
}
接下来咱们就可以将接口请求下来的数据进行model转换了,如下:
Provide处理:
现在分类商品的接口数据也已经解析好了,接下来则需要将它显示在界面上对吧,这里则打算用Provide来进行数据状态的管理,关于为啥要使用Provide来进行状态管理可以参考Flutter项目实战之女装商城------导航栏添加、状态管理、屏幕适配。
新建Provide:
发现之前有的命名不太对:
这里顺带着给修正一下:
其里面的内容也比较简单,目前只需要保存分类商品列表:
使用了Provide之后,其实就可以发现把数据的处理跟界面进行了剥离了,有益无害,当然这种开发方式需要适应一下,习惯成自然。
2、将列表数据设置到Provide中:
有了Provide之后,我们就可以把请求到转换后的实体数据设置到里面了,如下:
注意,这样代码写不太完善哟,因为有可能data为空,所以这里处理一下空的情况,如下:
3、配置Provide:【重点!!!这个容易遗忘】
最后别忘了在这配置一下,不然运行是会报错的:
界面实现:
这里还是用EasyRefresh来构建上拉加载更多效果,关于EasyRefresh的使用可以参考Flutter项目实战之女装商城------首页设计分析、数据准备、Dio请求处理、接口配置、请求首页数据。
1、先来定义GlobalKey:
2、整个界面由Provide包裹:
既然我们的数据是由Provide管理了,那怎么能在咱们的界面上进行状态变更呢,此时就需要在构建界面时由Provide进行包裹了:
3、滚动处理:
接下来先来处理一个滚动的状态,就是每次我们点击一级分类时,此时列表都需要让它滚动到0项的位置才行,因为新分类都是从0开始展示,这块场景也很容易脑补出来,那如何来做呢?首先当然得先定义一个滚动控制的变量:
那怎么判断用户是否切换了一级分类呢?其实也很简单,在分类的Provide中每次切换时是会执行它的:
也就是我们只要判断page为1的情况下,就证明用户是切换了一级分类,此时请求分类商品时就需要主动将列表滚动到0处的位置,如下:
另外有可能滚动会发生异常,这里主动捕获一下:
4、处理列表为空的情况:
接下来则需要判断返回的商品数据是否为空,先把边界条件处理了,如果为空,则界面给个提示:
其中将字符串提到KString当中:
有个好习惯提示一下,就是在用到KString的地方可以加一个注释:
这样在你全局搜索时,可以快速进行代码定位。
5、构建列表:
1、用Expanded包裹:
接下来则需要将数据展示到列表当中,这里为了防止显示溢出,用它包裹一下:
2、refreshFooter:
先来定义一下加载更多的提示区域,这块在之前都用过,直接给出代码了:
3、构建ListView:
用这种构建方式可以么?这里需要控制滚动,需要使用builder方式,关于这块的基础可以参考开启Fluter基础之旅<五>-------ListView 3D滚动、Flipper效果、ListView下拉刷新&上拉加载、ListView重排序 - cexo - 博客园,如下:
4、构建列表项:
1、整体框架搭建:
接下来构建具体的列表项,这里将其封装到一个方法中:
接下来则来根据效果图来具体列表项的构建:
2、定义整条的样式:
3、定义横向布局:
从效果图中也可以看出,整体是一个横向的布局,所以:
4、商品图片元素构建:
接下来则需要来构建里面的元素了,还是按照封装的思想,将每一个元素都进行封装,先来构建左侧的商品图片:
接下来运行看一下目前的样子:
嗯,整体的样子出来了,不过这里发现有个bug,就是第一次进来发现数据没加载出来,这个先忽略,为了学习完整性待整个布局都完成之后再来解。
5、剩余元素构建:
接下来则来将剩下的元素给构建了,这里直接贴出,比较简单:
//商品列表项
Widget _ListWidget(List newList, int index) {
return InkWell(
onTap: () {
//TODO 跳转到商品详情页
},
child: Container(
padding: EdgeInsets.only(top: 5.0, bottom: 5.0), //给上下加边距
decoration: BoxDecoration(
//给底部增加一条分隔线
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: KColor.defaultBorderColor),
)),
child: Row(
children: <Widget>[
_goodsImage(newList, index), //商品图片
Column(
children: <Widget>[
_goodsName(newList, index),
_goodsPrice(newList, index),
],
),
],
),
),
);
}
//商品图片
Widget _goodsImage(List newList, int index) {
return Container(
width: ScreenUtil().setWidth(200),
child: Image.network(newList[index].image),
);
}
//商品名称
Widget _goodsName(List newList, int index) {
return Container(
padding: EdgeInsets.all(5.0),
width: ScreenUtil().setWidth(370),
child: Text(
newList[index].name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: ScreenUtil().setSp(28)),
),
);
}
//商品价格
Widget _goodsPrice(List newList, int index) {
return Container(
margin: EdgeInsets.only(top: 20.0),
width: ScreenUtil().setWidth(370),
child: Row(
children: <Widget>[
Text(
'价格:¥${newList[index].presentPrice}',
style: TextStyle(color: KColor.presentPriceTextColor),
),
Text(
'¥${newList[index].oriPrice}',
style: KFont.oriPriceStyle,
),
],
),
);
}
其中一定要有widget分封装的思想,不然代码嵌套层级会非常深,代码可读性也比较差,下面运行看一下:
6、加载更多商品实现:
接下来则来处理上拉加载更多分页处理。
定义加载方法:
首先需要将咱们分类的page++:
为啥呢?因为咱们在这块有这么一个逻辑:
接下来则来发起接口请求,如下:
其中还涉及到一个字符串需要提取一下:
界面实现:
接下来则需要回到界面上来调用一下,如下:
其中Toast可以用到三方的一个框架:fluttertoast | Flutter Package,
这里将其集成一下,这里发现用最新版本我工程编译出错了。。
目前我本机用的Dart sdk是2.9.1版本的,貌似是不支持空安全的,而这个FlutterToast在最新版本都使用到空安全选择了:
关于空安全我记得之前有一个网友在评论区特意提到过:
关于这块dart版本的升级打算下一次搞一搞,不然一直基于老版本学习会跟不上潮流的,这里为了学习的节奏暂且使用一个稍低一点的版本,也就是它:
此时就可以用它来弹toast啦,如下:
此时又有一个文本产生,提至公共位置 :
接下来运行看一下效果:
现在由于接口数据每次都返回的是同样的数据,所以界面上看着会比较奇怪,这里关注点主要是在app前端,毕竟不是后端开发,整个app端的逻辑还是按正常的分页逻辑来写的,只是由于目前咱们的api是一个模拟数据而已。
7、Bug修复:
整个分类商品列表加载的逻辑基本已经实现完了,不过在上文中提到过目前存在有一个bug:
也就是第一次切到分类tab时,其内容显示不出来,这里再来演示一下:
首先第一个问题是全部的右侧二级分类菜单木有出来:
这个简单,在获取一级分类数据之后,主动更改一下Provide的状态既可,如下:
因为在secondCategory中就对于二级分类进行了处理:
这样在右侧菜单栏这块就可以实时的监听到数据变化了:
此时运行看一下:
好,接下来则来解决第二个问题,就是此时商品列表出不来的问题,其实也比较简单,只要在左侧菜单栏构建时再请求一次商品列表接口既可,如下:
因为第一次进来是不会触发左侧第一个菜单的这个事件的:
当然也就不会发起商品列表的请求喽,所以解决的话就是主动再调一次既可,再运行:
总结:
至此,整个分类的界面功能就已经完成了,整个功能还是挺麻烦的,其重点还是要学会使用Provider来进行状态管理,接下来则需要点击查看商品详情,不过下次开篇首先打算解决空安全的特性问题,让本地的Dart库升级一下。
关注个人公众号,获得实时推送