在我个人认为学习一门新的言语(快速高效学习) 一定是经过实践,最好的便是做项目,这儿我会简略写一个京东的Demo。

第一天 搭建项目框架,完成首页的功用:/editor/draf…

第二天完成 分类和产品列表页面: /post/704471…

第三天完成 产品详情页功用:/editor/draf…

Flutter-混合工程的持续集成实践: /post/704209…

前面完成了首页、分类页面、产品列表页和产品详情页的功用,这篇文章完成购物车页面的功用。

用到的知识点

1. shared_preferences 完成本地数据存储

shared_preferences 是 Flutter 供给的 key-value 存储插件,能够将数据耐久化到磁盘中,支撑 Android 和 iOS,在 iOS 中是基于 NSUserDefaults,在 Android 中基于SharedPreferences

在项目的 pubspec.yaml 文件中添加依靠:shared_preferences: ^2.0.11,然后履行 pub get
shared_preferences 支撑的数据类型有 int、double、bool、string、stringList

services文件里边界说一个storage.dart,在里边封装常用的功用:

import 'package:shared_preferences/shared_preferences.dart';
class Storage {
  //设置值
  static Future<void> setString(key, value) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    var result = sp.setString(key, value);
  }
  //获取值
  static Future<String?> getString(key) async{
    SharedPreferences sp = await SharedPreferences.getInstance();
    var result = sp.getString(key);
    return result;
  }
  //删去值
  static Future<void> remove(key) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.remove(key);
  }
  //整理值
  static Future<void> clear() async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.clear();
  }
}

我这儿是用了String类型举例,封装了一个类专门办理。在之前的文章中页讲到数据存储的两种方法:/post/704098…

2. JSON 转 Model

在日常开发中JSON的序列化与反序列化是一个常见的操作,假设都是咱们手动去解析JSON数据,是很费事的。假设能够主动转化就省去了许多工作。

东西完成

在iOS上面我就找到了一个东西能够主动转化 json 数据:/post/702689… 。那在Flutter 中也找到了转化的东西:app.quicktype.io ,相对来讲也是比较好用的。

截屏2021-12-28 下午9.54.10.png

这样就能够完成转化。由于Flutter禁用运行时反射,才导致没有像iOS成熟的库完成解析,比方 MJExtensionYYModel,这儿介绍一个相对成熟的库 json_serializable 完成转化。

json_serializable 完成

在项目的 pubspec.yaml 文件中添加依靠:

json_serializable: ^6.1.3
build_runner: ^2.1.7
json_annotation: ^4.4.0

然后履行 pub get。要想运用转化,首先要先用东西生成模型类,东西地址:caijinglong.github.io/json2dart/i…

截屏2021-12-28 下午9.31.39.png

在项目里边创建模型类,把东西转化的代码拷贝到这个模型类里边

import 'package:json_annotation/json_annotation.dart';
part 'person.g.dart';
List<person> getpersonList(List<dynamic> list){
  List<person> result = [];
  list.forEach((item){
    result.add(person.fromJson(item));
  });
  return result;
}
@JsonSerializable()
class person extends Object with _$personSerializerMixin{
  @JsonKey(name: 'name')
  String name;
  @JsonKey(name: 'age')
  String age;
  @JsonKey(name: 'tele')
  String tele;
  person(this.name,this.age,this.tele,);
  factory person.fromJson(Map<String, dynamic> srcJson) => _$personFromJson(srcJson);
}

接下来在终端履行flutter packages pub run build_runner watch,就会在项目里边生成person.g.dart文件,这个里边便是转化好的代码。

截屏2021-12-28 下午9.38.57.png

也能够履行 flutter packages pub run build_runner build生成 person.g.dart文件,区别在于上面是持续生成,下面这个是一次性生成。

留意上面东西运用时,会按着list里边第一个map里边的数据进行解析,假设数组里边其他map字段比较多,就会存在漏字段的情况,这个还需要留意检查下。全体运用下来也不是很便利,还不如用东西直接生成简略:app.quicktype.io 。

插件 JsonToDart 完成

zhuanlan.zhihu.com/p/163330265 这个插件也能够完成转化

在 Android Studio 中装置 JsonToDart 插件,翻开 Preferences(Mac)或许 Setting(Window),挑选 Plugins,搜索 JsonToDart

截屏2021-12-28 下午10.08.59.png

点击 Install 装置,装置完成后重启。这个时分选定目录,点击右键,挑选 New->Json to Dart,或许运用快捷键

Windows:ALT + Shift + D
Mac:Option + Shift + D

截屏2021-12-28 下午10.11.24.png

选中 Json To Dart 后,弹出页面输入要转化的json数据

截屏2021-12-28 下午10.10.35.png

点击完成,就会生成对应的模型文件了

截屏2021-12-28 下午10.10.55.png

这个是三个json转model计划里边最简略的了。

上篇文章完成了五种JSON转Model的计划:/post/704701…

3. 在不同分辨率的手机上检查UI作用

Flutter 开发最大的优势便是其跨渠道,当开发完成时,想在不同分辨率的手机检查其作用,假设跑每个机型去看作用还是比较费事的。这个包 device_preview 能够完成检查不同分辨率手机上的UI作用。

装备 device_preview: ^1.0.0,然后履行 pub get。在 main.dart里边运用

import 'package:device_preview/device_preview.dart';
void main() => runApp(
  DevicePreview(
    enabled: !kReleaseMode,//在非release环境下运用
    builder: (context) => MyApp(), // Wrap your app
  ),
);
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      useInheritedMediaQuery: true,
      locale: DevicePreview.locale(context),
      builder: DevicePreview.appBuilder,
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: const HomePage(),
    );
  }
}

这个包能够完成下列功用:

  • 更改设备方向
  • 动态系统装备:言语,暗形式,文本缩放比例
  • 可自在调整分辨率和安全区域的设备
  • 保持应用程序状况
  • 截图

Simulator Screen Shot - iPhone 12 Pro - 2021-12-29 at 18.24.12.png

4. Provider 状况办理

什么是Provider 状况办理?

当咱们想在多个页面(组件/Widget)之间同享状况(数据),或许一个页面(组 件/Widget)中的多个子组件之间同享状况(数据),这个时分咱们就能够用 Flutter 中的状况办理来办理一致的状况(数据),完成不同组件直接的传值和数据同享。provider 是 Flutter 官方团队推出的状况办理形式。

详细的运用:

  • 装备provider: ^6.0.1
  • 新建一个文件夹叫 provider,在 provider 文件夹里边放咱们关于的状况办理类
  • 在 provider 里边新建 cart.dart
  • cart.dart 里边新建一个类承继 ChangeNotifier 代码如下,这儿主要是处理购物车中的数据
class Cart with ChangeNotifier {
  List _cartList = [];//购物车数据
  bool _isCheckAll = false;//全选
  double _allPrice = 0;//总价
  List get cartList => _cartList;
  bool get isCheckAll => _isCheckAll;
  double get allPrice => _allPrice;
  Cart(){
    this.init();
  }
  //初始化的时分获取购物车数据
  init() async {
    String? cartList = await Storage.getString(('cartList'));
    if(cartList != null){
      List cartListData = json.decode(cartList);
      _cartList = cartListData;
    } else {
      _cartList = [];
    }
    //获取全选的状况
    _isCheckAll = this.isCheckAll;
    //核算总价
    computeAllPrice();
    notifyListeners();
  }
  updateCartList() {
    this.init();
  }
  itemCountChange() {
    Storage.setString('cartList', json.encode(_cartList));
    //核算总价
    computeAllPrice();
    notifyListeners();
  }
  //全选 反选
  checkAll(value) {
    for (var i = 0; i < _cartList.length; i++) {
      _cartList[i]['checked'] = value;
    }
    _isCheckAll = value;
    //核算总价
    computeAllPrice();
    Storage.setString('cartList', json.encode(_cartList));
    notifyListeners();
  }
  //判断是否全选
  bool isCheckedAll() {
    if (_cartList.length > 0) {
      for (var i = 0; i < cartList.length; i++) {
        if (_cartList[i]['checked'] == false) {
          return false;
        }
      }
      return true;
    }
    return false;
  }
  //监听每一项的选中事情
  itemChage() {
    if (isCheckAll == true) {
      _isCheckAll = true;
    } else {
      _isCheckAll = false;
    }
    //核算总价
    computeAllPrice();
    Storage.setString('cartList', json.encode(_cartList));
    notifyListeners();
  }
  //核算总价
  computeAllPrice() {
    double tempAllPrice = 0;
    for (var i = 0; i < _cartList.length; i++) {
      if (_cartList[i]['checked'] == true) {
        tempAllPrice += _cartList[i]['price'] * _cartList[i]['count'];
      }
    }
    _allPrice = tempAllPrice;
    notifyListeners();
  }
  //删去数据
  removeItem() {
    List tempList=[];
    for (var i = 0; i < _cartList.length; i++) {
      if (_cartList[i]['checked'] == false) {
        tempList.add(_cartList[i]);
      }
    }
    _cartList=tempList;
    //核算总价
    computeAllPrice();
    Storage.setString('cartList', json.encode(_cartList));
    notifyListeners();
  }
}

最终别忘记在main.dart中的MultiProvider添加上这个文件

providers:[
  ChangeNotifierProvider(create: (_) => CheckOut()),
  ChangeNotifierProvider(create: (_) => Cart()),
],

完成作用

Simulator Screen Shot - iPhone 12 Pro - 2021-12-28 at 20.07.24.png

详细完成代码

界面框架代码

class CartPage extends StatefulWidget {
  CartPage({Key? key}) : super(key: key);
  _CartPageState createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
  bool _isEdit = false;
  var checkOutProvider;
  @override
  void initState() {
    super.initState();
  }
  //去结算
  doCheckOut() async {
    //1、获取购物车选中的数据
    List checkOutData = await CartServices.getCheckOutData();
    //2、保存购物车选中的数据
    this.checkOutProvider.changeCheckOutListData(checkOutData);
    //3、购物车有没有选中的数据
    if (checkOutData.length > 0) {
      Navigator.pushNamed(context, '/checkOut');
    } else {
      Fluttertoast.showToast(
        msg: '购物车没有选中的数据',
        toastLength: Toast.LENGTH_SHORT,
        gravity: ToastGravity.CENTER,
      );
    }
  }
  @override
  Widget build(BuildContext context) {
    var cartProvider = Provider.of<Cart>(context);
    checkOutProvider = Provider.of<CheckOut>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('购物车'),
        actions: [
          IconButton(onPressed: (){
          }, icon: Icon(Icons.launch))
        ],
      ),
      body: cartProvider.cartList.length > 0 ? Stack(
        children: [
          //列表
          ListView(
            children: [
              Column(
                children: [
                  Column(
                    children: cartProvider.cartList.map((value){
                      //返回生成每个Item
                      return CartItem(value);
                    }).toList(),
                  ),
                  SizedBox(height: ScreenAdapter.height(100))
                ],
              )
            ],
          ),
          //底部的全选和结算按钮
          Positioned(
              bottom: 0,
              width: ScreenAdapter.width(750),
              height: ScreenAdapter.height(78),
              child: Container(
                decoration: BoxDecoration(
                    border: Border(
                      top: BorderSide(width: 1, color: Colors.black12),
                    ),
                  color: Colors.white
                ),
                width: ScreenAdapter.width(750),
                height: ScreenAdapter.height(78),
                child: Stack(
                  children: [
                    Align(
                      alignment: Alignment.centerLeft,
                      child: Row(
                        children: [
                          Container(
                            width: ScreenAdapter.width(60),
                            child: Checkbox(
                              value: false,
                              activeColor: Colors.pink,
                              onChanged: (v){
                              },
                            ),
                          ),
                          Text('全选'),
                        ],
                      ),
                    ),
                    Align(
                      alignment: Alignment.centerRight,
                      child: Container(
                        margin: EdgeInsets.only(right: 10),
                        child: ElevatedButton(
                          child: Text('结算', style: TextStyle(color: Colors.white),),
                          style: ButtonStyle(
                            backgroundColor: MaterialStateProperty.all(Colors.red),
                          ),
                          onPressed: (){
                            doCheckOut();
                          },
                        ),
                      ),
                    )
                  ],
                ),
              )
          ),
        ],
      ) : Center(
        child: Text("购物车空空的..."),
      ),
    );
  }
}

每个Item的完成代码

截屏2021-12-29 下午5.46.29.png

独自创建一个cart文件夹,在里边放在主页面抽离的代码

class CartItem extends StatefulWidget {
  Map _itemData;
  CartItem(this._itemData,{Key? key}) : super(key: key);
  _CartItemState createState() => _CartItemState();
}
class _CartItemState extends State<CartItem> {
  //从本地存储的数据里边读取的
  late Map _itemData;
  @override
  Widget build(BuildContext context) {
    //留意:给属性赋值
    this._itemData=widget._itemData;
    //经过Provider完成了页面和组件间的数据同享
    var cartProvider = Provider.of<Cart>(context);
    return Container(
      height: ScreenAdapter.height(220),
      padding: EdgeInsets.all(5),
      decoration: BoxDecoration(
          border: Border(bottom: BorderSide(width: 1, color: Colors.black12))),
      child: Row(
        children: <Widget>[
          Container(
            width: ScreenAdapter.width(60),
            child: Checkbox(
              value: _itemData["checked"],
              onChanged: (val) {
                _itemData["checked"]=!_itemData["checked"];
                //更新数据
                cartProvider.itemChage();
              },
              activeColor: Colors.pink,
            ),
          ),
          Container(
            width: ScreenAdapter.width(160),
            child: Image.network(
                "${_itemData["pic"]}",
                fit: BoxFit.cover),
          ),
          Expanded(
            flex: 1,
            child: Container(
              padding: EdgeInsets.fromLTRB(10, 10, 10, 5),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text("${_itemData["title"]}",
                      maxLines: 2),
                  Text("${_itemData["selectedAttr"]}",
                      maxLines: 2),
                  Stack(
                    children: <Widget>[
                      Align(
                        alignment: Alignment.centerLeft,
                        child: Text("¥${_itemData["price"]}",style: TextStyle(
                            color: Colors.red
                        )),
                      ),
                      Align(
                        alignment: Alignment.centerRight,
                        //完成添加/削减物品件数
                        child: CartNum(_itemData),
                      )
                    ],
                  )
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

每件产品的数量加/减组件代码

截屏2021-12-29 下午5.46.45.png

class CartNum extends StatefulWidget {
  Map _itemData;
  CartNum(this._itemData,{Key? key}) : super(key: key);
  _CartNumState createState() => _CartNumState();
}
class _CartNumState extends State<CartNum> {
  late Map _itemData;
  var cartProvider;
  @override
  Widget build(BuildContext context) {
    //留意
    _itemData=widget._itemData;
    cartProvider = Provider.of<Cart>(context);
    return Container(
      width: ScreenAdapter.width(168),
      decoration:
      BoxDecoration(border: Border.all(width: ScreenAdapter.width(2), color: Colors.black12)),
      child: Row(
        children: <Widget>[
          _leftBtn(),
          _centerArea(),
          _rightBtn()
        ],
      ),
    );
  }
  //左边按钮
  Widget _leftBtn() {
    return InkWell(
      onTap: () {
        if(_itemData["count"]>1){
          _itemData["count"]--;
          cartProvider.itemCountChange();
        }
      },
      child: Container(
        alignment: Alignment.center,
        width: ScreenAdapter.width(45),
        height: ScreenAdapter.height(45),
        child: Text("-"),
      ),
    );
  }
  //右侧按钮
  Widget _rightBtn() {
    return InkWell(
      onTap: (){
        _itemData["count"]++;
        cartProvider.itemCountChange();
      },
      child: Container(
        alignment: Alignment.center,
        width: ScreenAdapter.width(45),
        height: ScreenAdapter.height(45),
        child: Text("+"),
      ),
    );
  }
//中间
  Widget _centerArea() {
    return Container(
      alignment: Alignment.center,
      width: ScreenAdapter.width(70),
      decoration: BoxDecoration(
          border: Border(
            left: BorderSide(width: ScreenAdapter.width(2), color: Colors.black12),
            right: BorderSide(width: ScreenAdapter.width(2), color: Colors.black12),
          )),
      height: ScreenAdapter.height(45),
      child: Text("${_itemData["count"]}"),
    );
  }
}

provider 代码

经过运用provider完成了数据同享,创建了两个文件 cart.dartcheck_out.dart

class Cart with ChangeNotifier {
  List _cartList = [];//购物车数据
  bool _isCheckAll = false;//全选
  double _allPrice = 0;//总价
  List get cartList => _cartList;
  bool get isCheckAll => _isCheckAll;
  double get allPrice => _allPrice;
  Cart(){
    this.init();
  }
  //初始化的时分获取购物车数据
  init() async {
    String? cartList = await Storage.getString(('cartList'));
    if(cartList != null){
      List cartListData = json.decode(cartList);
      _cartList = cartListData;
    } else {
      _cartList = [];
    }
    //获取全选的状况
    _isCheckAll = this.isCheckAll;
    //核算总价
    computeAllPrice();
    notifyListeners();
  }
  updateCartList() {
    this.init();
  }
  itemCountChange() {
    Storage.setString('cartList', json.encode(_cartList));
    //核算总价
    computeAllPrice();
    notifyListeners();
  }
  //全选 反选
  checkAll(value) {
    for (var i = 0; i < _cartList.length; i++) {
      _cartList[i]['checked'] = value;
    }
    _isCheckAll = value;
    //核算总价
    computeAllPrice();
    Storage.setString('cartList', json.encode(_cartList));
    notifyListeners();
  }
  //判断是否全选
  bool isCheckedAll() {
    if (_cartList.length > 0) {
      for (var i = 0; i < cartList.length; i++) {
        if (_cartList[i]['checked'] == false) {
          return false;
        }
      }
      return true;
    }
    return false;
  }
  //监听每一项的选中事情
  itemChage() {
    if (isCheckAll == true) {
      _isCheckAll = true;
    } else {
      _isCheckAll = false;
    }
    //核算总价
    computeAllPrice();
    Storage.setString('cartList', json.encode(_cartList));
    notifyListeners();
  }
  //核算总价
  computeAllPrice() {
    double tempAllPrice = 0;
    for (var i = 0; i < _cartList.length; i++) {
      if (_cartList[i]['checked'] == true) {
        tempAllPrice += _cartList[i]['price'] * _cartList[i]['count'];
      }
    }
    _allPrice = tempAllPrice;
    notifyListeners();
  }
  //删去数据
  removeItem() {
    List tempList=[];
    for (var i = 0; i < _cartList.length; i++) {
      if (_cartList[i]['checked'] == false) {
        tempList.add(_cartList[i]);
      }
    }
    _cartList=tempList;
    //核算总价
    computeAllPrice();
    Storage.setString('cartList', json.encode(_cartList));
    notifyListeners();
  }
}
class CheckOut with ChangeNotifier {
  List _checkOutListData = []; //购物车数据
  List get checkOutListData => _checkOutListData;
  changeCheckOutListData(data){
    _checkOutListData=data;
    notifyListeners();
  }
}

以上便是购物车页面的完成代码。