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 NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

一文教你Python如何快速精准抓取网页数据

《一文教你Python如何快速精准抓取网页数据》这篇文章主要为大家详细介绍了如何利用Python实现快速精准抓取网页数据,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录1. 准备工作2. 基础爬虫实现3. 高级功能扩展3.1 抓取文章详情3.2 保存数据到文件4. 完整示例

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

SpringBoot3.4配置校验新特性的用法详解

《SpringBoot3.4配置校验新特性的用法详解》SpringBoot3.4对配置校验支持进行了全面升级,这篇文章为大家详细介绍了一下它们的具体使用,文中的示例代码讲解详细,感兴趣的小伙伴可以参考... 目录基本用法示例定义配置类配置 application.yml注入使用嵌套对象与集合元素深度校验开发

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息