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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na