火爆专区实现:
继续Flutter项目的学习,在上一次https://www.cnblogs.com/webor2006/p/14410445.html咱们已经对于首页的大部分功能已经完成,但是呢,还差一个上拉加载时的一个处理,回忆一下目前的状态:
接下来则来完成上拉加载更多功能,其实这里上拉加载更多时是加载的火爆专区的商品,具体效果如下:
数据准备:
接下来首先准备接口数据JSON,还记得这个项目的后台数据是用什么提供的么?对的,node:
准备也很简单,依照之前的框架添加一个火爆专区的接口就成,下面简单复习一下接口准备的步骤:
1、准备一个对应的接口js:
const express = require("express"); const router = express(); const config = require("./config"); const base_url = "http://" + config.IP + ":" + config.PORT + "/images/goods/"; router.post("/",(req,res) => { var hotGoods = { "code":"0", "message":"success", "data":[ { "name": "法国代购新款江疏影同款翻领修身中长裙春夏印花连衣裙", "image": base_url + "001/cover.jpg", "presentPrice": 98.88, "goodsId": "001", "oriPrice": 108.88 }, { "name": "柔美而精致~高贵而优雅~圆领金银丝春季毛衣羊毛开衫女短款白外套", "image": base_url + "002/cover.jpg", "presentPrice": 229.90, "goodsId": "002", "oriPrice": 320.99 }, { "name": "明星同款高端西服2019春装新款韩版英伦风短款格子小西装女外套潮", "image": base_url + "003/cover.jpg", "presentPrice": 318.88, "goodsId": "003", "oriPrice": 388.88 }, { "name": "复古廓形机车进口绵羊皮衣真皮外套女E142", "image": base_url + "004/cover.jpg", "presentPrice": 238.99, "goodsId": "004", "oriPrice": 248.99 }, { "name": "单排扣高腰牛仔裤女春夏薄款紧身弹力小脚裤显瘦百搭网红浅色长裤", "image": base_url + "005/cover.jpg", "presentPrice": 588.99, "goodsId": "005", "oriPrice": 888.88 }, { "name": "MIUCO女装夏季重工星星烫钻圆领短袖宽松显瘦百搭T恤上衣k", "image": base_url + "006/cover.jpg", "presentPrice": 1028.88, "goodsId": "006", "oriPrice": 1888.88 }, { "name": "春夏一步裙包臀裙开叉弹力修身显瘦短裙黑色高腰职业半身裙", "image": base_url + "007/cover.jpg", "presentPrice": 2388.66, "goodsId": "007", "oriPrice": 2888.88 }, { "name": "夏季新款短袖圆领紧身小黑超短裙开叉包臀性感连衣裙夜店女装", "image": base_url + "008/cover.jpg", "presentPrice": 666.88, "goodsId": "008", "oriPrice": 888.88 }, ] }; res.send(hotGoods); }); module.exports = router;
也就是指向本地的这些商品资源:
注意:这里是用的post方式。
2、app.js中将新建的接口js注册一下:
3、重启npm,验证一下接口能否正常访问
这个很好理解,浏览器中敲入请求的肯定都是get方式,所以为了验证接口数据是否通,这里先将接口请求方式由post改为get,待验证通过之后再将其还原,如下:
再重启npm试一下:
其中点击一下图片,确保都能正常访问:
嗯,那我们火爆专区的数据已经准备好了,注意:别忘了将接口的请求方式要还原成post了哟。
另外还有一个小点提前说明一下,如上面火爆专区的效果展示,很明显是支持上拉分页加载的,但是这里只是为了简单模拟,就没有分页的处理逻辑了,每次分页加载都返同样的数据,重点是咱们的Flutter端是一个“活”的数据展现形式,而后端的数据是死的还是活的对于其实丝毫不影响咱们整个项目的学习。
数据获取:
接下来则回到Android studio Flutter端进入代码的编写,首先来对请求火爆专区的接口数据,确保数据畅通无阻:
1、发起请求:
其实现逻辑也比较简单,先请求,注意这里肯定是需要用一个page参数的,因为要支持分页,如下:
注意:其中request的接口名getHotGoods需要在这块注册一下:
此时运行看一下效果,发现报错了。。
这里重新重启一下npm就可以了,应该是之前改回post方式时服务木有重启,再运行就对了,如下:
2、response处理:
接下来则来处理reponse中的json数据,具体如下:
Wrap布局:
分析:
接下来则来进行界面布局了,这里需要用到一个Wrap布局方式了,简单对它有一个了解,像咱们目前界面是一行2列均匀分布对吧:
明显应该用列表相关的布局的,但是呢这里采用一个Wrap布局,它其实类似于流式布局的效果,会根据宽度自动的来决定一行显示多少个,比如像这种一行可以显示四个:
其中看到细节木有,就是上面的“第11集”之后,由于没有元素了,则就会空着,其实刚好就符合咱们目前商品列表的场景。
实现:
接下来则来先使用Wrap构建列表项。
1、 定义Wrap:
2、定义children:
这里如何将咱们的数据给转换成一个Widget列表呢?具体如下:
3、构建列表项元素:
接下来则来构建InkWell中Container的元素,也就是:
首先是定义上下结构:
先来定义一个图片:
其中BoxFit.cover是啥意思可以参考https://www.cnblogs.com/webor2006/p/14410445.html:
接着再定义商品的标题,比较简单:
最后就是价格,横向布局,如下:
//火爆专区子项 Widget _wrapList() { if (hotGoodsList.length != 0) { List<Widget> listWidget = hotGoodsList.map((val) { return InkWell( onTap: () { //TODO 跳转到商品详情 }, child: Container( ScreenUtil().setWidth(372), color: Colors.white, padding: EdgeInsets.all(5.0), margin: EdgeInsets.only(bottom: 3.0), child: Column( children: <Widget>[ Image.network( //商品图片 val['image'], ScreenUtil().setWidth(375), height: 200, fit: BoxFit.cover, ), Text( //商品名称 val['name'], maxLines: 1, overflow: TextOverflow.ellipsis, //单行,后面用... style: TextStyle(fontSize: ScreenUtil().setSp(26)), ), Row( //商品价格 children: <Widget>[ Text( '¥${val['presentPrice']}', style: TextStyle(color: KColor.presentPriceTextColor), ), Text( '¥${val['oriPrice']}', style: TextStyle(color: KColor.oriPriceColor), ), ], ), ], ), ), ); }).toList(); return Wrap( spacing: 2, //主轴上子控件中间的间距 children: listWidget, //要显示的子控件集合 ); } else { return Text(''); } } }
其中涉及到一个色值,提取到公共位置:
布局组合:
由于在列表上面需要再加上一个标题:
然后再将整个给串起来,所以先来构建一个标题widget:
接下来将其组合挂到首页界面上来看看效果:
然后将此组合Widget挂到列表中:
看到一个好的Flutter编码的技巧了么?其实就是将每一个细节都进行一个封装,从局部构建,再到整体拼装,这样实现思路也不会觉得特别乱,而且代码层次也比较清晰,对于Flutter它的widget本身就是一层套一层的,如果将所有细节全直接实现不进行封装,真的到了商业大型项目业务复杂时到时维护起来会非常头痛的,所以对于Flutter的开发建议按这种封装的思路一环套一环的来实现。
好,接下来可以运行看一下效果:
首页功能总结:
至此,对于首页相关的界面就基本搭建完毕了,从启动学习到现在也横跨近大半年了【学习速度比蜗牛还慢,可能有些人会觉得当你学习完之后,技术也落伍了,是的,技术落伍归落伍,但不靠它挣钱落伍了也无妨,另外还有一个层面我始终坚信:即使落伍的技术在未来新技术的学习中也能发挥它的威力,没有哪一门新技术是完全独立的,另外其实技术的一些思想都是可以相互借鉴的,慢点学,精点学,学透它,让所学的真正能成为你自己成长技能树上的一员其实是很重要的,哪怕你自己跟着敲一遍那也会有很多的收获,那就够了~~】,所以有必要回头来简单梳理一下通过首页模块的编写,学到了哪些东东,温故知新不管对于学啥都非常重要,没有人能过目不忘,但是呢如果你把整个当时的过程全给备忘了那即使忘了那一点都不慌,心灵鸡汤就不多说了,整体挼一挼。
代码结构:
整体结构还是非常清晰的:
另外对于列表中的每一个条目都是通过封装的思路来做的:
那如果说没有一个封装的思想,所有的模块直接在里面嵌套实现,那代码是不是变得非常难维护,本身Flutter这种嵌套的结构就有点乱,所以这块思想是要重点学习的!!!
实现的功能:
Banner轮播图:
所对应的代码:
商品分类:
所对应的代码:
然后可以熟悉一下GridView的使用。
商品推荐及广告:
其中重点是要学会如何学会搭建这样一个竖横混排的效果:
左二张图,右三张图,回忆一下代码:
火爆专区:
这个是才完成的,还是热的,就不过多说明了。
技术点:
wantKeepAlive属性意义:
这样界面在切走之后就不会频繁创建了,类似于Android中的Fragment的tag切换时实例不被销毁,平常有此需要一定要记得配置它。
Wrap布局:
这是算是一个新的知识点吧,以前木有使用过,用用就会了:
商品分类实现准备:
设计分析:
接下来则来开启一个全新的模块的编写,也挺复杂的,先来瞅瞅效果:
其中可以看到有2级分类,也是很多电商非常常见的一种展现形式。
先来分析一下该界面的接口数据,应该是有两个:
1、getCategory:获取分类的标签,包含一二级。
2、getCategoryGoods:获取分类下面的商品列表。
另外从首页点击需要支持跳到某个分类下面来,所以这块在设计时也得考虑一下。
界面主框架搭建:
接下来回到程序中来对分类界面进行一个主框架搭建:
1、脚手架构建:
2、左右侧内容区域搭建:
这里先来占个位,具体逻辑之后再去实现,如下:
import 'package:flutter/material.dart'; import 'package:fluttershop/config/string.dart'; class CategoryPage extends StatefulWidget { @override _CategoryPageState createState() => _CategoryPageState(); } class _CategoryPageState extends State<CategoryPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( //商品分类 title: Text(KString.categoryTitle), ), body: Container( child: Row( children: <Widget>[ LeftCategoryNav(), Column( children: <Widget>[ RightCategoryNav(), CategoryGoodsList(), ], ), ], ), ), ); } } //左侧导航菜单 class LeftCategoryNav extends StatefulWidget { @override _LeftCategoryNavState createState() => _LeftCategoryNavState(); } class _LeftCategoryNavState extends State<LeftCategoryNav> { @override Widget build(BuildContext context) { return Text('左侧分类'); } } //右侧导航菜单 class RightCategoryNav extends StatefulWidget { @override _RightCategoryNavState createState() => _RightCategoryNavState(); } class _RightCategoryNavState extends State<RightCategoryNav> { @override Widget build(BuildContext context) { return Text('右侧分类'); } } //商品列表 class CategoryGoodsList extends StatefulWidget { @override _CategoryGoodsListState createState() => _CategoryGoodsListState(); } class _CategoryGoodsListState extends State<CategoryGoodsList> { @override Widget build(BuildContext context) { return Text('商品列表'); } }
此时运行预览一下:
当然此时还是一个无比丑陋的界面,之后再慢慢润色。
分类数据准备:
这块没啥好说的,回到node端,整就完事了:
const express = require("express"); const router = express(); const config = require("./config"); const category_url = "http://" + config.IP + ":" + config.PORT + "/images/category/"; router.post("/",(req,res) => { var data = { "code":"0", "message":"success", "data":[ { "firstCategoryId": "1", "firstCategoryName": "毛衣", "secondCategoryVO": [{ "secondCategoryId": "11", "firstCategoryId": "1", "secondCategoryName": "羊绒", "comments": "" }, ], "comments": null, "image": category_url + "1.png" }, { "firstCategoryId": "2", "firstCategoryName": "西服", "secondCategoryVO": [{ "secondCategoryId": "21", "firstCategoryId": "2", "secondCategoryName": "小西服", "comments": "" }, { "secondCategoryId": "22", "firstCategoryId": "2", "secondCategoryName": "职业装", "comments": "" }, ], "comments": null, "image": category_url + "2.png" }, { "firstCategoryId": "3", "firstCategoryName": "皮衣", "secondCategoryVO": [{ "secondCategoryId": "31", "firstCategoryId": "3", "secondCategoryName": "真皮皮衣", "comments": "" }, { "secondCategoryId": "32", "firstCategoryId": "3", "secondCategoryName": "仿皮皮衣", "comments": "" }], "comments": null, "image": category_url + "3.png" }, { "firstCategoryId": "4", "firstCategoryName": "连衣裙", "secondCategoryVO": [{ "secondCategoryId": "41", "firstCategoryId": "4", "secondCategoryName": "半身裙", "comments": "" }, { "secondCategoryId": "42", "firstCategoryId": "4", "secondCategoryName": "打底裙", "comments": "" }, ], "comments": null, "image": category_url + "4.png" }, { "firstCategoryId": "5", "firstCategoryName": "牛仔裤", "secondCategoryVO": [{ "secondCategoryId": "51", "firstCategoryId": "5", "secondCategoryName": "阔腿牛仔裤", "comments": "" }, { "secondCategoryId": "52", "firstCategoryId": "5", "secondCategoryName": "紧身牛仔裤", "comments": "" }], "comments": null, "image": category_url + "5.png" }, { "firstCategoryId": "6", "firstCategoryName": "T恤", "secondCategoryVO": [{ "secondCategoryId": "61", "firstCategoryId": "6", "secondCategoryName": "印花T恤", "comments": "" }, { "secondCategoryId": "62", "firstCategoryId": "6", "secondCategoryName": "字母T恤", "comments": "" }, ], "comments": null, "image": category_url + "6.png" }, { "firstCategoryId": "7", "firstCategoryName": "运动装", "secondCategoryVO": [{ "secondCategoryId": "71", "firstCategoryId": "7", "secondCategoryName": "春季运动装", "comments": "" }, { "secondCategoryId": "72", "firstCategoryId": "7", "secondCategoryName": "秋季运动装", "comments": "" }, ], "comments": null, "image": category_url + "7.png" }, { "firstCategoryId": "8", "firstCategoryName": "短裙", "secondCategoryVO": [{ "secondCategoryId": "81", "firstCategoryId": "8", "secondCategoryName": "宽松", "comments": "" }, { "secondCategoryId": "82", "firstCategoryId": "8", "secondCategoryName": "包臀", "comments": "" }, ], "comments": null, "image": category_url + "8.png" }, { "firstCategoryId": "9", "firstCategoryName": "礼服", "secondCategoryVO": [{ "secondCategoryId": "91", "firstCategoryId": "9", "secondCategoryName": "晚礼服", "comments": "" }, { "secondCategoryId": "92", "firstCategoryId": "9", "secondCategoryName": "婚纱", "comments": "" }, ], "comments": null, "image": category_url + "9.png" }, { "firstCategoryId": "10", "firstCategoryName": "风衣", "secondCategoryVO": [{ "secondCategoryId": "101", "firstCategoryId": "10", "secondCategoryName": "中长款", "comments": "" }, { "secondCategoryId": "102", "firstCategoryId": "10", "secondCategoryName": "长款", "comments": "" }, ], "comments": null, "image": category_url + "10.png" }, ] }; res.send(data); }); module.exports = router;
接下来注册一下接口:
这里为了能在浏览器中直接访问该接口来确保接口是畅通的,还是先将post改为get,如下:
好,重启npm,然后浏览器看一下能否正常访问到:
嗯,妥妥的,记得将get还是还原成post。
接着简单瞅一下数据格式,其父子的关系是这么来搭建的:
分类数据获取:
接下来回到Flutter端,咱们简单来调用一下该分类接口,确保数据都能正常获取到:
下面来运行一下:
嗯,木问题,关于分类模块实现稍麻烦一点,待之后再慢慢完善。