Flutter项目实战之女装商城------首页设计分析、数据准备、Dio请求处理、接口配置、请求首页数据...

本文主要是介绍Flutter项目实战之女装商城------首页设计分析、数据准备、Dio请求处理、接口配置、请求首页数据...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

首页设计分析:

在上一次https://www.cnblogs.com/webor2006/p/13661960.html已经用node搭建好了后台接口的环境了,接下来则来开始实现首页页面,它也是电商里面最最核心的页面,当然也是比较复杂的,在正式撸码之前先来对整个首页的功能有一个大体的印象,其实也就是分几大区域:

广告轮播图区:

商品分类区:

商品推荐及广告:

其中商品推荐栏是可以左右滑动的:

而下面的图片广告则是有两种展现形式:

火爆专区:

当然这块还需要有上拉加载下一页的常用功能,总的来看用一个Flutter来搭建这么复杂的列表页还是有些小难度的,当然也能学到很多知识的,期待实现的那一天~~

数据准备:

接一下则开始撸码了, 这里先来准备首页需要的后台API数据,先来回忆一下上次搭建的node后台环境的代码结构:

它的测试数据如下:

const express = require("express");
const router = express();
const config = require("./config");
const base_url = "http://" + config.IP + ":" + config.PORT + "/images/banner/";router.get("/",(req,res) => {var id = req.query.id;console.log("传递过来的参数测试:id = " + id);var data = {"code":"0","message":"success","data":[{"image": base_url + "1.jpeg",},{"image": base_url + "2.jpeg",},{"image": base_url + "3.jpeg",},]};res.send(data);});module.exports = router;

这里可以直接copy一个用来准备首页数据的准备:

然后改吧改吧,没啥可说的,整个数据如下:

const express = require("express");
const router = express();
const config = require("./config");
const base_url = "http://" + config.IP + ":" + config.PORT + "/images/";
const category_url = "http://" + config.IP + ":" + config.PORT + "/images/category/";router.get("/",(req,res) => {var data = {"code":"0","message":"success","data":{//banner轮播图片"slides": [{"image": base_url + "banner/1.jpeg","goodsId": "001"}, {"image": base_url + "banner/2.jpeg","goodsId": "002"},{"image": base_url + "banner/3.jpeg","goodsId": "003"},{"image": base_url + "banner/4.jpeg","goodsId": "004"},],//商品推荐上层"recommend": [{"name": "法国代购新款江疏影同款翻领修身中长裙春夏印花连衣裙","image": base_url + "goods/001/cover.jpg","presentPrice": 98.88,"goodsId": "001","oriPrice": 108.88}, {"name": "柔美而精致~高贵而优雅~圆领金银丝春季毛衣羊毛开衫女短款白外套","image": base_url + "goods/002/cover.jpg","presentPrice": 229.90,"goodsId": "002","oriPrice": 320.99}, {"name": "明星同款高端西服2019春装新款韩版英伦风短款格子小西装女外套潮","image": base_url + "goods/003/cover.jpg","presentPrice": 318.88,"goodsId": "003","oriPrice": 388.88}, {"name": "复古廓形机车进口绵羊皮衣真皮外套女E142","image": base_url + "goods/004/cover.jpg","presentPrice": 238.99,"goodsId": "004","oriPrice": 248.99}, {"name": "单排扣高腰牛仔裤女春夏薄款紧身弹力小脚裤显瘦百搭网红浅色长裤","image": base_url + "goods/005/cover.jpg","presentPrice": 588.99,"goodsId": "005","oriPrice": 888.88}, {"name": "MIUCO女装夏季重工星星烫钻圆领短袖宽松显瘦百搭T恤上衣k","image": base_url + "goods/006/cover.jpg","presentPrice": 1028.88,"goodsId": "006","oriPrice": 1888.88}, {"name": "春夏一步裙包臀裙开叉弹力修身显瘦短裙黑色高腰职业半身裙","image": base_url + "goods/007/cover.jpg","presentPrice": 2388.66,"goodsId": "007","oriPrice": 2888.88}, {"name": "夏季新款短袖圆领紧身小黑超短裙开叉包臀性感连衣裙夜店女装","image": base_url + "goods/008/cover.jpg","presentPrice": 666.88,"goodsId": "008","oriPrice": 888.88}, ],//商品中间广告"floor1Pic": {"PICTURE_ADDRESS": base_url + "advert/ad02.png","TO_PLACE": "4"},//商品推荐底部"floor1": [{"image": base_url + "floor/1.png","goodsId": "001"}, {"image": base_url + "floor/2.png","goodsId": "002"}, {"image": base_url + "floor/3.png","goodsId": "003"}, {"image": base_url + "floor/4.png","goodsId": "004"}, {"image": base_url + "floor/5.png","goodsId": "005"}],"category": [ {"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;

关于具体的数据咋用等到时开发到了再来细看,然后再到app.js中配置一下接口对应的路由路径,如下:

const express = require("express");
const path = require("path");
const app = express();app.use(express.static(path.resolve(__dirname, "public")));app.use(function(req, res, next) {const proxy = req.query.proxy;if(proxy) {req.header.cookie = req.header.cookie + `__proxy__${proxy}`;}next();
});//获取数据 路由到不同的数据接口
app.use("/getTestData",require("./router/test"));
app.use("/getHomePageContent",require("./router/home_page_content"));const port = process.env.PORT || 3000;
app.listen(port, ()=>{console.log(`server running @http://localhost:${port}`);
})module.exports = app;

其中有个命名的约定:

此时咱们重启一下node服务,访问一下看是否api一切ok?

嗯,妥妥的~~

Dio请求处理:

这是干嘛的?

 先来上官方https://pub.dev/packages/dio了解一下这个库:

它的简单用法看一下:

 

就是一个网络开源库~~

使用它:

先到Flutter项目中增加它的依赖,这里有个小技巧,在添加依赖时不需要知道具体版本,先这样添加:

然后此时到它的.lock文件中就可以看到具体版本了:

 

封装网络请求:

接下来用它来封装一下网络请求,这里封装到这个文件:

该请求肯定是异步的,所以得用async..await:

Future request(url, {formData}) async {try {//TODO} catch (e) {//TODO}
}

其中有个Dart的小语法提示一下:

接下来则用Dio来实现一下:

import 'dart:io';import 'package:dio/dio.dart';Future request(url, {formData}) async {try {Response response;Dio dio = Dio();dio.options.contentType = Headers.formUrlEncodedContentType;if (formData == null) {response = await dio.post(servicePath[url]);} else {response = await dio.post(servicePath[url], data: formData);}if (response.statusCode == 200) {return response;} else {throw Exception('后端接口异常,请检查测试代码和服务器运行情况...');}} catch (e) {return print('error:::${e}');}
}

其中这块需要配置一下:

接口配置:

为啥在网络请求封装逻辑中要接口配置一下而非直接使用url,统一管理,另外也方便调用,具体如下:

const base_url = 'http://192.168.0.105:3000/';
const servicePath = {'homePageContext': base_url + 'getHomePageContent', //首页数据'getHotGoods': base_url + 'getHotGoods', //火爆专区'getCategory': base_url + 'getCategory', //商品类别信息'getCategoryGoods': base_url + 'getCategoryGoods', //商品分类别的商品列表'getGoodDetail': base_url + 'getGoodDetail', //商品详细信息
};

这里先提前定义未来用到后个URL配置,另外再回到咱们的网络封装文件中导一下包既可:

 

请求首页数据:

接下来则来发起首页数据的请求,先来回忆一下目前首页的代码:

这里需要替换成脚手架的Widget,而不能用Center了:

接下来则可以发起数据请求了,此时需要用到FutureBuilder了,之前已经用过了https://www.cnblogs.com/webor2006/p/13336013.html,它可以防止重绘,而使用也很简单,如该类名一下,先定义future,然后再定义builder,对于future的定义当然就是直接请求网络了喽,如下:

而builder很显然就可以获取请求数据的结果然后做相应的结果处理了:

下面运行看一下能否成功请求到首页的数据,发现报错了:

原因是对于FutureBuilder是需要返回一个Widget的,所以修改一下代码:

再来运行:

I/flutter (26841): error:::DioError [DioErrorType.RESPONSE]: Http status error [404]

抛异常了,咱会404呢,咱们打印一下请求的url看有木有问题:

运行:

2020-11-13 09:18:38.683 26841-26892/com.fluttershop I/flutter: request url:http://192.168.31.188:3000/getHomePageContent
2020-11-13 09:18:38.932 26841-26892/com.fluttershop I/flutter: error:::DioError [DioErrorType.RESPONSE]: Http status error [404]

【提示】:貌似看着ip变了,是因为此时已经从家里换到公司了,所以ip变了~~

貌似这个地址没啥问题呢,用浏览器访问一下:

其实换成get就成了:

再运行:

这是为啥呢?咱们就是要post,其实是node接口写成get了,如下:

将其改成post:

重新一下node,还是将flutter中的http_service还原成post:

此时就不能用get请求了:

再运行就一切正常啦~~

数据解析:

有了请求数据之后,接下来则需要将json解析成实体,供界面的渲染用,比较简单,直接贴出来了:

保持状态处理:

数据解析一切就绪之后,接下来照理就来处理界面的渲染了,但是!!!这个先暂缓一下,因为有个知识点需要道出来,就是标题所示,它说的是啥意思呢?咱们目前底部的Tab使用的是BottomNavigationBar控件,而对于使用过它的可能知道它会有一个切换重绘的问题,具体可以参考网上这篇文章https://www.jianshu.com/p/4930fde7efa2,但是!!!咱们这其实是不会重绘的,因为在构建bottom的时候使用的是它:

 

不信的话可以在首页的这个生命周期上打印一个日志:

只要在每次切换如果都回调了此方法证明肯定重绘了,很明显结果是只会绘一次,在tab切出去再切回来时是不会再次执行这个生命周期的,那没问题提出来干嘛?是因为有可能在实现Tab效果时会这样用:

上面截图的这代码是之前https://www.cnblogs.com/webor2006/p/12879031.html学习Flutter做的一个小项目, 这种情况下就会导致每次切换都会执行initState()造成页面重绘了,效果如下:

而解决办法两个,如博主所说:

回到咱们这个首页,如果用第二种解决方法则就是这样写的:

另外还有一个Flutter语法,就是啥是Mixin呢?这里可以参考https://www.cnblogs.com/webor2006/p/11981709.html,

EasyRefresh刷新处理:

了解:

对于首页需要有上拉加载与下拉刷新的功能,如果是Android里面实现有经典的PullToRefresh来实现,类似的这里可以用EasyRefresh,先来了解一下它https://pub.dev/packages/flutter_easyrefresh:

使用:

先添加依赖:

然后来使用到界面上,这里参考官方的DEMO来https://pub.dev/packages/flutter_easyrefresh/example:这里先弄上拉分页的,也就是footer,这个构建细节就不一一解释了:

import 'dart:convert';import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:fluttershop/config/color.dart';
import 'package:fluttershop/config/string.dart';
import 'package:fluttershop/service/http_service.dart';class HomePage extends StatefulWidget {@override_HomePageState createState() => _HomePageState();
}class _HomePageState extends State<HomePage>with AutomaticKeepAliveClientMixin {//这里只是做一个演示,其实是不用加这个的,因为咱们的BottomNavigationBar是由IndexedStack构建的,具体参考index_page.dart文件@overridebool get wantKeepAlive => true;GlobalKey<ClassicalFooterWidgetState> _footerKey =GlobalKey<ClassicalFooterWidgetState>();@overridevoid initState() {super.initState();print('home_page.initState()');}@overrideWidget build(BuildContext context) {print('home_page.build()');return Scaffold(backgroundColor: Color.fromRGBO(244, 245, 245, 1.0),appBar: AppBar(title: Text(KString.homeTitle),),body: FutureBuilder(future: request('homePageContext', formData: null),builder: (context, snapshot) {if (snapshot.hasData) {var data = json.decode(snapshot.data.toString());print("gethomePageContext data:${data}");var dataList = data['data'];List<Map> swiperDataList =(dataList['slides'] as List).cast(); //轮播图List<Map> navigatorList =(dataList['category'] as List).cast(); //分类List<Map> recommendList =(dataList['recommend'] as List).cast(); //商品推荐List<Map> floor1 =(dataList['floor1'] as List).cast(); //底部商品推荐Map fp1 = dataList['floor1Pic']; //广告return EasyRefresh.custom(enableControlFinishRefresh: false,enableControlFinishLoad: true,footer: ClassicalFooter(key: _footerKey,bgColor: Colors.white,textColor: KColor.refreshTextColor,infoColor: KColor.refreshTextColor,noMoreText: '',//加载中...loadingText: KString.loading,loadReadyText: KString.loadReadyText,),slivers: [SliverList(delegate: SliverChildBuilderDelegate((context, index) {return Container(width: 60.0,height: 60.0,child: Center(child: Text('$index'),),color: index % 2 == 0? Colors.grey[300]: Colors.transparent,);},childCount: 5,),),],onLoad: () async {print('开始加载更多');//TODO 执行下一页的请求},);} else {return Center(child: Text('加载中...'),);}}),);}
}

其中用到了一个Key:

这是干嘛用的呢?关于这块网上搜了搜https://blog.csdn.net/qq_32760901/article/details/91798507,其目的是可以在外面的Widget来调用它泛型中的Widget中的东东:

 

啥意思,回到咱们定义的:

而拿到这个State有啥有呢,再点一下:

但是我们底部Widget叫它呀:

它俩其实是一个东东,看一眼ClassicalFooter的源码:

 

然后这程序还涉及到一个色值:

还有两个文本:

也就是在外部可以通过这个GlobalKey来操纵底部加载Widget的啦,大概就是做这个用的,接下来运行看一下效果:

但是标题不对,先把外层的Flutter女装商城给去掉:

运行结果:

接下来则需要实现列表项的内容了,累了,下次继续~~

这篇关于Flutter项目实战之女装商城------首页设计分析、数据准备、Dio请求处理、接口配置、请求首页数据...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/771643

相关文章

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Oracle Expdp按条件导出指定表数据的方法实例

《OracleExpdp按条件导出指定表数据的方法实例》:本文主要介绍Oracle的expdp数据泵方式导出特定机构和时间范围的数据,并通过parfile文件进行条件限制和配置,文中通过代码介绍... 目录1.场景描述 2.方案分析3.实验验证 3.1 parfile文件3.2 expdp命令导出4.总结

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

javafx 如何将项目打包为 Windows 的可执行文件exe

《javafx如何将项目打包为Windows的可执行文件exe》文章介绍了三种将JavaFX项目打包为.exe文件的方法:方法1使用jpackage(适用于JDK14及以上版本),方法2使用La... 目录方法 1:使用 jpackage(适用于 JDK 14 及更高版本)方法 2:使用 Launch4j(

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一