Flutter:仿京東項目實戰(4)-購物車頁面功能實現
highlight: a11y-dark
在我個人認為學習一門新的語言(快速高效學習) 一定是通過實踐,最好的就是做項目,這裏我會簡單寫一個京東的Demo。
第一天 搭建項目框架,實現首頁的功能:https://juejin.cn/editor/drafts/7043351582573854727。
第二天實現 分類和商品列表頁面: https://juejin.cn/post/7044716539550892068。
第三天實現 商品詳情頁功能:https://juejin.cn/editor/drafts/7045849478170935332
Flutter-混合工程的持續集成實踐: https://juejin.cn/post/7042099001679675422
前面實現了首頁、分類頁面、商品列表頁和商品詳情頁的功能,這篇文章實現購物車頁面的功能。
用到的知識點
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
,在裏面封裝常用的功能:
```dart import 'package:shared_preferences/shared_preferences.dart';
class Storage {
//設置值
static Future
//獲取值
static Future
//刪除值
static Future
//清理值
static Future
我這裏是用了String類型舉例,封裝了一個類專門管理。在之前的文章中頁講到數據存儲的兩種方式:https://juejin.cn/post/7040986659533357087
2. JSON 轉 Model
在日常開發中JSON的序列化與反序列化
是一個常見的操作,如果都是我們手動去解析JSON數據,是很麻煩的。如果能夠自動轉化就省去了很多事情。
工具實現
在iOS上面我就找到了一個工具可以自動轉換 json 數據:https://juejin.cn/post/7026898009900187679 。那在Flutter 中也找到了轉換的工具:https://app.quicktype.io ,相對來講也是比較好用的。
這樣就可以實現轉化。由於Flutter禁用運行時反射,才導致沒有像iOS成熟的庫完成解析,比如 MJExtension
、 YYModel
,這裏介紹一個相對成熟的庫 json_serializable 實現轉化。
json_serializable 實現
在項目的 pubspec.yaml
文件中添加依賴:
json_serializable: ^6.1.3
build_runner: ^2.1.7
json_annotation: ^4.4.0
然後執行 pub get
。要想使用轉化,首先要先用工具生成模型類,工具地址:https://caijinglong.github.io/json2dart/index_ch.html
在項目裏面創建模型類,把工具轉換的代碼拷貝到這個模型類裏面
```dart import 'package:json_annotation/json_annotation.dart';
part 'person.g.dart';
List
@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
} ```
接下來在終端執行flutter packages pub run build_runner watch
,就會在項目裏面生成person.g.dart
文件,這個裏面就是轉換好的代碼。
也可以執行 flutter packages pub run build_runner build
生成 person.g.dart
文件,區別在於上面是持續生成,下面這個是一次性生成。
注意上面工具使用時,會按着list裏面第一個map裏面的數據進行解析,假如數組裏面其他map字段比較多,就會存在漏字段的情況,這個還需要注意檢查下。整體使用下來也不是很方便,還不如用工具直接生成簡單:https://app.quicktype.io 。
插件 JsonToDart 實現
https://zhuanlan.zhihu.com/p/163330265 這個插件也可以實現轉換
在 Android Studio 中安裝 JsonToDart
插件,打開 Preferences(Mac)或者 Setting(Window),選擇 Plugins,搜索 JsonToDart
點擊 Install 安裝,安裝完成後重啟。這個時候選定目錄,點擊右鍵,選擇 New->Json to Dart
,或者使用快捷鍵
Windows:ALT + Shift + D
Mac:Option + Shift + D
選中 Json To Dart
後,彈出頁面輸入要轉換的json數據
點擊完成,就會生成對應的模型文件了
這個是三個json轉model方案裏面最簡單
的了。
上篇文章實現了五種JSON轉Model的方案:https://juejin.cn/post/7047011637248655396
3. 在不同分辨率的手機上查看UI效果
Flutter 開發最大的優勢就是其跨平台,當開發完成時,想在不同分辨率的手機查看其效果,如果跑每個機型去看效果還是比較麻煩的。這個包 device_preview
可以實現查看不同分辨率手機上的UI效果。
配置 device_preview: ^1.0.0
,然後執行 pub get
。在 main.dart裏面使用
```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(), ); } } ```
這個包可以實現下列功能: - 更改設備方向 - 動態系統配置:語言,暗模式,文本縮放比例 - 可自由調整分辨率和安全區域的設備 - 保持應用程序狀態 - 截圖
4. Provider 狀態管理
什麼是Provider 狀態管理?
當我們想在多個頁面(組件/Widget)之間共享狀態(數據),或者一個頁面(組 件/Widget)中的多個子組件之間共享狀態(數據),這個時候我們就可以用 Flutter 中的狀態管理來管理統一的狀態(數據),實現不同組件直接的傳值和數據共享。provider
是 Flutter 官方團隊
推出的狀態管理模式。
具體的使用:
- 配置
provider: ^6.0.1
, - 新建一個文件夾叫 provider,在 provider 文件夾裏面放我們對於的狀態管理類
- 在 provider 裏面新建 cart.dart
- cart.dart 裏面新建一個類繼承
ChangeNotifier
代碼如下,這裏主要是處理購物車中的數據
```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(); } } ```
最後別忘記在main.dart
中的MultiProvider
添加上這個文件
dart
providers:[
ChangeNotifierProvider(create: (_) => CheckOut()),
ChangeNotifierProvider(create: (_) => Cart()),
],
實現效果
具體實現代碼
界面框架代碼
```dart class CartPage extends StatefulWidget { CartPage({Key? key}) : super(key: key);
_CartPageState createState() => _CartPageState(); }
class _CartPageState extends State
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
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的實現代碼
單獨創建一個cart文件夾,在裏面放在主頁面抽離的代碼
```dart class CartItem extends StatefulWidget { Map _itemData; CartItem(this._itemData,{Key? key}) : super(key: key);
_CartItemState createState() => _CartItemState(); }
class _CartItemState extends State
//從本地存儲的數據裏面讀取的 late Map _itemData;
@override
Widget build(BuildContext context) {
//注意:給屬性賦值
this._itemData=widget._itemData;
//通過Provider實現了頁面和組件間的數據共享
var cartProvider = Provider.of
每件商品的數量加/減組件代碼
```dart class CartNum extends StatefulWidget { Map _itemData; CartNum(this._itemData,{Key? key}) : super(key: key);
_CartNumState createState() => _CartNumState(); }
class _CartNumState extends State
@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.dart 和 check_out.dart
```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(); } } ```
```dart class CheckOut with ChangeNotifier { List _checkOutListData = []; //購物車數據 List get checkOutListData => _checkOutListData;
changeCheckOutListData(data){ _checkOutListData=data; notifyListeners(); } } ```
以上就是購物車頁面的實現代碼。
- Flutter:仿京東項目實戰(4)-購物車頁面功能實現
- Flutter集成原生遇到的問題彙總
- Flutter:仿京東項目實戰(3)-商品詳情頁功能實現
- Flutter-Dart中的異步和多線程講解
- iOS-底層原理分析之Block本質
- Flutter-官方推薦的Flutter與原生交互插件Pigeon
- Flutter-flutter_sound錄音與播放
- iOS-CocoaPods的原理及Podfile.lock問題
- iOS配置多環境的三種方案
- iOS-各種Crash防護
- iOS-Swift中常見的幾種閉包
- Flutter:仿京東項目實戰(2)-分類和商品列表頁面功能實現
- Flutter:仿京東項目實戰(1)-首頁功能實現
- Flutter-JSON轉Model的四種便捷方案
- Flutter-導航與路由堆棧詳解
- Flutter 與原生通信的三種方式
- iOS-內存泄漏檢測
- Fastlane實現自動打包
- 懶人必備神器-Xcode代碼塊
- Jenkins實現自動化打包