likes
comments
collection
share

Flutter:仿京东项目实战(4)-购物车页面功能实现

作者站长头像
站长
· 阅读数 58

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

前面实现了首页、分类页面、商品列表页和商品详情页的功能,这篇文章实现购物车页面的功能。

用到的知识点

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

2. JSON 转 Model

在日常开发中JSON的序列化与反序列化是一个常见的操作,如果都是我们手动去解析JSON数据,是很麻烦的。如果能够自动转化就省去了很多事情。

工具实现

Flutter:仿京东项目实战(4)-购物车页面功能实现

这样就可以实现转化。由于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…

Flutter:仿京东项目实战(4)-购物车页面功能实现

在项目里面创建模型类,把工具转换的代码拷贝到这个模型类里面

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文件,这个里面就是转换好的代码。

Flutter:仿京东项目实战(4)-购物车页面功能实现

也可以执行 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

Flutter:仿京东项目实战(4)-购物车页面功能实现

点击 Install 安装,安装完成后重启。这个时候选定目录,点击右键,选择 New->Json to Dart,或者使用快捷键

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

Flutter:仿京东项目实战(4)-购物车页面功能实现

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

Flutter:仿京东项目实战(4)-购物车页面功能实现

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

Flutter:仿京东项目实战(4)-购物车页面功能实现

这个是三个json转model方案里面最简单的了。

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

这个包可以实现下列功能:

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

Flutter:仿京东项目实战(4)-购物车页面功能实现

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()),
],

实现效果

Flutter:仿京东项目实战(4)-购物车页面功能实现

具体实现代码

界面框架代码

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的实现代码

Flutter:仿京东项目实战(4)-购物车页面功能实现

单独创建一个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),
                      )
                    ],
                  )
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

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

Flutter:仿京东项目实战(4)-购物车页面功能实现

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

以上就是购物车页面的实现代码。

转载自:https://juejin.cn/post/7047303734501572639
评论
请登录