Flutter 商城實例 分類列表


列表頁_使用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.dartCategoryPage類的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]);
            },
          );
        },
      ),  
    );
  }

修改步驟:

  1. Container Widget外層加入一個Provie widget
  2. 修改ListView WidgetitemCount選項為childCategory.childCategoryList.length
  3. 修改itemBuilder里的傳值選項為return _rightInkWell(childCategory.childCategoryList[index]);

交互效果的設置

現在二級分類已經能跟隨我們的點擊發生變化了,但是大類還沒有高亮顯示,所以要作一下交互效果,這種交互效果跟其它類或者頁面沒什么關系,所以我們還是使用最簡單的setState來實現了。 這個變化主要在_leftInkWell里,所以操作也基本在這個里邊。

  1. 先聲明一個變量,用於控制是否高亮顯示bool isClick = false;
  2. _leftInkWell接收一個變量,變量是ListView傳遞過來的Widget _leftInkWel(int index)
  3. 聲明一個全局的變量var listIndex = 0; //索引
  4. 對比index和listIndexisClick=(index==listIndex)?true:false;.
  5. 修改為動態顯示背景顏色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.dartgetchildCategory方法。思路是聲明一個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進行包裹,然后加上各種需要的參數,

 
         
  GlobalKey<RefreshFooterState> _footerkey = new GlobalKey<RefreshFooterState>(); //定義key

@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();
}

現在重新運行就可以了 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM