Flutter - 应用程序状态
应用程序状态 - scoped_model
Flutter 提供了一种使用 scoped_model 包管理应用程序状态的简单方法。Flutter 包只是可重用功能库。我们将在接下来的章节中详细了解 Flutter 包。
scoped_model 提供了三个主要类,用于在应用程序中实现强大的状态管理,这里将详细讨论 −
模型
模型封装了应用程序的状态。我们可以根据需要使用尽可能多的模型(通过继承模型类)来维护应用程序状态。它有一个方法,notifyListeners,每当模型状态发生变化时都需要调用该方法。notifyListeners 将执行必要的操作来更新 UI。
class Product extends Model { final String name; final String description; final int price; final String image; int rating; Product(this.name, this.description, this.price, this.image, this.rating); factory Product.fromMap(Map<String, dynamic> json) { return Product( json['name'], json['description'], json['price'], json['image'], json['rating'], ); } void updateRating(int myRating) { rating = myRating; notifyListeners(); } }
ScopedModel
ScopedModel 是一个小部件,它保存给定的模型,然后根据需要将其传递给所有后代小部件。如果需要多个模型,则需要嵌套 ScopedModel。
单一模型
ScopedModel<Product>( model: item, child: AnyWidget() )
多种模型
ScopedModel<Product>( model: item1, child: ScopedModel<Product>( model: item2, child: AnyWidget(), ), )
ScopedModel.of 是一种用于获取 ScopedModel 底层模型的方法。当模型将要更改但不需要更改 UI 时,可以使用该方法。以下操作不会更改产品的 UI(评级)。
ScopedModel.of<Product>(context).updateRating(2);
ScopedModelDescendant
ScopedModelDescendant 是一个窗口小部件,它从上层窗口小部件 ScopedModel 获取模型,并在模型更改时构建其用户界面。
ScopedModelDescendant 有两个属性 - builder 和 child。child 是不会更改的 UI 部分,将传递给 builder。builder 接受一个带有三个参数的函数 −
content − ScopedModelDescendant 传递应用程序的上下文。
child − UI 的一部分,不会根据模型而改变。
model − 该实例的实际模型。
return ScopedModelDescendant<ProductModel>( builder: (context, child, cart) => { ... Actual UI ... }, child: PartOfTheUI(), );
让我们将之前的示例更改为使用 scoped_model 而不是 StatefulWidget
在 Android Studio 中创建一个新的 Flutter 应用程序,product_scoped_model_app
用我们的 product_state_app 代码替换默认启动代码 (main.dart)
将 assets 文件夹从 product_nav_app 复制到 product_rest_app 并在 pubspec.yaml 文件中添加 assets
flutter: assets: - assets/appimages/floppy.png - assets/appimages/iphone.png - assets/appimages/laptop.png - assets/appimages/pendrive.png - assets/appimages/pixel.png - assets/appimages/tablet.png
在pubspec.yaml文件中配置scoped_model包,如下所示:−
dependencies: scoped_model: ^1.0.1
这里,你应该使用最新版本的http包
Android Studio将提醒pubspec.yaml已更新。
单击获取依赖项选项。Android Studio将从Internet获取包并为应用程序正确配置它。
用我们的启动代码替换默认启动代码(main.dart)。
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // 此小部件是您的应用程序的根。 @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData(primarySwatch: Colors.blue,), home: MyHomePage(title: 'Product state demo home page'), ); } } class MyHomePage extends StatelessWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(this.title), ), body: Center( child: Text( 'Hello World', ) ), ); } }
在 main.dart 文件中导入 scoped_model 包。
import 'package:scoped_model/scoped_model.dart';
让我们创建一个 Product 类 Product.dart 来组织产品信息。
import 'package:scoped_model/scoped_model.dart'; class Product extends Model { final String name; final String description; final int price; final String image; int rating; Product(this.name, this.description, this.price, this.image, this.rating); factory Product.fromMap(Map<String, dynamic> json) { return Product( json['name'], json['description'], json['price'], json['image'], json['rating'], ); } void updateRating(int myRating) { rating = myRating; notifyListeners(); } }
在这里,我们使用了notifyListeners来在评级发生变化时更改UI。
让我们在Product类中编写一个方法getProducts来生成我们的虚拟产品记录。
static List<Product> getProducts() { List<Product> items = <Product>[]; items.add( Product( "Pixel", "Pixel is the most feature-full phone ever", 800, "pixel.png", 0 ) ); items.add( Product( "Laptop", "Laptop is most productive development tool", 2000, "laptop.png", 0 ) ); items.add( Product( "Tablet", "Tablet is the most useful device ever for meeting", 1500, "tablet.png", 0 ) ); items.add( Product( "Pendrive", "Pendrive is useful storage medium", 100, "pendrive.png", 0 ) ); items.add( Product( "Floppy Drive", "Floppy drive is useful rescue storage medium", 20, "floppy.png", 0 ) ); return items; } import product.dart in main.dart import 'Product.dart';
让我们改变我们的新小部件,RatingBox 以支持 scoped_model 概念。
class RatingBox extends StatelessWidget { RatingBox({Key key, this.item}) : super(key: key); final Product item; Widget build(BuildContext context) { double _size = 20; print(item.rating); return Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.max, children: <Widget>[ Container( padding: EdgeInsets.all(0), child: IconButton( icon: ( item.rating >= 1 ? Icon( Icons.star, size: _size, ) : Icon( Icons.star_border, size: _size, ) ), color: Colors.red[500], onPressed: () => this.item.updateRating(1), iconSize: _size, ), ), Container( padding: EdgeInsets.all(0), child: IconButton( icon: (item.rating >= 2 ? Icon( Icons.star, size: _size, ) : Icon( Icons.star_border, size: _size, ) ), color: Colors.red[500], onPressed: () => this.item.updateRating(2), iconSize: _size, ), ), Container( padding: EdgeInsets.all(0), child: IconButton( icon: ( item.rating >= 3? Icon( Icons.star, size: _size, ) : Icon( Icons.star_border, size: _size, ) ), color: Colors.red[500], onPressed: () => this.item.updateRating(3), iconSize: _size, ), ), ], ); } }
在这里,我们从 StatelessWidget 而不是 StatefulWidget 扩展了 RatingBox。此外,我们使用了 Product 模型的 updateRating 方法来设置评级。
让我们修改我们的 ProductBox 小部件以与 Product、ScopedModel 和 ScopedModelDescendant 类一起使用。
class ProductBox extends StatelessWidget { ProductBox({Key key, this.item}) : super(key: key); final Product item; Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(2), height: 140, child: Card( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Image.asset("assets/appimages/" + this.item.image), Expanded( child: Container( padding: EdgeInsets.all(5), child: ScopedModel<Product>( model: this.item, child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text(this.item.name, style: TextStyle(fontWeight: FontWeight.bold)), Text(this.item.description), Text("Price: " + this.item.price.toString()), ScopedModelDescendant<Product>( builder: (context, child, item) { return RatingBox(item: item); } ) ], ) ) ) ) ] ), ) ); } }
在这里,我们将 RatingBox 小部件包装在 ScopedModel 和 ScopedModelDecendant 中。
将 MyHomePage 小部件更改为使用我们的 ProductBox 小部件。
class MyHomePage extends StatelessWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; final items = Product.getProducts(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Product Navigation")), body: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ProductBox(item: items[index]); }, ) ); } }
这里我们使用 ListView.builder 来动态构建我们的产品列表。
该应用程序的完整代码如下 −
Product.dart import 'package:scoped_model/scoped_model.dart'; class Product extends Model { final String name; final String description; final int price; final String image; int rating; Product(this.name, this.description, this.price, this.image, this.rating); factory Product.fromMap(Map<String, dynamic> json) { return Product( json['name'], json['description'], json['price'], json['image'], json['rating'], );n } void cn "Laptop is most productive development tool", 2000, "laptop.png", 0)); items.add( Product( "Tablet"cnvn, "Tablet is the most useful device ever for meeting", 1500, "tablet.png", 0 ) ); items.add( Product( "Pendrive", "Pendrive is useful storage medium", 100, "pendrive.png", 0 ) ); items.add( Product( "Floppy Drive", "Floppy drive is useful rescue storage medium", 20, "floppy.png", 0 ) ) ; return items; } main.dart import 'package:flutter/material.dart'; import 'package:scoped_model/scoped_model.dart'; import 'Product.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Product state demo home page'), ); } } class MyHomePage extends StatelessWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; final items = Product.getProducts(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Product Navigation")), body: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ProductBox(item: items[index]); }, ) ); } } class RatingBox extends StatelessWidget { RatingBox({Key key, this.item}) : super(key: key); final Product item; Widget build(BuildContext context) { double _size = 20; print(item.rating); return Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.max, children: <Widget>[ Container( padding: EdgeInsets.all(0), child: IconButton( icon: ( item.rating >= 1? Icon( Icons.star, size: _size, ) : Icon( Icons.star_border, size: _size, ) ), color: Colors.red[500], onPressed: () => this.item.updateRating(1), iconSize: _size, ), ), Container( padding: EdgeInsets.all(0), child: IconButton( icon: (item.rating >= 2 ? Icon( Icons.star, size: _size, ) : Icon( Icons.star_border, size: _size, ) ), color: Colors.red[500], onPressed: () => this.item.updateRating(2), iconSize: _size, ), ), Container( padding: EdgeInsets.all(0), child: IconButton( icon: ( item.rating >= 3 ? Icon( Icons.star, size: _size, ) : Icon( Icons.star_border, size: _size, ) ), color: Colors.red[500], onPressed: () => this.item.updateRating(3), iconSize: _size, ), ), ], ); } } class ProductBox extends StatelessWidget { ProductBox({Key key, this.item}) : super(key: key); final Product item; Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(2), height: 140, child: Card( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Image.asset("assets/appimages/" + this.item.image), Expanded( child: Container( padding: EdgeInsets.all(5), child: ScopedModel<Product>( model: this.item, child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text( this.item.name, style: TextStyle( fontWeight: FontWeight.bold ) ), Text(this.item.description), Text("Price: " + this.item.price.toString()), ScopedModelDescendant<Product>( builder: (context, child, item) { return RatingBox(item: item); } ) ], ) ) ) ) ] ), ) ); } }
最后,编译并运行应用程序以查看其结果。它的工作原理与前面的示例类似,只是应用程序使用了 scoped_model 概念。