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

相关文章

Java实战之利用POI生成Excel图表

《Java实战之利用POI生成Excel图表》ApachePOI是Java生态中处理Office文档的核心工具,这篇文章主要为大家详细介绍了如何在Excel中创建折线图,柱状图,饼图等常见图表,需要的... 目录一、环境配置与依赖管理二、数据源准备与工作表构建三、图表生成核心步骤1. 折线图(Line Ch

Python自动化处理手机验证码

《Python自动化处理手机验证码》手机验证码是一种常见的身份验证手段,广泛应用于用户注册、登录、交易确认等场景,下面我们来看看如何使用Python自动化处理手机验证码吧... 目录一、获取手机验证码1.1 通过短信接收验证码1.2 使用第三方短信接收服务1.3 使用ADB读取手机短信1.4 通过API获取

MySQL zip安装包配置教程

《MySQLzip安装包配置教程》这篇文章详细介绍了如何使用zip安装包在Windows11上安装MySQL8.0,包括下载、解压、配置环境变量、初始化数据库、安装服务以及更改密码等步骤,感兴趣的朋... 目录mysql zip安装包配置教程1、下载zip安装包:2、安装2.1 解压zip包到安装目录2.2

Python自动化Office文档处理全攻略

《Python自动化Office文档处理全攻略》在日常办公中,处理Word、Excel和PDF等Office文档是再常见不过的任务,手动操作这些文档不仅耗时耗力,还容易出错,幸运的是,Python提供... 目录一、自动化处理Word文档1. 安装python-docx库2. 读取Word文档内容3. 修改

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2

Java使用Tesseract-OCR实战教程

《Java使用Tesseract-OCR实战教程》本文介绍了如何在Java中使用Tesseract-OCR进行文本提取,包括Tesseract-OCR的安装、中文训练库的配置、依赖库的引入以及具体的代... 目录Java使用Tesseract-OCRTesseract-OCR安装配置中文训练库引入依赖代码实

MySQL 中的服务器配置和状态详解(MySQL Server Configuration and Status)

《MySQL中的服务器配置和状态详解(MySQLServerConfigurationandStatus)》MySQL服务器配置和状态设置包括服务器选项、系统变量和状态变量三个方面,可以通过... 目录mysql 之服务器配置和状态1 MySQL 架构和性能优化1.1 服务器配置和状态1.1.1 服务器选项

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2