Flutter - 应用状态



应用程序状态 - scoped_model

Flutter 提供了一种简单的方法来使用 scoped_model 包管理应用程序的状态。Flutter 包仅仅是可重用功能的库。我们将在接下来的章节中详细了解 Flutter 包。

scoped_model 提供了三个主要的类来支持应用程序中强大的状态管理,这里将详细讨论它们:

模型

模型封装了应用程序的状态。我们可以根据需要使用任意多个模型(通过继承 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 是一个 Widget,它持有给定的模型,然后在请求时将其传递给所有后代 Widget。如果需要多个模型,则需要嵌套 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 是一个 Widget,它从上层 Widget 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

  • 将默认的启动代码 (main.dart) 替换为我们的 product_state_app 代码

  • 将 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 已更新。

PubSpec YAML
  • 单击“获取依赖项”选项。Android Studio 将从互联网获取包并为应用程序正确配置它。

  • 将默认的启动代码 (main.dart) 替换为我们的启动代码。

import 'package:flutter/material.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; 
   
   @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';
  • 让我们更改新的 Widget 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 Widget 以与 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 Widget 包含在 ScopedModel 和 ScopedModelDescendant 中。

  • 更改 MyHomePage Widget 以使用我们的 ProductBox Widget。

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 概念。

flutter_state_management.htm
广告