Flutter for OpenHarmony二手物品置换App实战 - 商品卡片实现

商品卡片是二手交易App中最常见的UI组件,在首页、搜索结果、分类列表等多个地方都会用到。今天我们来详细讲解"闲置换"中商品卡片的实现。

商品卡片的设计思路

一个好的商品卡片应该在有限的空间内展示最重要的信息:商品图片、标题、价格、位置、发布时间。图片占主要空间吸引用户注意,价格用醒目的颜色突出显示,辅助信息用小字不抢风头。

完整代码实现

dart 复制代码
Widget _buildProductCard(Map<String, dynamic> product) {
  return GestureDetector(
    onTap: () => Get.to(() => ProductDetailPage(productId: product['id'])),
    child: Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            flex: 3,
            child: Container(
              decoration: BoxDecoration(
                borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
                color: Colors.grey[200],
              ),
              child: Center(
                child: Icon(
                  Icons.image,
                  size: 60,
                  color: Colors.grey[400],
                ),
              ),
            ),
          ),

卡片整体用GestureDetector包裹处理点击事件,点击跳转到商品详情页。外层Container设置白色背景和12像素圆角,让卡片看起来像一张卡。

Column垂直排列图片和信息区域。图片区域用Expanded并设置flex: 3,占卡片高度的大部分。只有顶部有圆角,和卡片整体圆角衔接。

这里用灰色背景加图标作为占位,实际项目要用Image.networkCachedNetworkImage加载网络图片。

dart 复制代码
          Expanded(
            flex: 2,
            child: Padding(
              padding: const EdgeInsets.all(10),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    product['title'],
                    style: const TextStyle(fontSize: 14),
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const Spacer(),
                  Row(
                    children: [
                      Text(
                        '¥${product['price'].toStringAsFixed(0)}',
                        style: const TextStyle(
                          color: Color(0xFFFF4D4F),
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(width: 4),
                      Text(
                        '¥${product['originalPrice'].toStringAsFixed(0)}',
                        style: const TextStyle(
                          color: Colors.grey,
                          fontSize: 12,
                          decoration: TextDecoration.lineThrough,
                        ),
                      ),
                    ],
                  ),

信息区域flex: 2,占剩余空间。Padding给内容加10像素内边距,不会贴着边缘。

商品标题最多两行,超出显示省略号。Spacer把标题和价格撑开,价格始终靠近底部。

售价用红色0xFFFF4D4F粗体突出显示,这是电商App的通用做法,红色能吸引用户注意力。原价用灰色小字加删除线,让用户直观感受到折扣力度。toStringAsFixed(0)把价格格式化成整数,去掉小数点。

dart 复制代码
                  const SizedBox(height: 4),
                  Row(
                    children: [
                      const Icon(Icons.location_on, size: 12, color: Colors.grey),
                      const SizedBox(width: 2),
                      Expanded(
                        child: Text(
                          product['location'],
                          style: const TextStyle(color: Colors.grey, fontSize: 10),
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      Text(
                        product['time'],
                        style: const TextStyle(color: Colors.grey, fontSize: 10),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    ),
  );
}

卡片底部显示位置和发布时间。位置前面加个定位图标,用Expanded让位置文字自适应宽度,太长就显示省略号。发布时间放右边,比如"3小时前"。

这些辅助信息用灰色10号小字,不抢主要信息的风头。整个卡片信息层次分明:图片最大、标题次之、价格醒目、辅助信息最小。

加载网络图片

实际项目中图片区域应该这样实现:

dart 复制代码
Expanded(
  flex: 3,
  child: ClipRRect(
    borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
    child: CachedNetworkImage(
      imageUrl: product['image'],
      fit: BoxFit.cover,
      width: double.infinity,
      placeholder: (context, url) => Container(
        color: Colors.grey[200],
        child: const Center(child: CircularProgressIndicator()),
      ),
      errorWidget: (context, url, error) => Container(
        color: Colors.grey[200],
        child: Icon(Icons.image, size: 60, color: Colors.grey[400]),
      ),
    ),
  ),
)

CachedNetworkImage来自cached_network_image包,会自动缓存图片,下次加载更快。placeholder是加载中显示的内容,errorWidget是加载失败显示的内容。

ClipRRect裁剪图片让它有圆角,fit: BoxFit.cover让图片填满容器并保持比例。

添加收藏按钮

可以在图片右上角加一个收藏按钮:

dart 复制代码
Expanded(
  flex: 3,
  child: Stack(
    children: [
      // 图片
      Positioned.fill(
        child: ClipRRect(
          borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
          child: CachedNetworkImage(
            imageUrl: product['image'],
            fit: BoxFit.cover,
          ),
        ),
      ),
      // 收藏按钮
      Positioned(
        top: 8,
        right: 8,
        child: GestureDetector(
          onTap: () => _toggleFavorite(product),
          child: Container(
            padding: const EdgeInsets.all(4),
            decoration: BoxDecoration(
              color: Colors.black.withOpacity(0.3),
              shape: BoxShape.circle,
            ),
            child: Icon(
              product['isFavorite'] ? Icons.favorite : Icons.favorite_border,
              color: product['isFavorite'] ? Colors.red : Colors.white,
              size: 16,
            ),
          ),
        ),
      ),
    ],
  ),
)

Stack叠加收藏按钮在图片上,半透明黑色背景让按钮在任何图片上都清晰可见。收藏后图标变成实心红色爱心。

添加标签

可以在图片左上角加标签,比如"急售"、"包邮":

dart 复制代码
Positioned(
  top: 8,
  left: 8,
  child: Container(
    padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
    decoration: BoxDecoration(
      color: const Color(0xFFFF4D4F),
      borderRadius: BorderRadius.circular(4),
    ),
    child: const Text(
      '急售',
      style: TextStyle(color: Colors.white, fontSize: 10),
    ),
  ),
)

红色背景白色文字,非常醒目。可以根据商品属性动态显示不同的标签。

封装成独立组件

商品卡片在多个地方使用,应该封装成独立组件:

dart 复制代码
class ProductCard extends StatelessWidget {
  final Map<String, dynamic> product;
  final VoidCallback? onTap;
  final VoidCallback? onFavorite;

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

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(12),
        ),
        child: // ... 卡片内容
      ),
    );
  }
}

使用时:

dart 复制代码
ProductCard(
  product: products[index],
  onTap: () => Get.to(() => ProductDetailPage(productId: products[index]['id'])),
  onFavorite: () => _toggleFavorite(products[index]),
)

封装后代码更清晰,修改卡片样式只需要改一个地方。

不同尺寸的卡片

有时候需要不同尺寸的卡片,比如首页推荐用大卡片,列表用小卡片:

dart 复制代码
class ProductCard extends StatelessWidget {
  final Map<String, dynamic> product;
  final ProductCardSize size;

  const ProductCard({
    super.key,
    required this.product,
    this.size = ProductCardSize.medium,
  });

  @override
  Widget build(BuildContext context) {
    switch (size) {
      case ProductCardSize.small:
        return _buildSmallCard();
      case ProductCardSize.medium:
        return _buildMediumCard();
      case ProductCardSize.large:
        return _buildLargeCard();
    }
  }
}

enum ProductCardSize { small, medium, large }

根据size参数返回不同样式的卡片。

小结

这篇详细讲解了"闲置换"App中商品卡片的实现,包括图片展示、标题价格、位置时间等信息的布局。还介绍了加载网络图片、添加收藏按钮、添加标签、封装组件等进阶内容。商品卡片是电商App最重要的UI组件,设计好了能显著提升用户体验。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
时光慢煮3 小时前
基于 Flutter × OpenHarmony 的文件管家 - 构建常用文件夹区域
flutter·华为·开源·openharmony
2601_949575864 小时前
Flutter for OpenHarmony二手物品置换App实战 - 表单验证实现
android·java·flutter
b2077214 小时前
Flutter for OpenHarmony 身体健康状况记录App实战 - 健康目标实现
python·flutter·harmonyos
龚礼鹏5 小时前
图像显示框架八——BufferQueue与BLASTBufferQueue(基于android 15源码分析)
android·c语言
血色橄榄枝6 小时前
04-06 Flutter列表清单实现上拉加载 + 下拉刷新 + 数据加载提示 On OpenHarmony
flutter
小风呼呼吹儿6 小时前
Flutter 框架跨平台鸿蒙开发 - 书法印章制作记录应用开发教程
flutter·华为·harmonyos
1登峰造极6 小时前
uniapp 运行安卓报错reportJSException >>>> exception function:createInstanceContext, exception:white screen
android·java·uni-app
木易 士心6 小时前
Android Handler 机制原理详解
android
kkk_皮蛋6 小时前
作为一个学生,如何用免费 AI 工具手搓了一款 Android AI 日记 App
android·人工智能