使用Provider 实现购物功能
下面这个demo 来自flutter 官网,有兴趣的小伙伴可以去看看原文。
https://flutter.cn/docs/development/data-and-backend/state-mgmt/simple
效果如下:
main.dart文件
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_shopper/common/theme.dart';
import 'package:provider_shopper/models/cart.dart';
import 'package:provider_shopper/models/catalog.dart';
import 'package:provider_shopper/screens/cart.dart';
import 'package:provider_shopper/screens/catalog.dart';
import 'package:provider_shopper/screens/login.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 提供多个对象时使用 MultiProvider 很方便。
return MultiProvider(
providers: [
// 在这个示例应用程序中,CatalogModel 永远不会改变,所以一个简单的 Provider 就足够了。
Provider(create: (context) => CatalogModel()),
// CartModel 实现为 ChangeNotifier,它要求使用 ChangeNotifierProvider。
// 此外,CartModel 依赖于 CatalogModel,
// 因此需要一个 ProxyProvider。
ChangeNotifierProxyProvider<CatalogModel, CartModel>(
create: (context) => CartModel(),
update: (context, catalog, cart) {
if (cart == null) throw ArgumentError.notNull('cart');
cart.catalog = catalog;
return cart;
},
),
],
child: MaterialApp(
title: 'Provider Demo',
theme: appTheme,
initialRoute: '/',
routes: {
'/': (context) => const MyLogin(),
'/catalog': (context) => const MyCatalog(),
'/cart': (context) => const MyCart(),
},
),
);
}
}
models\cart.dart文件
import 'package:flutter/foundation.dart';
import 'package:provider_shopper/models/catalog.dart';
class CartModel extends ChangeNotifier {
/// 支持 [目录] 的私有字段。
late CatalogModel _catalog;
/// 购物车的内部私有状态。存储每个项目的 ID。
final List<int> _itemIds = [];
/// 当前目录。用于从数字 id 构造项目。
CatalogModel get catalog => _catalog;
set catalog(CatalogModel newCatalog) {
_catalog = newCatalog;
// 通知听众,以防新目录提供的信息与以前的不同。
// 例如,项目的可用性可能已更改。
notifyListeners();
}
/// List of items in the cart.
List<Item> get items => _itemIds.map((id) => _catalog.getById(id)).toList();
/// 所有商品的当前总价。
int get totalPrice =>
items.fold(0, (total, current) => total + current.price);
/// 将 [item] 添加到购物车。这是从外部修改购物车的唯一方法。
void add(Item item) {
_itemIds.add(item.id);
// 这一行告诉 [Model] 它应该重建依赖它的小部件。
notifyListeners();
}
void remove(Item item) {
_itemIds.remove(item.id);
// 每次更改模型时,不要忘记告诉依赖的小部件重新构建。
notifyListeners();
}
}
models\catalog.dart文件
import 'package:flutter/material.dart';
/// 用户可以购买的商品目录的代理。
///
/// 在真实的应用程序中,这可能由后端支持并缓存在设备上。
/// 在这个示例应用程序中,目录是程序生成的并且是无限的。
///
/// 为简单起见,目录应该是不可变的(在应用程序执行期间不希望添加、删除或更改任何产品)。
class CatalogModel {
static List<String> itemNames = [
'Code Smell',
'Control Flow',
'Interpreter',
'Recursion',
'Sprint',
'Heisenbug',
'Spaghetti',
'Hydra Code',
'Off-By-One',
'Scope',
'Callback',
'Closure',
'Automata',
'Bit Shift',
'Currying',
];
/// 通过 [id] 获取项目。
///
/// 在此示例中,目录是无限的,在 [itemNames] 上循环。
Item getById(int id) => Item(id, itemNames[id % itemNames.length]);
/// 通过其在目录中的位置获取项目。
Item getByPosition(int position) {
// 在这个简化的例子中,一个项目在目录中的位置也是它的 id。
return getById(position);
}
}
@immutable
class Item {
final int id;
final String name;
final Color color;
final int price = 42;
Item(this.id, this.name)
// 为了使示例应用程序看起来更好,每个项目都被赋予了一种 Material Design 原色。
: color = Colors.primaries[id % Colors.primaries.length];
@override
int get hashCode => id;
@override
bool operator ==(Object other) => other is Item && other.id == id;
}
screens\cart.dart文件
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_shopper/models/cart.dart';
class MyCart extends StatelessWidget {
const MyCart({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cart', style: Theme.of(context).textTheme.headline1),
backgroundColor: Colors.white,
),
body: Container(
color: Colors.yellow,
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(32),
child: _CartList(),
),
),
const Divider(height: 4, color: Colors.black),
_CartTotal()
],
),
),
);
}
}
class _CartList extends StatelessWidget {
@override
Widget build(BuildContext context) {
var itemNameStyle = Theme.of(context).textTheme.headline6;
// This gets the current state of CartModel and also tells Flutter
// to rebuild this widget when CartModel notifies listeners (in other words,
// when it changes).
var cart = context.watch<CartModel>();
return ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.done),
trailing: IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: () {
cart.remove(cart.items[index]);
},
),
title: Text(
cart.items[index].name,
style: itemNameStyle,
),
),
);
}
}
class _CartTotal extends StatelessWidget {
@override
Widget build(BuildContext context) {
var hugeStyle =
Theme.of(context).textTheme.headline1!.copyWith(fontSize: 48);
return SizedBox(
height: 200,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 侦听模型更改的另一种方法是包含 Consumer 小部件。
// 此小部件将自动侦听 CartModel 并在每次更改时重新运行其构建器。
//
// 重要的是它不会在此构建方法中重建其余的小部件。
Consumer<CartModel>(
builder: (context, cart, child) =>
Text('\$${cart.totalPrice}', style: hugeStyle)),
const SizedBox(width: 24),
TextButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Buying not supported yet.')));
},
style: TextButton.styleFrom(primary: Colors.white),
child: const Text('BUY'),
),
],
),
),
);
}
}
screens\catalog.dart文件
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_shopper/models/cart.dart';
import 'package:provider_shopper/models/catalog.dart';
class MyCatalog extends StatelessWidget {
const MyCatalog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
_MyAppBar(),
const SliverToBoxAdapter(child: SizedBox(height: 12)),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _MyListItem(index)),
),
],
),
);
}
}
class _AddButton extends StatelessWidget {
final Item item;
const _AddButton({required this.item, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// context.select() 方法可以让你监听模型一部分的变化。
// 您定义一个“选择”(即返回)您感兴趣的部分的函数,并且提供程序包不会重建此小部件,
// 除非模型的特定部分发生更改。
//
// 这可以导致显着的性能改进。
var isInCart = context.select<CartModel, bool>(
// Here, we are only interested whether [item] is inside the cart.
(cart) => cart.items.contains(item),
);
return TextButton(
onPressed: isInCart
? null
: () {
// 如果商品不在购物车中,我们让用户添加它。
// 我们在这里使用 context.read() ,因为每当用户点击按钮时都会执行回调。
// 换句话说,它是在 build 方法之外执行的。
var cart = context.read<CartModel>();
cart.add(item);
},
style: ButtonStyle(
overlayColor: MaterialStateProperty.resolveWith<Color?>((states) {
if (states.contains(MaterialState.pressed)) {
return Theme.of(context).primaryColor;
}
return null; // 遵循小部件的默认设置。
}),
),
child: isInCart
? const Icon(Icons.check, semanticLabel: 'ADDED')
: const Text('ADD'),
);
}
}
class _MyAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SliverAppBar(
title: Text('Catalog', style: Theme.of(context).textTheme.headline1),
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () => Navigator.pushNamed(context, '/cart'),
),
],
);
}
}
class _MyListItem extends StatelessWidget {
final int index;
const _MyListItem(this.index, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var item = context.select<CatalogModel, Item>(
// 在这里,我们只对 [index] 处的项目感兴趣。我们不关心任何其他变化。
(catalog) => catalog.getByPosition(index),
);
var textTheme = Theme.of(context).textTheme.headline6;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: LimitedBox(
maxHeight: 48,
child: Row(
children: [
AspectRatio(
aspectRatio: 1,
child: Container(
color: item.color,
),
),
const SizedBox(width: 24),
Expanded(
child: Text(item.name, style: textTheme),
),
const SizedBox(width: 24),
_AddButton(item: item),
],
),
),
);
}
}
login.dart
import 'package:flutter/material.dart';
class MyLogin extends StatelessWidget {
const MyLogin({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
padding: const EdgeInsets.all(80.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Welcome',
style: Theme.of(context).textTheme.headline1,
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Username',
),
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Password',
),
obscureText: true,
),
const SizedBox(
height: 24,
),
ElevatedButton(
child: const Text('ENTER'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/catalog');
},
style: ElevatedButton.styleFrom(
primary: Colors.yellow,
),
)
],
),
),
),
);
}
}
theme.dart
import 'package:flutter/material.dart';
final appTheme = ThemeData(
primarySwatch: Colors.yellow,
textTheme: const TextTheme(
headline1: TextStyle(
fontFamily: 'Corben',
fontWeight: FontWeight.w700,
fontSize: 24,
color: Colors.black,
),
),
);
|