列表頁_使用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(); }
現在重新運行就可以了 。