列表页_使用Provide
控制子类
使用flutter_provide,初步实现点击列表页的大类,改变小类的效果。
继续上次的代码接着写:https://www.cnblogs.com/joe235/p/11633989.html
新写了一个右侧小类,暂时用假数据代替,代码如下:
//右侧小类类别 class RightCategoryNav extends StatefulWidget { _RightCategoryNavState createState() => _RightCategoryNavState(); } class _RightCategoryNavState extends State<RightCategoryNav> { List list = ['名酒','宝丰','北京二锅头','舍得','五粮液','茅台','散白']; @override Widget build(BuildContext context) { return Container( height: ScreenUtil().setHeight(80), width: ScreenUtil().setWidth(570), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1,color: Colors.black12) ) ), child:ListView.builder( scrollDirection: Axis.horizontal, itemCount: list.length, itemBuilder: (context,index){ return _rightInkWell(list[index]); }, ) ); } //子类 Widget _rightInkWell(String item){ return InkWell( onTap: (){}, child: Container( padding:EdgeInsets.fromLTRB(5.0,10.0,5.0,10.0), child: Text( item, style: TextStyle(fontSize:ScreenUtil().setSp(28)), ), ), ); }
添加到界面中
在category_page.dart
的CategoryPage
类的build方法里,加入右侧子类导航区域.
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('商品分类'), ), body: Container( child: Row( children: <Widget>[ LeftCategoryNav(), Column( children: <Widget>[ RightCategoryNav() ], ) ], ), ), ); }
我们就应该能看到效果,但是现在数据都是写死的,还没有实现状态的控制,下一步就是用provide来控制,实现二级分类和一级分类的交互效果。
编写二级分类的Provide文件
我们先设置一个子类的provide
,在lib/provide/
文件夹下,新建一个child_category.dart
文件,这个文件就是控制子类的状态管理文件。代码如下:
import 'package:flutter/material.dart'; import '../model/category.dart'; //ChangeNotifier的混入是不用管理听众 class ChildCategory with ChangeNotifier{ List<BxMallSubDto> childCategoryList = []; getChildCategory(List list){ childCategoryList=list; notifyListeners(); } }
引入了category.dart
的model文件,这样就可以很好的对象化,先声明了一个泛型的List变量childCategoryList
。然后做了个方法,进行赋值。(注意这种形式也是在工作中最常用的一种形式。)
main里进行引入
import './provide/child_category.dart'; void main() { var childCategory= ChildCategory(); providers ..provide(Provider<Counter>.value(counter)) //依赖 ..provide(Provider<ChildCategory>.value(childCategory)); 。。。 }
修改二级分类状态
有了Provide
类之后,就可以修改二级分类了,这时候先引入child_category.dart
文件和provide.dart,再修改左侧大类的
InkWell
中的onTap方法。
import 'package:provide/provide.dart'; import '../provide/child_category.dart';
onTap: () { var childList = list[index].bxMallSubDto; Provide.value<ChildCategory>(context).getChildCategory(childList); },
编写好后,其实状态已经改变了,那接下来就可以设置二级分类的修改状态了。
二级分类展现
修改右侧二级分类的展示,这个先改变子项的接受数据。把原来的item,改成item.mallSubName
,修改后的代码如下:
Widget _rightInkWell(BxMallSubDto item){ return InkWell( onTap: (){}, child: Container( padding:EdgeInsets.fromLTRB(5.0,10.0,5.0,10.0), child: Text( item.mallSubName, style: TextStyle(fontSize:ScreenUtil().setSp(28)), ), ), ); }
子项修改好后哦,再修改build里的Container
,我们需要加入一个Provide
组件,注意这里使用了泛型。
@override Widget build(BuildContext context) { return Container( width: ScreenUtil().setWidth(570), height: ScreenUtil().setHeight(80), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1,color: Colors.black12) ) ), child: Provide<ChildCategory>( builder: (context,child,childCategory){ return ListView.builder( //ListView要设置宽高,纵向的可以只设置高,横向的宽高都要设置 scrollDirection: Axis.horizontal, //横向 itemCount: childCategory.childCategoryList.length, itemBuilder: (content,index){ return _rightInkWell(childCategory.childCategoryList[index]); }, ); }, ), ); }
修改步骤:
- 在
Container Widget
外层加入一个Provie widget
。 - 修改
ListView Widget
的itemCount
选项为childCategory.childCategoryList.length
。 - 修改
itemBuilder
里的传值选项为return _rightInkWell(childCategory.childCategoryList[index]);
交互效果的设置
现在二级分类已经能跟随我们的点击发生变化了,但是大类还没有高亮显示,所以要作一下交互效果,这种交互效果跟其它类或者页面没什么关系,所以我们还是使用最简单的setState
来实现了。 这个变化主要在_leftInkWell
里,所以操作也基本在这个里边。
- 先声明一个变量,用于控制是否高亮显示
bool isClick = false;
- 让
_leftInkWell
接收一个变量,变量是ListView传递过来的Widget _leftInkWel(int index)
- 声明一个全局的变量
var listIndex = 0; //索引
- 对比index和listIndex
isClick=(index==listIndex)?true:false;
. - 修改为动态显示背景颜色
color: isClick?Colors.black26:Colors.white,
在进入页面后的_getCategory
里在等到大类数据后,把第一个小类的数据同时进行状态修改。
代码如下:
//得到后台大类数据 void _getCategory() async{ await request('post','getCategory').then((val){ var data = json.decode(val.toString()); //print(data); CategoryModel category = CategoryModel.fromJson(data); setState(() { list = category.data; }); Provide.value<ChildCategory>(context).getChildCategory( list[0].bxMallSubDto); //print(list[0].bxMallSubDto); //list.data.forEach((item)=>print(item.mallCategoryName)); }); }
反白显示颜色过重,使用RGBO颜色
这个直接使用Flutter里的RGBO模式就可以了,当然你也完全可以使用Colors.black12
color: isClick ? Color.fromRGBO(236, 238, 239, 1.0) : Colors.white,
添加子类“全部”按钮
原型上在二级分类上是有“全部”字样的,但我们作的这里并没有。其实加上这个全部也非常简单,只要我们在状态管理,改变状态的方法getChildCategory
里,现加入一个全部的BxMallSubDto
对象就可以了。
代码部分就是修改provide/child_Category.dart
的getchildCategory
方法。思路是声明一个all对象,然后进行赋值,复制后组成List赋给childCategoryList
。然后把list添加到childCategoryList
里。
import 'package:flutter/material.dart'; import '../model/category.dart'; //ChangeNotifier的混入是不用管理听众 class ChildCategory with ChangeNotifier{ List<BxMallSubDto> childCategoryList = []; getChildCategory(List<BxMallSubDto> list){ BxMallSubDto all= BxMallSubDto(); all.mallSubId='00'; all.mallCategoryId='00'; all.mallSubName = '全部'; all.comments = 'null'; childCategoryList=[all]; childCategoryList.addAll(list); notifyListeners(); } }
全部代码如下:
import 'package:flutter/material.dart'; import '../service/service_method.dart'; import 'dart:convert'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../model/category.dart'; import 'package:provide/provide.dart'; import '../provide/child_category.dart'; class CategoryPage extends StatefulWidget { _CategoryPageState createState() => _CategoryPageState(); } class _CategoryPageState extends State<CategoryPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('商品分类')), body: Container( child: Row( children: <Widget>[ LeftCategoryNav(), Column( children: <Widget>[ RightCategoryNav(), ], ) ] ) ) ); } } //左侧大类导航 class LeftCategoryNav extends StatefulWidget { _LeftCategoryNavState createState() => _LeftCategoryNavState(); } class _LeftCategoryNavState extends State<LeftCategoryNav> { List list = []; var listIndex = 0; //索引 @override void initState() { _getCategory(); //请求接口的数据 super.initState(); } @override Widget build(BuildContext context) { return Container( width: ScreenUtil().setWidth(180), decoration: BoxDecoration( border: Border( right: BorderSide(width: 1,color: Colors.black12) ) ), child: ListView.builder( itemCount: list.length, itemBuilder:(context,index){ return _leftInkWell(index); } ), ); } Widget _leftInkWell(int index){ bool isClick = false; //是否点击了 isClick = (index==listIndex) ? true : false; //判断当前点击的索引是否为全局变量的索引 return InkWell( onTap: (){ setState(() { listIndex = index; //每次点击了大类就把索引赋值给listIndex }); var childList = list[index].bxMallSubDto; Provide.value<ChildCategory>(context).getChildCategory(childList); }, child: Container( height: ScreenUtil().setHeight(100), padding: EdgeInsets.only(left: 10,top: 13), decoration: BoxDecoration( color: isClick ? Color.fromRGBO(236,236,236,1.0) : Colors.white, border: Border( bottom: BorderSide(width: 1,color: Colors.black12) ) ), child: Text(list[index].mallCategoryName,style: TextStyle(fontSize: ScreenUtil().setSp(28))), ), ); } //得到后台大类数据 void _getCategory()async{ await request('post','getCategory').then((val){ var data = json.decode(val.toString()); //print(data); CategoryModel category = CategoryModel.fromJson(data); setState(() { list = category.data; }); Provide.value<ChildCategory>(context).getChildCategory( list[0].bxMallSubDto); print(list[0].bxMallSubDto); list[0].bxMallSubDto.forEach((item) => print(item.mallSubName)); //list.data.forEach((item)=>print(item.mallCategoryName)); }); } } //右侧二级分类 class RightCategoryNav extends StatefulWidget { _RightCategoryNavState createState() => _RightCategoryNavState(); } class _RightCategoryNavState extends State<RightCategoryNav> { //List list = ['名酒','宝丰','北京二锅头','舍得','五粮液','茅台','散白']; @override Widget build(BuildContext context) { return Container( width: ScreenUtil().setWidth(570), height: ScreenUtil().setHeight(80), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1,color: Colors.black12) ) ), child: Provide<ChildCategory>( builder: (context,child,childCategory){ return ListView.builder( //ListView要设置宽高,纵向的可以只设置高,横向的宽高都要设置 scrollDirection: Axis.horizontal, //横向 itemCount: childCategory.childCategoryList.length, itemBuilder: (content,index){ return _rightInkWell(childCategory.childCategoryList[index]); }, ); }, ), ); } //子类 Widget _rightInkWell(BxMallSubDto item){ return InkWell( onTap: (){}, child: Container( padding: EdgeInsets.fromLTRB(5, 10, 5, 10), child: Text( item.mallSubName, style:TextStyle(fontSize:ScreenUtil().setSp(28.0)) ), ), ); } }
现在点击左侧大类,右侧顶部的二级分类会随之变化。
列表页_商品列表接口调试
下面先调通商品分类页里的商品列表接口,这个接口包括上拉加载、大类切换和小类切换的互动。
配置URL路径
对于后台接口的调试,应该有所了解了,第一步就是配置后台接口的路径到统一的配置文件中,这样方便以后的维护。
打开lib\config\service_ulr.dart
文件,再最下面加上商品分类的商品列表接口路径,现在的配置文件,代码如下:
const servicePath={ 'homePageContext': serviceUrl+'wxmini/homePageContent', // 商家首页信息 'homePageBelowConten': serviceUrl+'wxmini/homePageBelowConten', //商城首页热卖商品拉取 'getCategory': serviceUrl+'wxmini/getCategory', //商品类别信息 'getMallGoods': serviceUrl+'wxmini/getMallGoods', //商品分类的商品列表 };
测试大类商品列表接口
在lib\pages\category_page.dart
文件里,新建一个CategoryGoodsList
类,这个类我们也将用状态管理进行管理,所以这个类并没有什么其它的耦合,不接收任何参数。
//右侧商品列表,可以上拉加载 class CategoryGoodsList extends StatefulWidget { @override _CategoryGoodsListState createState() => _CategoryGoodsListState(); } class _CategoryGoodsListState extends State<CategoryGoodsList> { @override Widget build(BuildContext context) { return Container( child: Text('商品列表'), ); } }
有了类以后,我们写一个内部获取后台数据的方法_getGoodList
。先声明了一个变量data
,用于放入传递的值。然后再把参数传递过去。具体代码如下:
//调试接口 void _getGoodList() async{ var data = { 'categoryId': '4', 'categorySubId': '', 'page': 1, }; await request('post', 'getMallGoods', formData: data).then((val){ var data = json.decode(val.toString()); print('分类列表===========》${data}'); }); }
然后我们在initState
中调用一下:
@override void initState() { _getGoodList(); super.initState(); }
然后把CategoryGoodsList加入到页面布局。
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('商品分类')), body: Container( child: Row( children: <Widget>[ LeftCategoryNav(), Column( children: <Widget>[ RightCategoryNav(), CategoryGoodsList(), ], ) ] ) ) ); }
如果一切正常应该可以在终端中看到输出的结果,有正常的列表结果输出,说明一切正常。
列表页_商品列表数据模型的建立
先用快速的方法,生成我们商品分类的商品列表数据模型,然后根据数据模型修改一下,读取后台的方法。
商品列表页数据模型
这里还是使用快速生成的方法,利用https://javiercbk.github.io/json_to_dart/
,直接生成。
给出一段JSON数据,当然你页可以自己抓取,这非常的容易。
{"code":"0","message":"success","data":[{"image":"http://images.baixingliangfan.cn/compressedPic/20190116145309_40.jpg","oriPrice":2.50,"presentPrice":1.80,"goodsName":"哈尔滨冰爽啤酒330ml","goodsId":"3194330cf25f43c3934dbb8c2a964ade"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190115185215_1051.jpg","oriPrice":2.00,"presentPrice":1.80,"goodsName":"燕京啤酒8°330ml","goodsId":"522a3511f4c545ab9547db074bb51579"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190121102419_9362.jpg","oriPrice":1.98,"presentPrice":1.80,"goodsName":"崂山清爽8°330ml","goodsId":"bbdbd5028cc849c2998ff84fb55cb934"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712181330_9746.jpg","oriPrice":2.50,"presentPrice":1.90,"goodsName":"雪花啤酒8°清爽330ml","goodsId":"87013c4315e54927a97e51d0645ece76"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712180233_4501.jpg","oriPrice":2.50,"presentPrice":2.20,"goodsName":"崂山啤酒8°330ml","goodsId":"86388a0ee7bd4a9dbe79f4a38c8acc89"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190116164250_1839.jpg","oriPrice":2.50,"presentPrice":2.30,"goodsName":"哈尔滨小麦王10°330ml","goodsId":"d31a5a337d43433385b17fe83ce2676a"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712181139_2653.jpg","oriPrice":2.70,"presentPrice":2.50,"goodsName":"三得利清爽啤酒10°330ml","goodsId":"74a1fb6adc1f458bb6e0788c4859bf54"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190121162731_3928.jpg","oriPrice":2.75,"presentPrice":2.50,"goodsName":"三得利啤酒7.5度超纯啤酒330ml","goodsId":"d52fa8ba9a5f40e6955be9e28a764f34"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712180452_721.jpg","oriPrice":4.50,"presentPrice":3.70,"goodsName":"青岛啤酒11°330ml","goodsId":"a42c0585015540efa7e9642ec1183940"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190121170407_7423.jpg","oriPrice":4.40,"presentPrice":4.00,"goodsName":"三得利清爽啤酒500ml 10.0°","goodsId":"94ec3df73f4446b5a5f0d80a8e51eb9d"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712181427_6101.jpg","oriPrice":4.50,"presentPrice":4.00,"goodsName":"雪花勇闯天涯啤酒8°330ml","goodsId":"d80462faab814ac6a7124cec3b868cf7"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180717151537_3425.jpg","oriPrice":4.90,"presentPrice":4.10,"goodsName":"百威啤酒听装9.7°330ml","goodsId":"91a849140de24546b0de9e23d85399a3"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190121101926_2942.jpg","oriPrice":4.95,"presentPrice":4.50,"goodsName":"崂山啤酒8°500ml","goodsId":"3758bbd933b145f2a9c472bf76c4920c"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712175422_518.jpg","oriPrice":5.00,"presentPrice":4.50,"goodsName":"百威3.6%大瓶9.7°P460ml","goodsId":"dc32954b66814f40977be0255cfdacca"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180717151454_4834.jpg","oriPrice":5.00,"presentPrice":4.50,"goodsName":"青岛啤酒大听装500ml","goodsId":"fc85510c3af7428dbf1cb0c1bcb43711"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712181007_4229.jpg","oriPrice":5.50,"presentPrice":5.00,"goodsName":"三得利金纯生啤酒580ml 9°","goodsId":"14bd89f066ca4949af5e4d5a1d2afaf8"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190121100752_4292.jpg","oriPrice":6.60,"presentPrice":6.00,"goodsName":"哈尔滨啤酒冰纯白啤(小麦啤酒)500ml","goodsId":"89bccd56a8e9465692ccc469cd4b442e"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712175656_777.jpg","oriPrice":7.20,"presentPrice":6.60,"goodsName":"百威啤酒500ml","goodsId":"3a94dea560ef46008dad7409d592775d"},{"image":"http://images.baixingliangfan.cn/compressedPic/20180712180754_2838.jpg","oriPrice":7.78,"presentPrice":7.00,"goodsName":"青岛啤酒皮尔森10.5°330ml","goodsId":"97adb29137fb47689146a397e5351926"},{"image":"http://images.baixingliangfan.cn/compressedPic/20190116164149_2165.jpg","oriPrice":7.78,"presentPrice":7.00,"goodsName":"青岛全麦白啤11°500ml","goodsId":"f78826d3eb0546f6a2e58893d4a41b43"}]}
得到快速生成的Model类,在model文件夹下,新建一个文件categoryGoodsList.dart
,这时候我们需要修改一下代码,防止产生冲突。修改完成的代码如下:
class CategoryGoodsListModel { String code; String message; List<CategoryListData> data; CategoryGoodsListModel({this.code, this.message, this.data}); CategoryGoodsListModel.fromJson(Map<String, dynamic> json) { code = json['code']; message = json['message']; if (json['data'] != null) { data = new List<CategoryListData>(); json['data'].forEach((v) { data.add(new CategoryListData.fromJson(v)); }); } } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['code'] = this.code; data['message'] = this.message; if (this.data != null) { data['data'] = this.data.map((v) => v.toJson()).toList(); } return data; } } class CategoryListData { String image; double oriPrice; double presentPrice; String goodsName; String goodsId; CategoryListData( {this.image, this.oriPrice, this.presentPrice, this.goodsName, this.goodsId}); CategoryListData.fromJson(Map<String, dynamic> json) { image = json['image']; oriPrice = json['oriPrice']; presentPrice = json['presentPrice']; goodsName = json['goodsName']; goodsId = json['goodsId']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['image'] = this.image; data['oriPrice'] = this.oriPrice; data['presentPrice'] = this.presentPrice; data['goodsName'] = this.goodsName; data['goodsId'] = this.goodsId; return data; } }
然后在category_page.dart里引入:
import '../model/categoryGoodsList.dart';
修改_getGoodList
方法
主要是让从后台得到的数据,可以使用数据模型。
void _getGoodList()async { var data={ 'categoryId':'4', 'categorySubId':"", 'page':1 }; await request('getMallGoods',formData:data ).then((val){ var data = json.decode(val.toString()); CategoryGoodsListModel goodsList= CategoryGoodsListModel.fromJson(data); setState(() { list= goodsList.data; }); print('>>>>>>>>>>>>>>>>>>>:${list[0].goodsName}'); }); }
写完后测试一下,如果可以在控制台输出,想要的结果,说明我们的Model类建立完成了。
列表页_商品列表UI布局
商品图片方法编写
我们把这个列表拆分成三个内部方法,分别是商品图片、商品名称和商品价格。这样拆分可以减少耦合和维护难度。
先来制作图片的内部方法,代码如下:
//图片 Widget _goodsImage(index){ return Container( width: ScreenUtil().setWidth(200), child: Image.network(list[index].image), ); }
商品名称方法编写
这个我们直接返回一个Container
,然后在里边子组件里放一个Text
,需要对Text进行一些样式设置,防止越界。
//商品名称 Widget _goodsName(index){ return Container( padding: EdgeInsets.all(5.0), width: ScreenUtil().setWidth(370), child: Text( list[index].goodsName, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: ScreenUtil().setSp(28)), ), ); }
商品价格方法编写
商品价格我们在Container
里放置一个Row
,这样就能实现同一排显示,具体可以查看代码。
//商品价格 Widget _goodsPrice(index){ return Container( margin: EdgeInsets.only(top:20.0), width: ScreenUtil().setWidth(370), child:Row( children: <Widget>[ Text( '价格:¥${list[index].presentPrice}', style: TextStyle(color:Colors.pink,fontSize:ScreenUtil().setSp(30)), ), Text( '¥${list[index].oriPrice}', style: TextStyle( color: Colors.black26, decoration: TextDecoration.lineThrough ), ) ] ) ); }
把方法进行组合
把一个列表项分成了好几个方法,现在需要把每一个方法进行组合。具体代码如下,我会在视频中进行详细讲解
Widget _ListWidget(int index){ return InkWell( onTap: (){}, child: Container( padding: EdgeInsets.only(top: 5.0,bottom: 5.0), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1.0,color: Colors.black12) ) ), child: Row( children: <Widget>[ _goodsImage(index) , Column( children: <Widget>[ _goodsName(index), _goodsPrice(index) ], ) ], ), ) ); }
ListView的构建
组合完成后,在build方法里,使用ListView来显示表单,记得要正确设置宽和高。
@override Widget build(BuildContext context) { return Container( width: ScreenUtil().setWidth(570) , height: ScreenUtil().setHeight(1000), child: ListView.builder( itemCount: list.length, itemBuilder: (context,index){ return _ListWidget(index); }, ) ); }
构建好后,就可以进行测试了。
列表页_商品列表交互效果制作
现在页面布局已经基本完成,接下来就要作商品分类页的各种交互效果了,
制作商品列表的Provide
制作Provide是有一个小技巧的,就是页面什么元素需要改变,你就制作什么的provide
类,比如现在我们要点击大类,改变商品列表,实质改变的就是List
的值,那只制作商品列表List的Provide
就可以了。
在lib/proive/
文件夹下,新建一个category_goods_list.dart
文件。
import 'package:flutter/material.dart'; import '../model/categoryGoodsList.dart'; class CategoryGoodsListProvide with ChangeNotifier{ List<CategoryListData> goodsList = []; //点击大类时更换商品列表 getGoodsList(List<CategoryListData> list){ goodsList=list; notifyListeners(); } }
先引入了model中的categoryGoodsList.dart
文件,管理的状态就是goodsList
变量,我们通关过一个方法getGoodsList
来改变状态。这样一个Provide
类就制作完成了。
将状态放入顶层
当Provide
编程完成以后,需要把写好的状态管理放到main.dart
中,我司叫它为放入顶层,就是全部页面想用这个状态都可以获得。代码如下:
void main(){ var childCategory= ChildCategory(); var categoryGoodsListProvide= CategoryGoodsListProvide(); var counter =Counter(); var providers =Providers(); providers ..provide(Provider<ChildCategory>.value(childCategory)) ..provide(Provider<CategoryGoodsListProvide>.value(categoryGoodsListProvide)) ..provide(Provider<Counter>.value(counter)); runApp(ProviderNode(child:MyApp(),providers:providers)); }
声明一个categoryGoodsListProvide
变量,然后放入顶层就可以了。
修改category_page.dart页面
这个页面需要伤筋动骨,进行彻底修改结构,步骤较多,请按步骤一步步完成。
1.引入provide文件
在lib/pages/category_page.dart
文件最上面引入刚写的provide
.
import '../provide/category_goods_list.dart';
2.修改_getGoodsList方法
上节课为了布局,把得到商品列表数据的方法,放到了商品列表类里。现在需要把这个方法放到我们的CategoryPage类里,作为一个内部方法,因为我们要在点击大类时,调用后台接口和更新状态。
//得到商品列表数据 void _getGoodList({String categoryId}) async{ var data = { 'categoryId': categoryId == null ? '4' : categoryId, 'categorySubId': '', 'page': 1, }; await request('post', 'getMallGoods', formData: data).then((val){ var data = json.decode(val.toString()); CategoryGoodsListModel goodsList = CategoryGoodsListModel.fromJson(data); Provide.value<CategoryGoodsListProvide>(context).getGoodsList(goodsList.data); }); }
首先方法要增加一个可选参数,就是大类ID,如果没有大类ID,我们默认为4,有了参数后到后台获得数据,获得后使用Provide
改变状态。
3.使用_getGoodList方法
修改完这个方法后,可以在每次点击大类的时候进行调用。代码如下:
onTap: (){ setState(() { listIndex = index; //每次点击了大类就把索引赋值给listIndex }); var childList = list[index].bxMallSubDto; //当前大类的子类列表 var categoryId = list[index].mallCategoryId; Provide.value<ChildCategory>(context).getChildCategory(childList); _getGoodList(categoryId:categoryId); },
这段代码,先声明了一个类别IDcategoryId
,然后调用了_getGoodList()
方法,调用方法时要传递categoryId参数。
4.修改商品列表代码
这个部分的代码修改要多一点,要把原来的setState模式,换成provide模式,所以很多地方都有所不同,但是我们的布局代码时不需要改的。
先去掉list ,然后用Provide widget
来监听变化,修改类里的子方法,多接收一个List参数,命名为newList
,每个子方法都要加入,这里提醒不要使用state,否则会报错。
修改后的代码如下:
//右侧商品列表 ,可以上拉加载 class CategoryGoodsList extends StatefulWidget { CategoryGoodsList({Key key}) : super(key: key); _CategoryGoodsListState createState() => _CategoryGoodsListState(); } class _CategoryGoodsListState extends State<CategoryGoodsList> { //List list = []; @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Provide<CategoryGoodsListProvide>( builder:(context, child, data){ return Container( width: ScreenUtil().setWidth(570), height: ScreenUtil().setHeight(980), child: ListView.builder( itemCount: data.goodsList.length, itemBuilder: (context,index){ return _listGoods(data.goodsList,index); }, ) ); }, ); } //图片 Widget _goodsImage(List newList,index){ return Container( width: ScreenUtil().setWidth(200), child: Image.network(newList[index].image), ); } //商品名称 Widget _goodsName(List newList,index){ return Container( width: ScreenUtil().setWidth(370), padding: EdgeInsets.all(5), child: Text( newList[index].goodsName, maxLines:2, overflow:TextOverflow.ellipsis, style: TextStyle(fontSize:ScreenUtil().setSp(28)), ), ); } //商品价格 Widget _goodsPrice(List newList,index){ return Container( margin: EdgeInsets.only(top: 20), width: ScreenUtil().setWidth(370), child: Row( children: <Widget>[ Text( '价格:${newList[index].presentPrice} ', style: TextStyle(color: Colors.blueGrey,fontSize: ScreenUtil().setSp(28)), ), Text( '价格:${newList[index].oriPrice}', style: TextStyle(color: Colors.black26,decoration: TextDecoration.lineThrough), ), ], ), ); } //商品组合 Widget _listGoods(List newList,index){ return InkWell( onTap: (){}, child: Container( padding: EdgeInsets.only(top:5,bottom: 5), decoration: BoxDecoration( color: Colors.white, border: Border( bottom: BorderSide(width: 1,color: Colors.black12) ) ), child: Row( children: <Widget>[ _goodsImage(newList,index), Column( children: <Widget>[ _goodsName(newList,index), _goodsPrice(newList,index), ], ) ], ), ), ); } }
然后使用_getGoodList方法,在_leftInkWell的onTap里修改:
var categoryId = list[index].mallCategoryId; _getGoodList(categoryId:categoryId);
还要在LeftCategoryNav的initState加上_getGoodList();
class _LeftCategoryNavState extends State<LeftCategoryNav> { List list = []; var listIndex = 0; //索引 @override void initState() { _getCategory(); //请求接口的数据 -----------添加部分start------------ _getGoodList(); -----------添加部分end---------------- super.initState(); }
总结:这节课算是Provide的高级应用了,如果这个状态管理小伙伴都很熟练了,至少Flutter的状态管理这个知识点是没有问题了。
列表页_小类高亮交互效果制作
Expanded Widget的使用
Expanded Widget 是让子Widget有伸缩能力的小部件,它继承自Flexible
,用法也差不多。那为什么要单独拿出来讲一下Expanded Widget那?我们在首页布局和列表页布局时都遇到了高度适配的问题,很多小伙伴出现了高度溢出的BUG,所以这节课开始前先解决一下这个问题。
修改 Category_page.dart
里的商品列表页面,不再约束高了,而是使用Expanded Widget
包裹外层,修改后的代码如下:
@override Widget build(BuildContext context) { return Provide<CategoryGoodsListProvide>( builder: (context,child,data){ return Expanded( child:Container( width: ScreenUtil().setWidth(570) , child:ListView.builder( itemCount: data.goodsList.length, itemBuilder: (context,index){ return _ListWidget(data.goodsList,index); }, ) , ) , ); }, ); }
小类高亮效果制作
由于高亮效果也受到大类的控制,不仅仅是子类别的控制,所以这个效果也要用到状态管理来制作。这个状态很简单,没必要单独写一个Provide
,所以直接使用以前的类就可以,我们直接在provide/child_category.dart
里修改。修改的代码为:
import 'package:flutter/material.dart'; import '../model/category.dart'; //ChangeNotifier的混入是不用管理听众 class ChildCategory with ChangeNotifier{ List<BxMallSubDto> childCategoryList = []; int childIndex = 0; //子类高亮索引 //点击大类时切换逻辑 getChildCategory(List<BxMallSubDto> list){ childIndex = 0; //每次点击切换大类,子类都要归0 BxMallSubDto all = BxMallSubDto(); all.mallSubId = '00'; all.mallCategoryId = '00'; all.mallSubName = '全部'; all.comments = 'null'; childCategoryList = [all]; childCategoryList.addAll(list); notifyListeners(); } //改变子类索引 changeChildIndex(index){ childIndex = index; notifyListeners(); } }
然后就可以修改UI部分了,UI部分主要是增加索引参数,然后进行判断。
(1)先把_rghtInkWell
方法增加一个接收参数int index
.这就是修改变量的索引值。
Widget _rightInkWell(int index,BxMallSubDto item)
(2)定义是否高亮变量,再根据状态进行赋值
bool isClick =false; //是否点击 isClick = (index == Provide.value<ChildCategory>(context).childIndex) ? true : false;
(3)用isCheck
判断是否高亮
color: isClick ? Colors.blueGrey:Colors.black
(4)_rightInkWell添加index
return _rightInkWell(index,childCategory.childCategoryList[index]);
(5)点击时修改状态_rightInkWell的onTap
Provide.value<ChildCategory>(context).changeChildIndex(index);
到这里,我们的子类高亮就制作完成了,并且当更换大类时,子类自动更改为第一个高亮。
列表页_子类和商品列表切换
修改Provide类
先改动一下child_ategory.dart
的Provide类,增加一个大类ID,然后在更改大类的时候改变ID。
import 'package:flutter/material.dart'; import '../model/category.dart'; //ChangeNotifier的混入是不用管理听众 class ChildCategory with ChangeNotifier{ List<BxMallSubDto> childCategoryList = []; int childIndex = 0; String categoryId = '4'; //点击大类时更换 getChildCategory(List<BxMallSubDto> list,String id){ childIndex=0; categoryId=id; BxMallSubDto all= BxMallSubDto(); all.mallSubId='00'; all.mallCategoryId='00'; all.mallSubName = '全部'; all.comments = 'null'; childCategoryList=[all]; childCategoryList.addAll(list); notifyListeners(); } //改变子类索引 changeChildIndex(index){ childIndex=index; notifyListeners(); } }
修改调用getChildCategory放
增加了参数,以前的调用方法也就都不对了,所以需要修改一下。直接用搜索功能就可以找到getChildCategory
方法,一共两处,直接修改就可以了
Provide.value<ChildCategory>(context).getChildCategory(childList,categoryId);
Provide.value<ChildCategory>(context).getChildCategory(list[0].bxMallSubDto,list[0].mallCategoryId);
增加getGoodsList方法
拷贝_getGoodsList
方法到子列表类里边,然后把传递参数换成子类的参数categorySubId
.代码如下:
//得到商品列表数据 void _getGoodList(String categorySubId) { var data={ 'categoryId':Provide.value<ChildCategory>(context).categoryId, 'categorySubId':categorySubId, 'page':1 }; request('getMallGoods',formData:data ).then((val){ var data = json.decode(val.toString()); CategoryGoodsListModel goodsList= CategoryGoodsListModel.fromJson(data); // Provide.value<CategoryGoodsList>(context).getGoodsList(goodsList.data); Provide.value<CategoryGoodsListProvide>(context).getGoodsList(goodsList.data); }); }
调用方法改版列表
当点击子类时,调用这个方法,并传入子类ID。
onTap: (){ Provide.value<ChildCategory>(context).changeChildIndex(index); _getGoodList(item.mallSubId); //调用子类ID },
子类没有商品时报错
有些小类别里是没有商品的,这时候就会报错。解决这个错误非常简单,只要进行判断就可以了
1、判断状态管理时是否存在数据
首先你要在修改状态的时候,就进行一次判断,方式类型不对,导致整个app崩溃。也就是在点击小类的ontap方法后,当然这里调用了_getGoodList()
方法。代码如下:
//得到商品列表数据 void _getGoodList(String categorySubId) { var data={ 'categoryId':Provide.value<ChildCategory>(context).categoryId, 'categorySubId':categorySubId, 'page':1 }; request('getMallGoods',formData:data ).then((val){ var data = json.decode(val.toString()); CategoryGoodsListModel goodsList= CategoryGoodsListModel.fromJson(data); // Provide.value<CategoryGoodsList>(context).getGoodsList(goodsList.data); if(goodsList.data==null){ Provide.value<CategoryGoodsListProvide>(context).getGoodsList([]); }else{ Provide.value<CategoryGoodsListProvide>(context).getGoodsList(goodsList.data); } }); }
2.判断界面输出时是不是有数据
这个主要时给用户一个友好的界面提示,如果没有数据,要提示用户。修改的是商品列表类的build
方法,代码如下:
@override Widget build(BuildContext context) { return Provide<CategoryGoodsListProvide>( builder: (context,child,data){ if(data.goodsList.length>0){ return Expanded( child:Container( width: ScreenUtil().setWidth(570) , child:ListView.builder( itemCount: data.goodsList.length, itemBuilder: (context,index){ return _ListWidget(data.goodsList,index); }, ) ) ) , ); }else{ return Text('暂时没有数据'); } }, ); }
把子类ID也Provide化
现在的子类ID,我们还没有形成状态,用的是普通的setState,如果要做下拉刷新,那setState肯定是不行的,因为这样就进行跨类了,没办法传递过去。
1.首先修改provide/child_category.dart
类,增加一个状态变量subId
,然后在两个方法里都进行修改,代码如下:
import 'package:flutter/material.dart'; import '../model/category.dart'; //ChangeNotifier的混入是不用管理听众 class ChildCategory with ChangeNotifier{ List<BxMallSubDto> childCategoryList = []; //商品列表 int childIndex = 0; //子类索引值 String categoryId = '4'; //大类ID String subId =''; //小类ID //点击大类时更换 getChildCategory(List<BxMallSubDto> list,String id){ categoryId=id; childIndex=0; subId=''; //点击大类时,把子类ID清空 BxMallSubDto all= BxMallSubDto(); all.mallSubId='00'; all.mallCategoryId='00'; all.mallSubName = '全部'; all.comments = 'null'; childCategoryList=[all]; childCategoryList.addAll(list); notifyListeners(); } //改变子类索引 , changeChildIndex(int index,String id){ //传递两个参数,使用新传递的参数给状态赋值 childIndex=index; subId=id; notifyListeners(); } }
这就为以后我们作上拉加载效果打下了基础。
列表页_上拉加载功能的制作
现在主要制作一下列表页的上拉加载更多功能,重点会放在上拉加载和Provide结合的方法。
增加page和noMoreText到Provide里
因为无论切换大类或者小类的时候,都需要把page变成1,所以需要在provide/child_category.dart
里新声明一个page变量.noMoreText
主要用来控制是否显示更多和如果没有数据了,不再向后台请求数据。每一次后台数据的请求都是宝贵的。
int page=1; //列表页数,当改变大类或者小类时进行改变 String noMoreText=''; //显示更多的标识
声明在切换大类和切换小类的时候都把page变成1,代码如下:
//点击大类时更换 getChildCategory(List<BxMallSubDto> list,String id){ isNewCategory=true; categoryId=id; childIndex=0; //------------------关键代码start page=1; noMoreText = ''; //------------------关键代码end subId=''; //点击大类时,把子类ID清空 noMoreText=''; BxMallSubDto all= BxMallSubDto(); all.mallSubId='00'; all.mallCategoryId='00'; all.mallSubName = '全部'; all.comments = 'null'; childCategoryList=[all]; childCategoryList.addAll(list); notifyListeners(); } //改变子类索引 , changeChildIndex(int index,String id){ isNewCategory=true; //传递两个参数,使用新传递的参数给状态赋值 childIndex=index; subId=id; //------------------关键代码start page=1; noMoreText = ''; //显示更多的表示 //------------------关键代码end notifyListeners(); }
还需要写一个增加page数量的方法,用来实现每次上拉加载后,page随之加一,代码如下:
//增加Page的方法f addPage(){ page++; }
在制作一个改变noMoreText
方法。
//改变noMoreText数据 changeNoMore(String text){ noMoreText=text; notifyListeners(); }
增加EasyRefresh组件
在category_page.dart
里增加EasyRefresh组件,首先需要使用import进行引入。
import 'package:flutter_easyrefresh/easy_refresh.dart';
引入之后,可以直接使用EasyRefresh
进行包裹,然后加上各种需要的参数,
@override Widget build(BuildContext context) { return Provide<CategoryGoodsListProvide>( builder: (context,child,data){ if(data.goodsList.length>0){ return Expanded( child:Container( width: ScreenUtil().setWidth(570) , child:EasyRefresh( refreshFooter: ClassicsFooter( key:_footerKey, bgColor:Colors.white, textColor:Colors.pink, moreInfoColor: Colors.pink, showMore:true, noMoreText:Provide.value<ChildCategory>(context).noMoreText, moreInfo:'加载中', loadReadyText:'上拉加载' ), child:ListView.builder( itemCount: data.goodsList.length, itemBuilder: (context,index){ return _ListWidget(data.goodsList,index); }, ) , loadMore: ()async{ print('上拉加载更多.......'); }, ) ) , ); }else{ return Text('暂时没有数据'); } }, ); }
修改请求数据的方法
这个类中也需要一个去后台请求数据的方法,这个方法要求从Provide里读出三个参数,大类ID,小类ID和页数。代码如下:
//上拉加载更多的方法 void _getMoreList(){ Provide.value<ChildCategory>(context).addPage(); var data={ 'categoryId':Provide.value<ChildCategory>(context).categoryId, 'categorySubId':Provide.value<ChildCategory>(context).subId, 'page':Provide.value<ChildCategory>(context).page }; request('getMallGoods',formData:data ).then((val){ var data = json.decode(val.toString()); CategoryGoodsListModel goodsList= CategoryGoodsListModel.fromJson(data); if(goodsList.data==null){ Provide.value<ChildCategory>(context).changeNoMore('没有更多了'); }else{ Provide.value<CategoryGoodsListProvide>(context).addGoodsList(goodsList.data); } }); }
每次都先调用增加页数的方法,这样请求的数据就是最新的,当没有数据的时候要把noMoreText
设置成‘没有更多了’。
切换类别返回顶部
到目前为止,我们应该可以正常展示上拉加载更多的方法了,但是还有一个小Bug,切换大类或者小类的时候,我们的页面没有回到顶部,这个其实很好解决。再build的Provide的构造器里加入下面的代码就可以了。
try{ if(Provide.value<ChildCategory>(context).page==1){ scrollController.jumpTo(0.0); } }catch(e){ print('进入页面第一次初始化:${e}'); }
当然你还要再列表类里进行声明scrollController
,如果你不声明是没办法使用的。
var scrollController=new ScrollController();
声明完成后,给ListView加上controller
属性。
child:ListView.builder( controller: scrollController, itemCount: data.goodsList.length, itemBuilder: (context,index){ return _ListWidget(data.goodsList,index); }, ) ,
这时候再进行测试,应该就可以了。
Fluttertoast组件的介绍
在APP的使用过程中,对用户的友好提示是必不可少的,比如当列表页上拉加载更多的时候,到达了数据的底部,没有更多数据了,就要给用户一个友好的提示。但是这种提示又不能影响用户的使用,一个轻提示组件给大家FlutterToast
。
Fluttertoast 组件简介
这是一个第三方组件,当你学习的时候可以到Github上查找最新版本。
GitHub地址:https://github.com/PonnamKarthik/FlutterToast
这个组件我觉的还时比较好用的,提供了样式自定义,而且自带的效果页是很酷炫的。
如何使用Fluttertoast
首先需要在pubspec.yaml
中进行引入Fluttertoast组件(也叫保持依赖,也叫包管理),主要版本号,请使用最新的,这里不保证时最新版本。
fluttertoast: ^3.1.3
引入后在需要使用的页面使用import
引入,引入代码如下:
import 'package:fluttertoast/fluttertoast.dart';
Fluttertoast使用方法
在需要使用的地方直接可以使用,如下代码:
Fluttertoast.showToast( msg: "已经到底了", toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIos: 1, backgroundColor: Colors.pink, textColor: Colors.white, fontSize: 16.0 );
- msg:提示的文字,String类型。
- toastLength: 提示的样式,主要是长度,有两个值可以选择:
Toast.LENGTH_SHORT
:短模式,就是比较短。Toast.LENGTH_LONG
: 长模式,就是比较长。 - gravity:提示出现的位置,分别是上中下,三个选项。
ToastGravity.TOP
顶部提示,ToastGravit.CENTER
中部提示,ToastGravity.BOTTOM
底部提示。 - bgcolor: 背景颜色,跟从Flutter颜色。
- textcolor:文字的颜色。
- fontSize: 文字的大小。
小Bug的处理
在列表页还存在着一个小Bug,就是当我们选择子类别后,然后返回全部,这时候会显示没有数据,这个主要是我们在Provide里构造虚拟类别时,传递的参数不对,只要把参数修改成空就可以了。
打开provide/child_category.dart
,修改getChildCateg()
方法。 修改代码如下:
//点击大类时更换 getChildCategory(List<BxMallSubDto> list,String id){ isNewCategory=true; categoryId=id; childIndex=0; page=1; subId=''; //点击大类时,把子类ID清空 noMoreText=''; BxMallSubDto all= BxMallSubDto(); //--------修改的关键代码start all.mallSubId=''; //--------修改的关键代码end all.mallCategoryId='00'; all.mallSubName = '全部'; all.comments = 'null'; childCategoryList=[all]; childCategoryList.addAll(list); notifyListeners(); }
现在重新运行就可以了 。