鸿蒙跨端框架Flutter学习:ListView卡片样式详解

ListView卡片样式详解

概述

在ListView中使用Card作为列表项是一种常见且美观的设计模式。Card组件提供了圆角、阴影、背景色等装饰效果,能够提升列表的视觉层次和用户体验。

Card样式的优势

|| 特性 | 普通ListTile | Card样式 |

|------|---------------|----------|

| 视觉层次 | 平淡 | 立体感强 |

| 分隔效果 | 需要Divider | 自然分隔 |

| 装饰能力 | 有限 | 丰富 |

| 性能 | 较好 | 稍差 |

| 适用场景 | 简单列表 | 内容丰富的列表 |

基础Card样式

1. 简单Card列表

dart 复制代码
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return Card(
      margin: const EdgeInsets.all(8),
      child: ListTile(
        title: Text(items[index]),
      ),
    );
  },
)

2. 带间距的Card

dart 复制代码
ListView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: items.length,
  itemBuilder: (context, index) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Text(items[index]),
      ),
    );
  },
)

实际应用场景

场景1:产品卡片列表

dart 复制代码
class ProductList extends StatelessWidget {
  final List<Map<String, dynamic>> products = [
    {'name': '智能手机', 'price': 5999, 'desc': '最新旗舰机型', 'icon': Icons.phone_android, 'color': Colors.blue},
    {'name': '笔记本电脑', 'price': 8999, 'desc': '高性能办公本', 'icon': Icons.laptop, 'color': Colors.green},
    {'name': '平板电脑', 'price': 3999, 'desc': '轻薄便携', 'icon': Icons.tablet, 'color': Colors.orange},
    {'name': '智能手表', 'price': 1999, 'desc': '健康监测', 'icon': Icons.watch, 'color': Colors.purple},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('产品列表')),
      body: ListView.builder(
        padding: const EdgeInsets.all(12),
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return Card(
            elevation: 4,
            margin: const EdgeInsets.only(bottom: 16),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(16),
            ),
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                children: [
                  Container(
                    padding: const EdgeInsets.all(16),
                    decoration: BoxDecoration(
                      color: (product['color'] as Color).withOpacity(0.1),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Icon(
                      product['icon'] as IconData,
                      size: 48,
                      color: product['color'] as Color,
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          product['name'] as String,
                          style: const TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 4),
                        Text(
                          product['desc'] as String,
                          style: const TextStyle(
                            fontSize: 14,
                            color: Colors.grey,
                          ),
                        ),
                        const SizedBox(height: 8),
                        Text(
                          '¥${product['price']}',
                          style: TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.bold,
                            color: Colors.red[700],
                          ),
                        ),
                      ],
                    ),
                  ),
                  IconButton(
                    icon: const Icon(Icons.add_shopping_cart),
                    onPressed: () {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('已加入购物车:${product['name']}')),
                      );
                    },
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

场景2:文章卡片列表

dart 复制代码
class ArticleList extends StatelessWidget {
  final List<Map<String, dynamic>> articles = [
    {
      'title': 'Flutter开发入门指南',
      'author': '张三',
      'date': '2024-01-15',
      'views': 1234,
      'likes': 56,
      'image': '📱',
    },
    {
      'title': 'Dart语言高级特性解析',
      'author': '李四',
      'date': '2024-01-14',
      'views': 2345,
      'likes': 89,
      'image': '🎯',
    },
    {
      'title': 'HarmonyOS跨平台开发实践',
      'author': '王五',
      'date': '2024-01-13',
      'views': 3456,
      'likes': 123,
      'image': '🚀',
    },
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('文章列表')),
      body: ListView.builder(
        padding: const EdgeInsets.all(12),
        itemCount: articles.length,
        itemBuilder: (context, index) {
          final article = articles[index];
          return Card(
            margin: const EdgeInsets.only(bottom: 16),
            clipBehavior: Clip.antiAlias,
            child: InkWell(
              onTap: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('查看文章:${article['title']}')),
                );
              },
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // 图片区域
                  Container(
                    height: 150,
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        colors: [
                          Colors.primaries[index % Colors.primaries.length].shade300,
                          Colors.primaries[index % Colors.primaries.length].shade500,
                        ],
                      ),
                    ),
                    child: Center(
                      child: Text(
                        article['image'] as String,
                        style: const TextStyle(fontSize: 64),
                      ),
                    ),
                  ),
                  // 内容区域
                  Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          article['title'] as String,
                          style: const TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            const Icon(Icons.person, size: 16),
                            const SizedBox(width: 4),
                            Text(article['author'] as String),
                            const SizedBox(width: 16),
                            const Icon(Icons.calendar_today, size: 16),
                            const SizedBox(width: 4),
                            Text(article['date'] as String),
                          ],
                        ),
                        const SizedBox(height: 12),
                        Row(
                          children: [
                            Row(
                              children: [
                                const Icon(Icons.visibility, size: 16),
                                const SizedBox(width: 4),
                                Text('${article['views']}'),
                              ],
                            ),
                            const SizedBox(width: 24),
                            Row(
                              children: [
                                const Icon(Icons.thumb_up, size: 16),
                                const SizedBox(width: 4),
                                Text('${article['likes']}'),
                              ],
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

场景3:任务卡片列表

dart 复制代码
class TaskList extends StatefulWidget {
  @override
  _TaskListState createState() => _TaskListState();
}

class _TaskListState extends State<TaskList> {
  final List<Map<String, dynamic>> tasks = [
    {'title': '完成项目文档', 'priority': '高', 'status': '进行中', 'color': Colors.red},
    {'title': '代码审查', 'priority': '中', 'status': '待处理', 'color': Colors.orange},
    {'title': '更新测试用例', 'priority': '低', 'status': '已完成', 'color': Colors.green},
    {'title': '性能优化', 'priority': '高', 'status': '进行中', 'color': Colors.blue},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('任务列表'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: ListView.builder(
        padding: const EdgeInsets.all(12),
        itemCount: tasks.length,
        itemBuilder: (context, index) {
          final task = tasks[index];
          final color = task['color'] as Color;
          return Card(
            elevation: 2,
            margin: const EdgeInsets.only(bottom: 12),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
              side: BorderSide(
                color: color.withOpacity(0.5),
                width: 2,
              ),
            ),
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: color,
                          borderRadius: BorderRadius.circular(12),
                        ),
                        child: Text(
                          task['priority'] as String,
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 12,
                          ),
                        ),
                      ),
                      const Spacer(),
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: color.withOpacity(0.2),
                          borderRadius: BorderRadius.circular(12),
                        ),
                        child: Text(
                          task['status'] as String,
                          style: TextStyle(
                            color: color,
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Text(
                    task['title'] as String,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      _buildActionButton(
                        icon: Icons.play_arrow,
                        label: '开始',
                        color: Colors.green,
                        onTap: () {},
                      ),
                      const SizedBox(width: 8),
                      _buildActionButton(
                        icon: Icons.check,
                        label: '完成',
                        color: Colors.blue,
                        onTap: () {},
                      ),
                      const SizedBox(width: 8),
                      _buildActionButton(
                        icon: Icons.delete,
                        label: '删除',
                        color: Colors.red,
                        onTap: () {},
                      ),
                    ],
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  Widget _buildActionButton({
    required IconData icon,
    required String label,
    required Color color,
    required VoidCallback onTap,
  }) {
    return Expanded(
      child: OutlinedButton.icon(
        icon: Icon(icon, size: 18),
        label: Text(label),
        onPressed: onTap,
        style: OutlinedButton.styleFrom(
          foregroundColor: color,
          side: BorderSide(color: color),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(8),
          ),
        ),
      ),
    );
  }
}

场景4:音乐卡片列表

dart 复制代码
class MusicList extends StatelessWidget {
  final List<Map<String, dynamic>> songs = [
    {'title': '夜曲', 'artist': '周杰伦', 'album': '十一月的萧邦', 'duration': '3:45', 'cover': '🎵'},
    {'title': '晴天', 'artist': '周杰伦', 'album': '叶惠美', 'duration': '4:29', 'cover': '🎵'},
    {'title': '稻香', 'artist': '周杰伦', 'album': '魔杰座', 'duration': '3:42', 'cover': '🎵'},
    {'title': '青花瓷', 'artist': '周杰伦', 'album': '我很忙', 'duration': '3:59', 'cover': '🎵'},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('音乐列表'),
        backgroundColor: Colors.purple,
        foregroundColor: Colors.white,
      ),
      body: ListView.builder(
        padding: const EdgeInsets.all(12),
        itemCount: songs.length,
        itemBuilder: (context, index) {
          final song = songs[index];
          return Card(
            margin: const EdgeInsets.only(bottom: 12),
            clipBehavior: Clip.antiAlias,
            child: InkWell(
              onTap: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('播放:${song['title']}')),
                );
              },
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      Colors.primaries[index % Colors.primaries.length].shade100,
                      Colors.primaries[index % Colors.primaries.length].shade200,
                    ],
                  ),
                ),
                child: Padding(
                  padding: const EdgeInsets.all(12),
                  child: Row(
                    children: [
                      // 专辑封面
                      Container(
                        width: 64,
                        height: 64,
                        decoration: BoxDecoration(
                          color: Colors.primaries[index % Colors.primaries.length],
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: Center(
                          child: Text(
                            song['cover'] as String,
                            style: const TextStyle(fontSize: 32),
                          ),
                        ),
                      ),
                      const SizedBox(width: 16),
                      // 歌曲信息
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              song['title'] as String,
                              style: const TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              '${song['artist']} - ${song['album']}',
                              style: const TextStyle(
                                fontSize: 14,
                                color: Colors.grey,
                              ),
                            ),
                          ],
                        ),
                      ),
                      // 时长和操作
                      Column(
                        children: [
                          Text(
                            song['duration'] as String,
                            style: const TextStyle(
                              fontSize: 12,
                              color: Colors.grey,
                            ),
                          ),
                          const SizedBox(height: 4),
                          IconButton(
                            icon: const Icon(Icons.play_circle_filled),
                            color: Colors.primaries[index % Colors.primaries.length],
                            onPressed: () {},
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

高级应用

1. 可滑动删除的Card

dart 复制代码
class SwipeableCard extends StatelessWidget {
  final String title;
  final VoidCallback onDelete;

  const SwipeableCard({
    super.key,
    required this.title,
    required this.onDelete,
  });

  @override
  Widget build(BuildContext context) {
    return Dismissible(
      key: Key(title),
      direction: DismissDirection.endToStart,
      onDismissed: (direction) => onDelete(),
      background: Container(
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 20),
        color: Colors.red,
        child: const Icon(Icons.delete, color: Colors.white, size: 32),
      ),
      child: Card(
        child: ListTile(title: Text(title)),
      ),
    );
  }
}

2. 可展开的Card

dart 复制代码
class ExpandableCard extends StatefulWidget {
  final String title;
  final String content;
  final List<Widget> actions;

  const ExpandableCard({
    super.key,
    required this.title,
    required this.content,
    required this.actions,
  });

  @override
  _ExpandableCardState createState() => _ExpandableCardState();
}

class _ExpandableCardState extends State<ExpandableCard> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: _expanded ? 8 : 2,
      margin: const EdgeInsets.all(8),
      child: Column(
        children: [
          ListTile(
            title: Text(widget.title),
            trailing: Icon(
              _expanded ? Icons.expand_less : Icons.expand_more,
            ),
            onTap: () {
              setState(() {
                _expanded = !_expanded;
              });
            },
          ),
          if (_expanded)
            Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(widget.content),
                  const SizedBox(height: 16),
                  Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: widget.actions,
                  ),
                ],
              ),
            ),
        ],
      ),
    );
  }
}

性能优化

1. 使用const构造函数

dart 复制代码
Card(
  margin: const EdgeInsets.all(8),  // const
  child: const ListTile(title: Text('固定内容')),
)

2. 避免嵌套过深

dart 复制代码
// ❌ 不推荐:嵌套过深
Card(
  child: Container(
    child: Padding(
      child: Column(
        children: [
          Container(
            child: ...,
          ),
        ],
      ),
    ),
  ),
)

// ✓ 推荐:合理使用参数
Card(
  margin: const EdgeInsets.all(8),
  padding: const EdgeInsets.all(16),
  child: ListTile(title: Text('简化结构')),
)

3. 使用clipBehavior

dart 复制代码
Card(
  clipBehavior: Clip.antiAlias,  // 避免内容溢出
  child: ...,
)

常见问题

Q1: Card和ListTile如何选择?

  • 简单列表项:使用ListTile
  • 内容丰富、需要装饰的列表项:使用Card包裹ListTile或自定义内容

Q2: 如何实现卡片间的间距?

使用margin参数设置Card的外边距。

Q3: 如何让Card可点击?

使用InkWell包裹Card,或在Card内部使用可点击组件。

总结

ListView + Card组合是一种强大的UI模式:

  • ✅ 提供美观的视觉效果
  • ✅ 支持丰富的装饰和交互
  • ✅ 适合展示复杂内容
  • ✅ 略低于普通列表的性能

合理使用Card样式,可以显著提升应用的视觉品质。

相关推荐
栈低来信2 小时前
Linux设备模型
linux
晚风吹长发2 小时前
初步了解Linux中的信号捕捉
linux·运维·服务器·c++·算法·进程·x信号
九皇叔叔2 小时前
【05】SpringBoot3 MybatisPlus 添加(Mapper)
windows
阡陌..2 小时前
Linux下用docker调用pytorch-无法检测到cuda问题
linux·pytorch·docker
herinspace2 小时前
管家婆分销软件中如何进行现金流量分配
运维·服务器·数据库·学习·电脑
山上三树2 小时前
详细介绍信号量
linux
(Charon)2 小时前
【网络编程】从零开始理解 io_uring:Linux 网络编程的“核动力”引擎
linux·运维·服务器
哪里不会点哪里.2 小时前
Nginx 详解:高性能 Web 服务器与反向代理
服务器·前端·nginx
水饺编程2 小时前
第4章,[标签 Win32] :系统字体与字符大小
c语言·c++·windows·visual studio