Flutter for OpenHarmony:注入灵魂:购物车的数据驱动与状态管理实战

Flutter for OpenHarmony:注入灵魂:购物车的数据驱动与状态管理实战

引言

在上一篇文章中,我们成功构建了一个视觉上令人满意的购物APP骨架。然而,一个真正的应用必须能够响应用户输入并动态更新其状态。想象一下,当用户在购物车中增减商品数量时,总价却纹丝不动,这将是多么糟糕的体验!这就是状态管理(State
Management)
的用武之地。

本文将承接上一篇的成果,深入探讨如何为我们的"淘淘购物"APP注入灵魂。我们将重点关注两个页面:"分类页"和"购物车页"。前者将展示如何组织和渲染动态数据;后者则是状态管理的经典战场,我们将一步步剖析问题,并找到优雅的解决方案。
完整效果展示

完整代码展示

dart 复制代码
import 'package:flutter/material.dart';

void main() {
  runApp(const ShoppingApp());
}

class ShoppingApp extends StatelessWidget {
  const ShoppingApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '淘淘购物',
      theme: ThemeData(
        primaryColor: Colors.orange,
        scaffoldBackgroundColor: Colors.grey[100],
        useMaterial3: true,
      ),
      home: const MainScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MainScreen extends StatefulWidget {
  const MainScreen({super.key});

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int _currentIndex = 0;
  final List<Widget> _screens = [
    const HomeScreen(),
    const CategoryScreen(),
    const CartScreen(),
    const ProfileScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _screens[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        type: BottomNavigationBarType.fixed,
        selectedItemColor: Colors.orange,
        unselectedItemColor: Colors.grey,
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.category),
            label: '分类',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart),
            label: '购物车',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

// 首页
class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('淘淘购物'),
        backgroundColor: Colors.orange,
        elevation: 0,
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () {},
          ),
          IconButton(
            icon: const Icon(Icons.message),
            onPressed: () {},
          ),
        ],
      ),
      body: ListView(
        children: [
          // 搜索框
          Container(
            padding: const EdgeInsets.all(10),
            color: Colors.orange,
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 15),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(20),
              ),
              child: const TextField(
                decoration: InputDecoration(
                  hintText: '搜索宝贝',
                  border: InputBorder.none,
                  prefixIcon: Icon(Icons.search, color: Colors.grey),
                ),
              ),
            ),
          ),

          // 轮播图区域
          Container(
            height: 150,
            margin: const EdgeInsets.all(10),
            decoration: BoxDecoration(
              color: Colors.orange[300],
              borderRadius: BorderRadius.circular(10),
            ),
            child: const Center(
              child: Text(
                '轮播图区域',
                style: TextStyle(color: Colors.white, fontSize: 20),
              ),
            ),
          ),

          // 快捷入口
          Container(
            padding: const EdgeInsets.all(15),
            color: Colors.white,
            child: GridView.count(
              crossAxisCount: 5,
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              children: const [
                QuickEntry(Icons.shopping_bag, '天猫'),
                QuickEntry(Icons.card_giftcard, '聚划算'),
                QuickEntry(Icons.local_offer, '优惠券'),
                QuickEntry(Icons.phone_android, '数码'),
                QuickEntry(Icons.style, '服饰'),
                QuickEntry(Icons.home, '家居'),
                QuickEntry(Icons.restaurant, '美食'),
                QuickEntry(Icons.flight, '旅行'),
                QuickEntry(Icons.sports_basketball, '运动'),
                QuickEntry(Icons.book, '图书'),
              ],
            ),
          ),

          const SizedBox(height: 10),

          // 推荐商品
          Container(
            color: Colors.white,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Padding(
                  padding: EdgeInsets.all(15),
                  child: Text(
                    '为你推荐',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                GridView.builder(
                  shrinkWrap: true,
                  physics: const NeverScrollableScrollPhysics(),
                  padding: const EdgeInsets.all(10),
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                    childAspectRatio: 0.75,
                    crossAxisSpacing: 10,
                    mainAxisSpacing: 10,
                  ),
                  itemCount: _recommendedProducts.length,
                  itemBuilder: (context, index) {
                    return ProductCard(product: _recommendedProducts[index]);
                  },
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 快捷入口组件
class QuickEntry extends StatelessWidget {
  final IconData icon;
  final String label;

  const QuickEntry(this.icon, this.label, {super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Icon(icon, color: Colors.orange, size: 30),
        const SizedBox(height: 5),
        Text(label, style: const TextStyle(fontSize: 12)),
      ],
    );
  }
}

// 分类页
class CategoryScreen extends StatelessWidget {
  const CategoryScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('商品分类'),
        backgroundColor: Colors.orange,
      ),
      body: Row(
        children: [
          // 左侧分类列表
          Container(
            width: 100,
            color: Colors.grey[200],
            child: ListView.builder(
              itemCount: _categories.length,
              itemBuilder: (context, index) {
                return Container(
                  padding: const EdgeInsets.symmetric(vertical: 20),
                  decoration: BoxDecoration(
                    color: index == 0 ? Colors.white : null,
                    border: index == 0
                        ? const Border(
                            left: BorderSide(color: Colors.orange, width: 3))
                        : null,
                  ),
                  child: Center(
                    child: Text(
                      _categories[index],
                      style: TextStyle(
                        color: index == 0 ? Colors.orange : Colors.black87,
                        fontWeight:
                            index == 0 ? FontWeight.bold : FontWeight.normal,
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
          // 右侧商品列表
          Expanded(
            child: GridView.builder(
              padding: const EdgeInsets.all(10),
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 0.8,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
              ),
              itemCount: _subCategories.length,
              itemBuilder: (context, index) {
                return SubCategoryCard(category: _subCategories[index]);
              },
            ),
          ),
        ],
      ),
    );
  }
}

// 子分类卡片
class SubCategoryCard extends StatelessWidget {
  final String category;

  const SubCategoryCard({super.key, required this.category});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: 60,
          height: 60,
          decoration: BoxDecoration(
            color: Colors.orange[100],
            borderRadius: BorderRadius.circular(10),
          ),
          child: const Icon(Icons.image, color: Colors.orange, size: 30),
        ),
        const SizedBox(height: 8),
        Text(
          category,
          style: const TextStyle(fontSize: 12),
          textAlign: TextAlign.center,
        ),
      ],
    );
  }
}

// 购物车页
class CartScreen extends StatefulWidget {
  const CartScreen({super.key});

  @override
  State<CartScreen> createState() => _CartScreenState();
}

class _CartScreenState extends State<CartScreen> {
  double _totalPrice = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('购物车'),
        backgroundColor: Colors.orange,
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: _cartItems.length,
              itemBuilder: (context, index) {
                return CartItemCard(
                  item: _cartItems[index],
                  onQuantityChanged: (quantity) {
                    setState(() {
                      _totalPrice = _calculateTotal();
                    });
                  },
                );
              },
            ),
          ),
          // 底部结算栏
          Container(
            padding: const EdgeInsets.all(15),
            decoration: BoxDecoration(
              color: Colors.white,
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(0.3),
                  blurRadius: 5,
                  offset: const Offset(0, -2),
                ),
              ],
            ),
            child: Row(
              children: [
                const Text(
                  '合计: ',
                  style: TextStyle(fontSize: 16),
                ),
                Text(
                  '¥$_totalPrice',
                  style: const TextStyle(
                    fontSize: 24,
                    color: Colors.orange,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const Spacer(),
                ElevatedButton(
                  onPressed: () {},
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.orange,
                    padding: const EdgeInsets.symmetric(
                      horizontal: 40,
                      vertical: 12,
                    ),
                  ),
                  child: const Text('结算', style: TextStyle(fontSize: 16)),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  double _calculateTotal() {
    return _cartItems.fold(
        0.0, (sum, item) => sum + item.price * item.quantity);
  }
}

// 我的页面
class ProfileScreen extends StatelessWidget {
  const ProfileScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('我的淘淘'),
        backgroundColor: Colors.orange,
      ),
      body: ListView(
        children: [
          // 用户信息
          Container(
            padding: const EdgeInsets.all(20),
            color: Colors.orange,
            child: Column(
              children: [
                Container(
                  width: 80,
                  height: 80,
                  decoration: const BoxDecoration(
                    color: Colors.white,
                    shape: BoxShape.circle,
                  ),
                  child:
                      const Icon(Icons.person, size: 50, color: Colors.orange),
                ),
                const SizedBox(height: 10),
                const Text(
                  '用户昵称',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 20,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 5),
                Container(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
                  decoration: BoxDecoration(
                    border: Border.all(color: Colors.white),
                    borderRadius: BorderRadius.circular(15),
                  ),
                  child: const Text(
                    '登录/注册',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ],
            ),
          ),

          const SizedBox(height: 10),

          // 订单状态
          Container(
            padding: const EdgeInsets.all(15),
            color: Colors.white,
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text(
                      '我的订单',
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Row(
                      children: const [
                        Text('全部订单', style: TextStyle(color: Colors.grey)),
                        Icon(Icons.chevron_right, color: Colors.grey),
                      ],
                    ),
                  ],
                ),
                const SizedBox(height: 15),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: const [
                    OrderStatusIcon(Icons.payment, '待付款'),
                    OrderStatusIcon(Icons.inventory, '待发货'),
                    OrderStatusIcon(Icons.local_shipping, '待收货'),
                    OrderStatusIcon(Icons.star, '待评价'),
                    OrderStatusIcon(Icons.replay, '退款/售后'),
                  ],
                ),
              ],
            ),
          ),

          const SizedBox(height: 10),

          // 功能列表
          Container(
            color: Colors.white,
            child: Column(
              children: const [
                ListTile(
                  leading: Icon(Icons.favorite_border, color: Colors.orange),
                  title: Text('我的收藏'),
                  trailing: Icon(Icons.chevron_right),
                ),
                Divider(height: 1),
                ListTile(
                  leading: Icon(Icons.location_on, color: Colors.orange),
                  title: Text('收货地址'),
                  trailing: Icon(Icons.chevron_right),
                ),
                Divider(height: 1),
                ListTile(
                  leading: Icon(Icons.help_outline, color: Colors.orange),
                  title: Text('联系客服'),
                  trailing: Icon(Icons.chevron_right),
                ),
                Divider(height: 1),
                ListTile(
                  leading: Icon(Icons.settings, color: Colors.orange),
                  title: Text('设置'),
                  trailing: Icon(Icons.chevron_right),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 订单状态图标
class OrderStatusIcon extends StatelessWidget {
  final IconData icon;
  final String label;

  const OrderStatusIcon(this.icon, this.label, {super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Icon(icon, color: Colors.orange, size: 28),
        const SizedBox(height: 5),
        Text(label, style: const TextStyle(fontSize: 12)),
      ],
    );
  }
}

// 商品卡片
class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({super.key, required this.product});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.2),
            blurRadius: 5,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Container(
              decoration: BoxDecoration(
                color: Colors.orange[100],
                borderRadius:
                    const BorderRadius.vertical(top: Radius.circular(10)),
              ),
              child: const Center(
                child: Icon(Icons.image, color: Colors.orange, size: 50),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  product.name,
                  style: const TextStyle(fontSize: 14),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 5),
                Row(
                  children: [
                    Text(
                      '¥${product.price.toStringAsFixed(2)}',
                      style: const TextStyle(
                        color: Colors.orange,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(width: 5),
                    Text(
                      '${product.sales}人付款',
                      style: const TextStyle(fontSize: 12, color: Colors.grey),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 购物车商品卡片
class CartItemCard extends StatefulWidget {
  final CartItem item;
  final Function(int) onQuantityChanged;

  const CartItemCard({
    super.key,
    required this.item,
    required this.onQuantityChanged,
  });

  @override
  State<CartItemCard> createState() => _CartItemCardState();
}

class _CartItemCardState extends State<CartItemCard> {
  bool _isChecked = true;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(10),
      padding: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.2),
            blurRadius: 5,
            offset: const Offset(0, 2),
          ),
        ],
      ),
      child: Row(
        children: [
          Checkbox(
            value: _isChecked,
            onChanged: (value) {
              setState(() {
                _isChecked = value ?? false;
              });
            },
          ),
          Container(
            width: 80,
            height: 80,
            decoration: BoxDecoration(
              color: Colors.orange[100],
              borderRadius: BorderRadius.circular(5),
            ),
            child: const Icon(Icons.image, color: Colors.orange),
          ),
          const SizedBox(width: 10),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  widget.item.name,
                  style: const TextStyle(fontSize: 14),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 10),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      '¥${widget.item.price.toStringAsFixed(2)}',
                      style: const TextStyle(
                        color: Colors.orange,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Row(
                      children: [
                        IconButton(
                          icon: const Icon(Icons.remove, size: 18),
                          onPressed: () {
                            if (widget.item.quantity > 1) {
                              widget.item.quantity--;
                              widget.onQuantityChanged(widget.item.quantity);
                            }
                          },
                        ),
                        Text('${widget.item.quantity}'),
                        IconButton(
                          icon: const Icon(Icons.add, size: 18),
                          onPressed: () {
                            widget.item.quantity++;
                            widget.onQuantityChanged(widget.item.quantity);
                          },
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// 数据模型
class Product {
  final String name;
  final double price;
  final int sales;

  Product({required this.name, required this.price, required this.sales});
}

class CartItem {
  final String name;
  final double price;
  int quantity;

  CartItem({
    required this.name,
    required this.price,
    this.quantity = 1,
  });
}

// 数据
final List<Product> _recommendedProducts = [
  Product(name: '新款智能手机', price: 2999.00, sales: 1000),
  Product(name: '无线蓝牙耳机', price: 199.00, sales: 5000),
  Product(name: '智能手表', price: 899.00, sales: 2000),
  Product(name: '运动鞋', price: 399.00, sales: 3000),
  Product(name: '男士休闲裤', price: 159.00, sales: 1500),
  Product(name: '女士连衣裙', price: 299.00, sales: 2500),
];

final List<String> _categories = [
  '热门推荐',
  '手机数码',
  '家用电器',
  '电脑办公',
  '家居家装',
  '服装服饰',
  '鞋靴箱包',
  '运动户外',
  '美妆个护',
  '食品生鲜',
];

final List<String> _subCategories = [
  '手机',
  '平板',
  '耳机',
  '充电宝',
  '数据线',
  '保护壳',
  '笔记本',
  '键盘',
  '鼠标',
  '显示器',
  '音响',
  '耳机',
  '冰箱',
  '洗衣机',
  '空调',
  '电视',
  '微波炉',
  '电饭煲',
];

final List<CartItem> _cartItems = [
  CartItem(name: '新款智能手机', price: 2999.00, quantity: 1),
  CartItem(name: '无线蓝牙耳机', price: 199.00, quantity: 2),
  CartItem(name: '智能手表', price: 899.00, quantity: 1),
];

第一步:定义数据模型------一切的起点

在开始处理UI逻辑之前,我们必须先定义好数据的结构。这是良好编程习惯的体现,也能让后续的开发事半功倍。

我们定义两个核心数据模型:

  1. Product: 代表一个商品,包含名称、价格和销量。
  2. CartItem : 代表购物车中的一项,除了商品信息外,还包含一个可变的quantity(数量)。
dart 复制代码
// models/product.dart & models/cart_item.dart
class Product {
  final String name;
  final double price;
  final int sales;
  // ... 构造函数
}

class CartItem {
  final String name;
  final double price;
  int quantity; // 注意:这里是var,因为数量会变

  CartItem({required this.name, required this.price, this.quantity = 1});
}

同时,我们在一个单独的data.dart文件中定义一些模拟数据(Mock Data),以便在没有后端的情况下进行开发。

dart 复制代码
// data/mock_data.dart
final List<Product> _recommendedProducts = [ /* ... */ ];
final List<String> _categories = [ /* ... */ ];
final List<CartItem> _cartItems = [
  CartItem(name: '新款智能手机', price: 2999.00, quantity: 1),
  // ...
];

第二步:让"分类页"活起来

"分类页"的左侧是一个垂直的分类列表,右侧是对应子分类的网格。虽然目前我们只是静态展示,但其结构已经为未来的动态数据加载做好了准备。

  • 左侧列表 :通过ListView.builder动态生成,itemCount绑定到_categories.length。当未来从API获取分类数据时,只需替换_categories列表即可。
  • 右侧网格 :同理,使用GridView.builderitemCount绑定到_subCategories.length

这里的SubCategoryCard组件接收一个String类型的category,未来可以轻松地将其升级为接收一个完整的SubCategory对象,以展示更多细节(如图片、商品数量等)。这种基于数据驱动的UI构建方式,使得我们的代码具有极强的扩展性。

第三步:攻克堡垒------购物车的状态管理

购物车是本文的核心。其难点在于:当任何一个商品的数量发生变化时,整个购物车的总价都必须立即、准确地更新。这是一个典型的父子组件通信和局部状态更新问题。

问题分析

在原始代码中,CartScreen是一个StatefulWidget,它持有一个_totalPrice状态。CartItemCard是它的子组件,负责渲染单个商品项及其数量控制器(+/-按钮)。

  • CartItemCard 知道自己的数量何时改变。
  • CartScreen 知道如何计算总价,但不知道子组件内部发生了什么。

因此,我们需要建立一种通信机制,让子组件能够通知父组件"我的数量变了,请重新计算总价"。

解决方案:回调函数(Callback)

这是Flutter中最常用、最直接的父子通信方式。我们在CartItemCard的构造函数中定义一个onQuantityChanged回调。

dart 复制代码
// cart_item_card.dart
class CartItemCard extends StatefulWidget {
  final CartItem item;
  final Function(int) onQuantityChanged; // 定义回调

  const CartItemCard({super.key, required this.item, required this.onQuantityChanged});
  // ...
}

CartItemCard的内部,当用户点击"+"或"-"按钮时,我们不仅更新自己的quantity,还会立刻调用这个回调函数,并将新的数量作为参数传递出去。

dart 复制代码
// 在_CartItemCardState中
IconButton(
  icon: const Icon(Icons.add, size: 18),
  onPressed: () {
    widget.item.quantity++; // 更新数据
    widget.onQuantityChanged(widget.item.quantity); // 通知父组件
  },
),
父组件的响应

CartScreen在构建CartItemCard时,传入一个具体的回调函数。这个函数的作用就是调用setState,触发自身状态的更新,从而重新计算总价并刷新UI。

dart 复制代码
// cart_screen.dart
ListView.builder(
  itemCount: _cartItems.length,
  itemBuilder: (context, index) {
    return CartItemCard(
      item: _cartItems[index],
      onQuantityChanged: (quantity) {
        // 当任意子项数量变化时,重新计算总价
        setState(() {
          _totalPrice = _calculateTotal();
        });
      },
    );
  },
),

_calculateTotal方法遍历所有购物车项,累加price * quantity,得到最终的总价。

dart 复制代码
double _calculateTotal() {
  return _cartItems.fold(0.0, (sum, item) => sum + item.price * item.quantity);
}
为什么这个方案有效?
  1. 职责分离CartItemCard只关心自己的UI和数量逻辑;CartScreen只关心总价的计算和展示。两者通过清晰的接口(回调)进行通信。
  2. 性能可控 :每次只更新必要的状态(_totalPrice),Flutter的setState会智能地只重绘受影响的部分,而不是整个页面。
  3. 简单直接:对于这种层级不深、逻辑不复杂的场景,回调函数是最轻量、最容易理解的方案。

第四步:超越基础------Checkbox与全选逻辑

一个成熟的购物车还应支持商品的勾选与全选功能。我们可以为CartItemCard添加一个Checkbox,并为其增加一个onCheckedChanged回调。

CartScreen中,我们可以维护一个List<bool>来记录每个商品的选中状态,或者更进一步,直接在CartItem模型中增加一个isSelected字段。当任一商品的选中状态改变,或者点击了"全选"按钮时,我们同样可以通过setState来更新UI,并在计算总价时只累加isSelected == true的商品。

这进一步证明了我们当前架构的灵活性:通过在模型中增加字段,并在父子组件间传递相应的回调,就能轻松扩展功能。

总结与反思

本文我们成功地将静态UI转变为动态、可交互的应用。核心收获如下:

  1. 数据驱动UI:UI是数据的可视化表现。定义清晰的数据模型是开发的第一步。
  2. 状态管理入门 :对于简单的父子组件通信,回调函数(Callback) 是最有效、最直观的工具。
  3. setState的威力与局限setState是Flutter状态管理的基石,适用于小范围、局部的状态更新。然而,当应用变得庞大,状态散落在各个StatefulWidget中时,管理和追踪状态变更将变得异常困难。

试想,如果我们的APP增加了"收藏夹"功能,收藏夹里的商品价格变动需要同步到购物车;或者用户在"首页"将商品加入购物车,需要实时更新"购物车"Tab上的小红点数量。这些跨组件、甚至跨页面的状态同步,仅靠setState和回调将变得极其繁琐和脆弱。


🌐 加入社区

欢迎加入 开源鸿蒙跨平台开发者社区 ,获取最新资源与技术支持:

👉 开源鸿蒙跨平台开发者社区


技术因分享而进步,生态因共建而繁荣

------ 晚霞的不甘 · 与您共赴鸿蒙跨平台开发之旅

相关推荐
福大大架构师每日一题2 小时前
milvus v2.6.9 发布:支持主键搜索、段重开机制、日志性能全面提升!
android·java·milvus
skywalk81632 小时前
cbsd的clonos/control-pane web管理页面一直闪烁和网页打开显示500error 的问题解决(500error问题未解决)
服务器·前端·freebsd·cbsd
weixin_436525072 小时前
若依多租户版 - modules中创建子模块
java·服务器·前端
好奇的菜鸟2 小时前
使用 Vite 快速创建 React + TypeScript 项目全记录
前端·react.js·typescript
*小雪2 小时前
nvm的安装与管理和npm audit的报错解决
前端·npm·node.js
Marshmallowc2 小时前
React useState 数据不同步?深度解析无限滚动中的“闭包陷阱”与异步更新丢失问题
前端·javascript·react.js·闭包·fiber架构
某柚啊2 小时前
解决 minimatch 类型报错问题
前端·webpack·npm
鸣弦artha2 小时前
Flutter框架跨平台鸿蒙开发——GridView基础入门
flutter·华为·harmonyos
前端 贾公子2 小时前
npm 发包配置双重身份验证
前端·javascript·微信小程序·小程序·github