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和回调将变得极其繁琐和脆弱。


🌐 加入社区

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

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


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

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

相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC7 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic7 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长9 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能