flutter组件学习之卡片与列表

Flutter 组件学习之卡片与列表

一、卡片组件(Card)

1. Card 基础使用

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

class BasicCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
// 阴影高度
elevation: 8.0,
// 阴影颜色
shadowColor: Colors.blue.withOpacity(0.3),
// 卡片颜色
color: Colors.white,
// 形状
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
// 内边距
margin: EdgeInsets.all(16.0),
// 内容
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'基本卡片',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
Text(
'这是一个简单的卡片组件示例,可以包含各种内容。',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
);
}
}

2. Card 与 ListTile 结合

dart 复制代码
class CardWithListTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Column(
children: [
// 带有图标的 ListTile
ListTile(
leading: Icon(
Icons.account_circle,
size: 40.0,
color: Colors.blue,
),
title: Text(
'张三',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
subtitle: Text('高级软件工程师'),
trailing: Icon(Icons.more_vert),
onTap: () {
print('点击了用户信息');
},
),

Divider(height: 1, thickness: 1),

// 带有开关的 ListTile
ListTile(
leading: Icon(Icons.notifications),
title: Text('消息通知'),
trailing: Switch(
value: true,
onChanged: (value) {
print('开关状态:$value');
},
),
onTap: () {},
),

Divider(height: 1, thickness: 1),

// 带有复选框的 ListTile
ListTile(
leading: Icon(Icons.privacy_tip),
title: Text('隐私设置'),
trailing: Checkbox(
value: false,
onChanged: (value) {
print('复选框状态:$value');
},
),
onTap: () {},
),
],
),
);
}
}

3. Card 布局示例

dart 复制代码
class ComplexCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
elevation: 6.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: Container(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 头部
Row(
children: [
CircleAvatar(
radius: 24.0,
backgroundImage: NetworkImage(
'https://via.placeholder.com/150',
),
),
SizedBox(width: 12.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Flutter 开发者社区',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
Text(
'5分钟前',
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
),
),
],
),
),
IconButton(
icon: Icon(Icons.more_horiz),
onPressed: () {},
),
],
),

SizedBox(height: 12.0),

// 内容
Text(
'Flutter 3.0 已经发布!新增了对 macOS 和 Linux 桌面应用的稳定支持,'
'并改进了性能。快来体验吧!',
style: TextStyle(fontSize: 14.0),
),

SizedBox(height: 12.0),

// 图片
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(
'https://via.placeholder.com/400x200',
width: double.infinity,
height: 200.0,
fit: BoxFit.cover,
),
),

SizedBox(height: 12.0),

// 底部操作栏
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
icon: Icon(Icons.thumb_up, color: Colors.blue),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.comment, color: Colors.grey),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.share, color: Colors.grey),
onPressed: () {},
),
],
),
],
),
),
);
}
}

4. Card 与 InkWell 结合(点击效果)

dart 复制代码
class ClickableCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Card(
elevation: 2.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: InkWell(
borderRadius: BorderRadius.circular(12.0),
onTap: () {
print('卡片被点击');
// 可以添加导航或弹窗等交互
},
onLongPress: () {
print('卡片被长按');
},
splashColor: Colors.blue.withOpacity(0.2),
highlightColor: Colors.blue.withOpacity(0.1),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Row(
children: [
Icon(
Icons.shopping_cart,
size: 40.0,
color: Colors.green,
),
SizedBox(width: 16.0),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'购物车',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
Text(
'3件商品待结算',
style: TextStyle(color: Colors.grey),
),
],
),
),
Icon(Icons.chevron_right, color: Colors.grey),
],
),
),
),
);
}
}

二、列表组件(ListView)

1. ListView 基本类型

dart 复制代码
class ListViewTypes extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// 1. 普通 ListView(适合短列表)
Container(
height: 150,
child: ListView(
padding: EdgeInsets.all(8.0),
scrollDirection: Axis.horizontal,
children: [
Container(
width: 100,
color: Colors.red,
child: Center(child: Text('Item 1')),
),
Container(
width: 100,
color: Colors.blue,
child: Center(child: Text('Item 2')),
),
Container(
width: 100,
color: Colors.green,
child: Center(child: Text('Item 3')),
),
Container(
width: 100,
color: Colors.yellow,
child: Center(child: Text('Item 4')),
),
Container(
width: 100,
color: Colors.orange,
child: Center(child: Text('Item 5')),
),
],
),
),

SizedBox(height: 20),

// 2. ListView.builder(适合动态列表,性能好)
Expanded(
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text('项目 ${index + 1}'),
subtitle: Text('这是第 ${index + 1} 个项目'),
trailing: Icon(Icons.chevron_right),
onTap: () {
print('点击了项目 ${index + 1}');
},
);
},
),
),
],
);
}
}

2. ListView.separated(带分隔线)

dart 复制代码
class SeparatedListView extends StatelessWidget {
final List<String> items = List.generate(
20,
(index) => '项目 ${index + 1}',
);

@override
Widget build(BuildContext context) {
return ListView.separated(
padding: EdgeInsets.all(16.0),
itemCount: items.length,
separatorBuilder: (context, index) {
// 分隔线
return Divider(
color: Colors.grey[300],
thickness: 1,
height: 1,
indent: 16.0,
endIndent: 16.0,
);
},
itemBuilder: (context, index) {
return Card(
elevation: 2.0,
child: ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length],
borderRadius: BorderRadius.circular(8.0),
),
child: Center(
child: Text(
'${index + 1}',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
title: Text(
items[index],
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
subtitle: Text(
'这是一个项目描述,这是第 ${index + 1} 个项目。',
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${DateTime.now().hour}:${DateTime.now().minute}',
style: TextStyle(
fontSize: 12.0,
color: Colors.grey,
),
),
SizedBox(height: 4.0),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
decoration: BoxDecoration(
color: index % 3 == 0
? Colors.green
: (index % 3 == 1 ? Colors.orange : Colors.red),
borderRadius: BorderRadius.circular(10.0),
),
child: Text(
index % 3 == 0
? '完成'
: (index % 3 == 1 ? '进行中' : '未开始'),
style: TextStyle(
color: Colors.white,
fontSize: 10.0,
),
),
),
],
),
contentPadding: EdgeInsets.all(12.0),
onTap: () {
print('点击了:${items[index]}');
},
onLongPress: () {
print('长按了:${items[index]}');
},
),
);
},
);
}
}

3. 嵌套 ListView

dart 复制代码
class NestedListView extends StatelessWidget {
final List<Map<String, dynamic>> categories = [
{
'name': '技术',
'items': ['Flutter', 'Dart', 'React Native', 'Vue.js', 'React'],
},
{
'name': '设计',
'items': ['UI设计', 'UX设计', '平面设计', '动效设计'],
},
{
'name': '产品',
'items': ['产品规划', '用户研究', '需求分析', '原型设计'],
},
{
'name': '运营',
'items': ['内容运营', '活动运营', '用户运营', '数据运营'],
},
];

@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(16.0),
itemCount: categories.length,
itemBuilder: (context, categoryIndex) {
final category = categories[categoryIndex];
return Card(
elevation: 4.0,
margin: EdgeInsets.only(bottom: 16.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: ExpansionTile(
leading: Icon(
Icons.category,
color: Colors.primaries[categoryIndex % Colors.primaries.length],
),
title: Text(
category['name'],
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
subtitle: Text(
'共 ${(category['items'] as List).length} 项',
style: TextStyle(color: Colors.grey),
),
children: [
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(), // 禁用滚动
itemCount: (category['items'] as List).length,
itemBuilder: (context, itemIndex) {
final item = (category['items'] as List)[itemIndex];
return ListTile(
contentPadding: EdgeInsets.only(left: 32.0, right: 16.0),
leading: Container(
width: 32.0,
height: 32.0,
decoration: BoxDecoration(
color: Colors.primaries[itemIndex % Colors.primaries.length],
shape: BoxShape.circle,
),
child: Center(
child: Text(
'${itemIndex + 1}',
style: TextStyle(
color: Colors.white,
fontSize: 12.0,
fontWeight: FontWeight.bold,
),
),
),
),
title: Text(item),
trailing: Icon(
Icons.arrow_forward_ios,
size: 16.0,
color: Colors.grey,
),
onTap: () {
print('点击了:${category['name']} - $item');
},
);
},
),
],
),
);
},
);
}
}

4. 分组列表

dart 复制代码
class GroupedListView extends StatelessWidget {
final Map<String, List<String>> groupedData = {
'A': ['Apple', 'Android', 'Amazon'],
'B': ['Baidu', 'Bytedance', 'Bilibili'],
'C': ['Cisco', 'Chrome', 'C++'],
'F': ['Facebook', 'Flutter', 'Firebase'],
'G': ['Google', 'GitHub', 'Gmail'],
};

@override
Widget build(BuildContext context) {
final sortedKeys = groupedData.keys.toList()..sort();

return ListView.builder(
itemCount: sortedKeys.length,
itemBuilder: (context, index) {
final key = sortedKeys[index];
final items = groupedData[key]!;

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 分组标题
Container(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
color: Colors.grey[100],
width: double.infinity,
child: Text(
key,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.blue,
),
),
),

// 分组内容
...items.map((item) {
return Card(
margin: EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
elevation: 1.0,
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text(
item[0],
style: TextStyle(color: Colors.white),
),
),
title: Text(item),
subtitle: Text('这是关于 $item 的描述信息'),
trailing: Text(
'${item.length} 字母',
style: TextStyle(color: Colors.grey),
),
onTap: () {
print('点击了:$item');
},
),
);
}).toList(),

// 分组间的间距
SizedBox(height: 16.0),
],
);
},
);
}
}

三、卡片与列表结合使用

1. 卡片式列表

dart 复制代码
class CardListExample extends StatelessWidget {
final List<Product> products = [
Product(
name: 'Flutter实战指南',
description: '全面介绍Flutter开发技巧',
price: 99.9,
rating: 4.8,
imageUrl: 'https://via.placeholder.com/150',
),
Product(
name: 'Dart编程语言',
description: '深入理解Dart语言特性',
price: 79.9,
rating: 4.5,
imageUrl: 'https://via.placeholder.com/150',
),
Product(
name: '移动UI设计',
description: '现代移动应用UI设计原则',
price: 89.9,
rating: 4.7,
imageUrl: 'https://via.placeholder.com/150',
),
Product(
name: '后端开发入门',
description: '使用Dart进行后端开发',
price: 109.9,
rating: 4.9,
imageUrl: 'https://via.placeholder.com/150',
),
Product(
name: '状态管理指南',
description: 'Flutter状态管理最佳实践',
price: 69.9,
rating: 4.6,
imageUrl: 'https://via.placeholder.com/150',
),
];

@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(16.0),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
margin: EdgeInsets.only(bottom: 16.0),
child: InkWell(
borderRadius: BorderRadius.circular(12.0),
onTap: () {
print('点击了:${product.name}');
},
child: Row(
children: [
// 图片区域
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12.0),
bottomLeft: Radius.circular(12.0),
),
child: Image.network(
product.imageUrl,
width: 120.0,
height: 120.0,
fit: BoxFit.cover,
),
),

// 内容区域
Expanded(
child: Padding(
padding: EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),

SizedBox(height: 4.0),

Text(
product.description,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14.0,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),

SizedBox(height: 8.0),

Row(
children: [
// 评分
Row(
children: [
Icon(
Icons.star,
color: Colors.amber,
size: 16.0,
),
SizedBox(width: 4.0),
Text(
product.rating.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14.0,
),
),
],
),

Spacer(),

// 价格
Text(
'¥${product.price}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.red,
),
),
],
),

SizedBox(height: 8.0),

// 操作按钮
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: () {
print('加入购物车:${product.name}');
},
icon: Icon(Icons.shopping_cart, size: 16.0),
label: Text('加购物车'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 8.0),
),
),
),

SizedBox(width: 8.0),

Expanded(
child: ElevatedButton.icon(
onPressed: () {
print('立即购买:${product.name}');
},
icon: Icon(Icons.bolt, size: 16.0),
label: Text('立即购买'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 8.0),
),
),
),
],
),
],
),
),
),
],
),
),
);
},
);
}
}

class Product {
final String name;
final String description;
final double price;
final double rating;
final String imageUrl;

Product({
required this.name,
required this.description,
required this.price,
required this.rating,
required this.imageUrl,
});
}

2. 网格卡片列表

dart 复制代码
class GridCardList extends StatelessWidget {
final List<GridItem> gridItems = List.generate(
20,
(index) => GridItem(
title: '项目 ${index + 1}',
subtitle: '描述 ${index + 1}',
color: Colors.primaries[index % Colors.primaries.length],
icon: Icons.widgets,
),
);

@override
Widget build(BuildContext context) {
return GridView.builder(
padding: EdgeInsets.all(16.0),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 每行显示的数量
crossAxisSpacing: 16.0, // 水平间距
mainAxisSpacing: 16.0, // 垂直间距
childAspectRatio: 0.8, // 宽高比
),
itemCount: gridItems.length,
itemBuilder: (context, index) {
final item = gridItems[index];
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
child: InkWell(
borderRadius: BorderRadius.circular(16.0),
onTap: () {
print('点击了:${item.title}');
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 图标
Container(
width: 60.0,
height: 60.0,
decoration: BoxDecoration(
color: item.color.withOpacity(0.2),
borderRadius: BorderRadius.circular(30.0),
),
child: Icon(
item.icon,
size: 32.0,
color: item.color,
),
),

SizedBox(height: 16.0),

// 标题
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
item.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),

SizedBox(height: 8.0),

// 描述
Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
item.subtitle,
style: TextStyle(
color: Colors.grey[600],
fontSize: 12.0,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),

SizedBox(height: 16.0),

// 按钮
Container(
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
decoration: BoxDecoration(
color: item.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20.0),
),
child: Text(
'查看详情',
style: TextStyle(
color: item.color,
fontSize: 12.0,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
},
);
}
}

class GridItem {
final String title;
final String subtitle;
final Color color;
final IconData icon;

GridItem({
required this.title,
required this.subtitle,
required this.color,
required this.icon,
});
}

四、高级列表特性

1. 下拉刷新与上拉加载

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

class _RefreshableListState extends State<RefreshableList> {
List<String> items = List.generate(20, (index) => '项目 ${index + 1}');
bool isLoading = false;
ScrollController _scrollController = ScrollController();

@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}

@override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}

void _scrollListener() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_loadMore();
}
}

Future<void> _refresh() async {
// 模拟网络请求延迟
await Future.delayed(Duration(seconds: 2));

setState(() {
items = List.generate(20, (index) => '刷新项目 ${index + 1}');
});
}

Future<void> _loadMore() async {
if (!isLoading) {
setState(() {
isLoading = true;
});

// 模拟网络请求延迟
await Future.delayed(Duration(seconds: 2));

setState(() {
final startIndex = items.length;
items.addAll(
List.generate(10, (index) => '新增项目 ${startIndex + index + 1}'),
);
isLoading = false;
});
}
}

@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refresh,
color: Colors.blue,
backgroundColor: Colors.white,
displacement: 40.0,
strokeWidth: 3.0,
child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.all(16.0),
itemCount: items.length + 1, // 多一个用于显示加载指示器
itemBuilder: (context, index) {
if (index == items.length) {
return Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: isLoading
? CircularProgressIndicator()
: TextButton(
onPressed: _loadMore,
child: Text('加载更多'),
),
),
);
}

return Card(
margin: EdgeInsets.only(bottom: 12.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text(
'${index + 1}',
style: TextStyle(color: Colors.white),
),
),
title: Text(items[index]),
subtitle: Text('这是第 ${index + 1} 个项目'),
trailing: Icon(Icons.chevron_right),
onTap: () {
print('点击了:${items[index]}');
},
),
);
},
),
);
}
}

2. 可拖拽排序列表

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

class _DraggableListState extends State<DraggableList> {
List<String> items = List.generate(
10,
(index) => '可拖拽项目 ${index + 1}',
);

@override
Widget build(BuildContext context) {
return ReorderableListView.builder(
padding: EdgeInsets.all(16.0),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
key: ValueKey(items[index]), // 必须提供唯一key
elevation: 2.0,
margin: EdgeInsets.only(bottom: 12.0),
child: ListTile(
leading: ReorderableDragStartListener(
index: index,
child: Icon(Icons.drag_handle, color: Colors.grey),
),
title: Text(items[index]),
subtitle: Text('长按拖动重新排序'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit, color: Colors.blue),
onPressed: () {
print('编辑:${items[index]}');
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
setState(() {
items.removeAt(index);
});
},
),
],
),
),
);
},
onReorder: (oldIndex, newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
);
}
}

3. 可展开列表

dart 复制代码
class ExpandableList extends StatelessWidget {
final List<ExpandableItem> items = [
ExpandableItem(
title: 'Flutter基础',
children: ['Widget基础', '布局系统', '状态管理', '路由导航'],
),
ExpandableItem(
title: 'Dart语言',
children: ['语法基础', '面向对象', '异步编程', '泛型'],
),
ExpandableItem(
title: '项目实战',
children: ['电商应用', '社交应用', '新闻应用', '工具应用'],
),
];

@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.all(16.0),
itemCount: items.length,
itemBuilder: (context, index) {
return ExpandableCard(item: items[index]);
},
);
}
}

class ExpandableItem {
final String title;
final List<String> children;

ExpandableItem({required this.title, required this.children});
}

class ExpandableCard extends StatefulWidget {
final ExpandableItem item;

ExpandableCard({required this.item});

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

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

@override
Widget build(BuildContext context) {
return Card(
elevation: 4.0,
margin: EdgeInsets.only(bottom: 16.0),
child: Column(
children: [
// 头部
ListTile(
leading: Container(
width: 40.0,
height: 40.0,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Center(
child: Text(
widget.item.title[0],
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
),
),
title: Text(
widget.item.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
subtitle: Text('包含 ${widget.item.children.length} 个子项'),
trailing: IconButton(
icon: Icon(
_isExpanded ? Icons.expand_less : Icons.expand_more,
color: Colors.blue,
),
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
),
onTap: () {
setState(() {
_isExpanded = !_isExpanded;
});
},
),

// 展开内容
if (_isExpanded) ...[
Divider(height: 1, thickness: 1),
Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: widget.item.children.map((child) {
return Padding(
padding: EdgeInsets.only(bottom: 12.0),
child: Row(
children: [
Icon(
Icons.chevron_right,
color: Colors.grey,
size: 16.0,
),
SizedBox(width: 8.0),
Expanded(
child: Text(
child,
style: TextStyle(fontSize: 14.0),
),
),
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(4.0),
),
child: Text(
'详情',
style: TextStyle(
color: Colors.blue,
fontSize: 12.0,
),
),
),
],
),
);
}).toList(),
),
),

// 底部操作按钮
Padding(
padding: EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
OutlinedButton(
onPressed: () {
print('学习:${widget.item.title}');
},
child: Text('开始学习'),
),
ElevatedButton(
onPressed: () {
print('分享:${widget.item.title}');
},
child: Text('分享'),
),
],
),
),
],
],
),
);
}
}

五、性能优化

1. 使用 ListView.builder 提高性能

dart 复制代码
class OptimizedListView extends StatelessWidget {
final List<String> items = List.generate(1000, (index) => '项目 $index');

@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
// 设置预估高度,提高性能
itemExtent: 72.0,
// 使用 const 构造函数优化
itemBuilder: (context, index) {
return const ListItem(
title: items[index],
index: index,
);
},
);
}
}

class ListItem extends StatelessWidget {
final String title;
final int index;

const ListItem({
required this.title,
required this.index,
});

@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(8.0),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text(
'${index + 1}',
style: TextStyle(color: Colors.white),
),
),
title: Text(title),
subtitle: Text('这是第 $index 个项目'),
trailing: Icon(Icons.chevron_right),
),
);
}
}

2. 使用 AutomaticKeepAlive 保持状态

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

class _KeepAliveListState extends State<KeepAliveList> {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('保持状态列表'),
bottom: TabBar(
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
body: TabBarView(
children: [
KeepAliveWrapper(child: Tab1Content()),
KeepAliveWrapper(child: Tab2Content()),
KeepAliveWrapper(child: Tab3Content()),
],
),
),
);
}
}

// 保持状态的包装器
class KeepAliveWrapper extends StatefulWidget {
final Widget child;

KeepAliveWrapper({required this.child});

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

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return widget.child;
}

@override
bool get wantKeepAlive => true;
}

class Tab1Content extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('Tab 1 - Item $index'),
);
},
);
}
}

// Tab2Content 和 Tab3Content 类似...

六、实战示例:新闻列表

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

class _NewsListScreenState extends State<NewsListScreen> {
final List<News> newsList = [
News(
title: 'Flutter 3.0 正式发布',
summary: '支持macOS和Linux桌面应用,性能大幅提升',
author: 'Google',
publishTime: '2023-05-10',
readCount: 12345,
likes: 2345,
category: '技术',
imageUrl: 'https://via.placeholder.com/150',
isFavorite: false,
),
News(
title: 'Dart 2.18 新特性',
summary: '新增语言特性,性能优化,支持更多平台',
author: 'Dart团队',
publishTime: '2023-05-09',
readCount: 8765,
likes: 1567,
category: '技术',
imageUrl: 'https://via.placeholder.com/150',
isFavorite: true,
),
// 更多新闻数据...
];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('新闻列表'),
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.filter_list),
onPressed: () {},
),
],
),
body: ListView.separated(
padding: EdgeInsets.all(16.0),
itemCount: newsList.length,
separatorBuilder: (context, index) => SizedBox(height: 16.0),
itemBuilder: (context, index) {
final news = newsList[index];
return NewsCard(
news: news,
onFavorite: () {
setState(() {
newsList[index].isFavorite = !newsList[index].isFavorite;
});
},
onTap: () {
// 跳转到新闻详情页
print('查看新闻:${news.title}');
},
);
},
),
);
}
}

class News {
String title;
String summary;
String author;
String publishTime;
int readCount;
int likes;
String category;
String imageUrl;
bool isFavorite;

News({
required this.title,
required this.summary,
required this.author,
required this.publishTime,
required this.readCount,
required this.likes,
required this.category,
required this.imageUrl,
required this.isFavorite,
});
}

class NewsCard extends StatelessWidget {
final News news;
final VoidCallback onFavorite;
final VoidCallback onTap;

NewsCard({
required this.news,
required this.onFavorite,
required this.onTap,
});

@override
Widget build(BuildContext context) {
return Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
),
child: InkWell(
borderRadius: BorderRadius.circular(12.0),
onTap: onTap,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 分类标签
Row(
children: [
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.0),
),
child: Text(
news.category,
style: TextStyle(
color: Colors.blue,
fontSize: 12.0,
fontWeight: FontWeight.bold,
),
),
),
Spacer(),
IconButton(
icon: Icon(
news.isFavorite
? Icons.favorite
: Icons.favorite_border,
color: news.isFavorite ? Colors.red : Colors.grey,
),
onPressed: onFavorite,
),
],
),

SizedBox(height: 8.0),

// 标题和图片
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
news.title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
height: 1.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),

SizedBox(height: 8.0),

Text(
news.summary,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14.0,
height: 1.4,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),

SizedBox(width: 12.0),

// 新闻图片
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.network(
news.imageUrl,
width: 100.0,
height: 100.0,
fit: BoxFit.cover,
),
),
],
),

SizedBox(height: 12.0),

// 底部信息
Row(
children: [
// 作者
Row(
children: [
Icon(
Icons.person_outline,
size: 14.0,
color: Colors.grey,
),
SizedBox(width: 4.0),
Text(
news.author,
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
),
),
],
),

SizedBox(width: 16.0),

// 发布时间
Row(
children: [
Icon(
Icons.access_time,
size: 14.0,
color: Colors.grey,
),
SizedBox(width: 4.0),
Text(
news.publishTime,
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
),
),
],
),

Spacer(),

// 阅读数和点赞数
Row(
children: [
Row(
children: [
Icon(
Icons.remove_red_eye,
size: 14.0,
color: Colors.grey,
),
SizedBox(width: 4.0),
Text(
'${news.readCount}',
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
),
),
],
),

SizedBox(width: 12.0),

Row(
children: [
Icon(
Icons.thumb_up,
size: 14.0,
color: Colors.grey,
),
SizedBox(width: 4.0),
Text(
'${news.likes}',
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
),
),
],
),
],
),
],
),
],
),
),
),
);
}
}

总结

卡片(Card)和列表(ListView)是 Flutter 中最常用的布局组件,掌握它们的用法对于构建美观、实用的界面至关重要:

  1. Card 组件:用于创建带有阴影和圆角的卡片式布局
  2. ListView 组件:支持多种构建方式,适用于不同场景
  3. 组合使用:卡片和列表通常结合使用创建复杂界面
  4. 性能优化 :对于长列表,使用 ListView.builderconst 构造函数
  5. 交互增强:添加下拉刷新、上拉加载、拖拽排序等功能
  6. 实战应用:在实际项目中灵活应用,如新闻列表、商品列表等

掌握这些技巧,你将能够构建出功能丰富、用户体验良好的列表界面。

相关推荐
moreen1 小时前
Koa3.1.2 迁移, 持续更新中
javascript
Luna-player1 小时前
[特殊字符] Spring Boot 静态资源默认映射规则详解
学习
苦瓜小生2 小时前
【黑马点评学习笔记 | 实战篇 】| 7-达人探店
redis·笔记·后端·学习
qq_211387472 小时前
基于LangGraph多agent
开发语言·前端·javascript·agent·langgraph
AI-Ming2 小时前
注意力机制拓展-大模型知识点(程序员转行AI大模型学习)
人工智能·学习
liuyao_xianhui2 小时前
优选算法_模拟_替换所有的‘?‘_C++
开发语言·javascript·数据结构·c++·算法·链表·动态规划
ADHD多动联盟2 小时前
多动症孩子的运动干预是什么?主要有怎样的方法?
学习·学习方法·玩游戏
不爱吃糖的程序媛2 小时前
Flutter-OH 升级指导
flutter
摸鱼仙人~2 小时前
Vue Todo 实战练习教程(简略版)
前端·javascript·vue.js