Flutter ListView 列表组件完全指南

什么是 ListView?

ListView 是 Flutter 中用于显示可滚动列表的组件,类似于 HTML 中的滚动容器。

用途:

  • 显示长列表
  • 聊天消息列表
  • 商品列表
  • 新闻列表
  • 任何需要滚动的内容

ListView 的类型

1. ListView - 基础列表

dart 复制代码
ListView(
  children: [
    ListTile(title: Text('项目 1')),
    ListTile(title: Text('项目 2')),
    ListTile(title: Text('项目 3')),
  ],
)

2. ListView.builder - 动态列表(推荐)

dart 复制代码
ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('项目 $index'),
    );
  },
)

3. ListView.separated - 带分隔符的列表

dart 复制代码
ListView.separated(
  itemCount: 20,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('项目 $index'),
    );
  },
  separatorBuilder: (context, index) {
    return Divider();  // 分隔线
  },
)

4. ListView.custom - 自定义列表

dart 复制代码
ListView.custom(
  childrenDelegate: SliverChildBuilderDelegate(
    (context, index) {
      return ListTile(title: Text('项目 $index'));
    },
    childCount: 20,
  ),
)

常用属性

1. 滚动方向 scrollDirection

dart 复制代码
// 垂直滚动(默认)
ListView(
  scrollDirection: Axis.vertical,
  children: [...],
)

// 水平滚动
ListView(
  scrollDirection: Axis.horizontal,
  children: [...],
)

2. 反向滚动 reverse

dart 复制代码
ListView(
  reverse: true,  // 从底部开始
  children: [...],
)

3. 内边距 padding

dart 复制代码
ListView(
  padding: EdgeInsets.all(10),
  children: [...],
)

4. 物理效果 physics

dart 复制代码
// 总是可滚动
ListView(
  physics: AlwaysScrollableScrollPhysics(),
  children: [...],
)

// 永不滚动
ListView(
  physics: NeverScrollableScrollPhysics(),
  children: [...],
)

// 弹性滚动(iOS 风格)
ListView(
  physics: BouncingScrollPhysics(),
  children: [...],
)

// 夹紧滚动(Android 风格)
ListView(
  physics: ClampingScrollPhysics(),
  children: [...],
)

ListTile 列表项

基础用法

dart 复制代码
ListTile(
  leading: Icon(Icons.person),      // 左侧图标
  title: Text('标题'),               // 标题
  subtitle: Text('副标题'),          // 副标题
  trailing: Icon(Icons.chevron_right),  // 右侧图标
  onTap: () {
    print('点击了列表项');
  },
)

完整属性

dart 复制代码
ListTile(
  leading: CircleAvatar(
    child: Icon(Icons.person),
  ),
  title: Text('张三'),
  subtitle: Text('Flutter 开发工程师'),
  trailing: Icon(Icons.chevron_right),
  isThreeLine: false,  // 是否三行
  dense: false,        // 是否紧凑
  enabled: true,       // 是否启用
  selected: false,     // 是否选中
  onTap: () {},
  onLongPress: () {},
)

实战案例

案例1:简单列表

dart 复制代码
ListView.builder(
  itemCount: 20,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(
        child: Text('${index + 1}'),
      ),
      title: Text('列表项 ${index + 1}'),
      subtitle: Text('这是副标题'),
      trailing: Icon(Icons.chevron_right),
      onTap: () {
        print('点击了第 ${index + 1} 项');
      },
    );
  },
)

案例2:联系人列表

dart 复制代码
class Contact {
  final String name;
  final String phone;
  final String avatar;

  Contact({required this.name, required this.phone, required this.avatar});
}

class ContactList extends StatelessWidget {
  final List<Contact> contacts = [
    Contact(name: '张三', phone: '138****0001', avatar: 'https://example.com/1.jpg'),
    Contact(name: '李四', phone: '138****0002', avatar: 'https://example.com/2.jpg'),
    Contact(name: '王五', phone: '138****0003', avatar: 'https://example.com/3.jpg'),
  ];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: contacts.length,
      itemBuilder: (context, index) {
        final contact = contacts[index];
        return ListTile(
          leading: CircleAvatar(
            backgroundImage: NetworkImage(contact.avatar),
          ),
          title: Text(contact.name),
          subtitle: Text(contact.phone),
          trailing: IconButton(
            icon: Icon(Icons.phone),
            onPressed: () {
              print('拨打 ${contact.phone}');
            },
          ),
          onTap: () {
            print('查看 ${contact.name} 的详情');
          },
        );
      },
    );
  }
}

案例3:聊天列表

dart 复制代码
class ChatItem {
  final String name;
  final String message;
  final String time;
  final int unread;

  ChatItem({
    required this.name,
    required this.message,
    required this.time,
    this.unread = 0,
  });
}

class ChatList extends StatelessWidget {
  final List<ChatItem> chats = [
    ChatItem(name: '张三', message: '你好啊', time: '10:30', unread: 2),
    ChatItem(name: '李四', message: '在吗?', time: '09:15', unread: 0),
    ChatItem(name: '王五', message: '晚上一起吃饭', time: '昨天', unread: 1),
  ];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: chats.length,
      itemBuilder: (context, index) {
        final chat = chats[index];
        return ListTile(
          leading: CircleAvatar(
            child: Text(chat.name[0]),
          ),
          title: Row(
            children: [
              Expanded(child: Text(chat.name)),
              Text(
                chat.time,
                style: TextStyle(fontSize: 12, color: Colors.grey),
              ),
            ],
          ),
          subtitle: Row(
            children: [
              Expanded(
                child: Text(
                  chat.message,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
              if (chat.unread > 0)
                Container(
                  padding: EdgeInsets.all(4),
                  decoration: BoxDecoration(
                    color: Colors.red,
                    shape: BoxShape.circle,
                  ),
                  constraints: BoxConstraints(minWidth: 20, minHeight: 20),
                  child: Center(
                    child: Text(
                      '${chat.unread}',
                      style: TextStyle(color: Colors.white, fontSize: 12),
                    ),
                  ),
                ),
            ],
          ),
          onTap: () {
            print('打开与 ${chat.name} 的聊天');
          },
        );
      },
    );
  }
}

案例4:商品列表

dart 复制代码
class Product {
  final String name;
  final String image;
  final double price;
  final String description;

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

class ProductList extends StatelessWidget {
  final List<Product> products = [
    Product(
      name: 'iPhone 15 Pro',
      image: 'https://example.com/iphone.jpg',
      price: 7999,
      description: '最新款 iPhone',
    ),
    // 更多商品...
  ];

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return Card(
          margin: EdgeInsets.all(10),
          child: Padding(
            padding: EdgeInsets.all(10),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                ClipRRect(
                  borderRadius: BorderRadius.circular(8),
                  child: Image.network(
                    product.image,
                    width: 100,
                    height: 100,
                    fit: BoxFit.cover,
                  ),
                ),
                SizedBox(width: 10),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        product.name,
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      SizedBox(height: 5),
                      Text(
                        product.description,
                        style: TextStyle(color: Colors.grey),
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      ),
                      SizedBox(height: 10),
                      Row(
                        children: [
                          Text(
                            '¥${product.price}',
                            style: TextStyle(
                              fontSize: 20,
                              fontWeight: FontWeight.bold,
                              color: Colors.red,
                            ),
                          ),
                          Spacer(),
                          ElevatedButton(
                            onPressed: () {},
                            child: Text('购买'),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

案例5:水平滚动列表

dart 复制代码
Container(
  height: 200,
  child: ListView.builder(
    scrollDirection: Axis.horizontal,
    itemCount: 10,
    itemBuilder: (context, index) {
      return Container(
        width: 150,
        margin: EdgeInsets.all(10),
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(10),
        ),
        child: Center(
          child: Text(
            '卡片 $index',
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
        ),
      );
    },
  ),
)

案例6:分组列表

dart 复制代码
class GroupedList extends StatelessWidget {
  final Map<String, List<String>> groups = {
    'A': ['Alice', 'Amy', 'Andrew'],
    'B': ['Bob', 'Betty', 'Brian'],
    'C': ['Charlie', 'Cathy'],
  };

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _getTotalCount(),
      itemBuilder: (context, index) {
        final item = _getItem(index);
        if (item['type'] == 'header') {
          return Container(
            padding: EdgeInsets.all(10),
            color: Colors.grey[300],
            child: Text(
              item['data'],
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
              ),
            ),
          );
        } else {
          return ListTile(
            title: Text(item['data']),
          );
        }
      },
    );
  }

  int _getTotalCount() {
    int count = 0;
    groups.forEach((key, value) {
      count += 1 + value.length;  // 1 个标题 + n 个项目
    });
    return count;
  }

  Map<String, dynamic> _getItem(int index) {
    int currentIndex = 0;
    for (var entry in groups.entries) {
      if (currentIndex == index) {
        return {'type': 'header', 'data': entry.key};
      }
      currentIndex++;
      
      for (var item in entry.value) {
        if (currentIndex == index) {
          return {'type': 'item', 'data': item};
        }
        currentIndex++;
      }
    }
    return {'type': 'item', 'data': ''};
  }
}

案例7:下拉刷新

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

class _RefreshableListState extends State<RefreshableList> {
  List<String> items = List.generate(20, (index) => '项目 $index');

  Future<void> _onRefresh() async {
    await Future.delayed(Duration(seconds: 2));
    setState(() {
      items = List.generate(20, (index) => '新项目 $index');
    });
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _onRefresh,
      child: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(items[index]),
          );
        },
      ),
    );
  }
}

案例8:滑动删除

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

class _DismissibleListState extends State<DismissibleList> {
  List<String> items = List.generate(20, (index) => '项目 $index');

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];
        return Dismissible(
          key: Key(item),
          background: Container(
            color: Colors.red,
            alignment: Alignment.centerRight,
            padding: EdgeInsets.only(right: 20),
            child: Icon(Icons.delete, color: Colors.white),
          ),
          direction: DismissDirection.endToStart,
          onDismissed: (direction) {
            setState(() {
              items.removeAt(index);
            });
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('$item 已删除')),
            );
          },
          child: ListTile(
            title: Text(item),
          ),
        );
      },
    );
  }
}

案例9:加载更多

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

class _LoadMoreListState extends State<LoadMoreList> {
  List<String> items = List.generate(20, (index) => '项目 $index');
  bool isLoading = false;
  ScrollController _scrollController = ScrollController();

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

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

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

    await Future.delayed(Duration(seconds: 2));

    setState(() {
      int currentLength = items.length;
      items.addAll(
        List.generate(10, (index) => '项目 ${currentLength + index}'),
      );
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _scrollController,
      itemCount: items.length + 1,
      itemBuilder: (context, index) {
        if (index == items.length) {
          return isLoading
              ? Center(
                  child: Padding(
                    padding: EdgeInsets.all(20),
                    child: CircularProgressIndicator(),
                  ),
                )
              : SizedBox.shrink();
        }
        return ListTile(
          title: Text(items[index]),
        );
      },
    );
  }

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

案例10:搜索列表

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

class _SearchableListState extends State<SearchableList> {
  List<String> allItems = List.generate(100, (index) => '项目 $index');
  List<String> filteredItems = [];
  TextEditingController _searchController = TextEditingController();

  @override
  void initState() {
    super.initState();
    filteredItems = allItems;
    _searchController.addListener(_onSearchChanged);
  }

  void _onSearchChanged() {
    setState(() {
      filteredItems = allItems
          .where((item) =>
              item.toLowerCase().contains(_searchController.text.toLowerCase()))
          .toList();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: EdgeInsets.all(10),
          child: TextField(
            controller: _searchController,
            decoration: InputDecoration(
              hintText: '搜索...',
              prefixIcon: Icon(Icons.search),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(10),
              ),
            ),
          ),
        ),
        Expanded(
          child: ListView.builder(
            itemCount: filteredItems.length,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text(filteredItems[index]),
              );
            },
          ),
        ),
      ],
    );
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }
}

完整示例

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ListViewDemo(),
    );
  }
}

class ListViewDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('ListView 示例')),
      body: ListView.separated(
        itemCount: 20,
        itemBuilder: (context, index) {
          return ListTile(
            leading: CircleAvatar(
              child: Text('${index + 1}'),
            ),
            title: Text('列表项 ${index + 1}'),
            subtitle: Text('这是副标题'),
            trailing: Icon(Icons.chevron_right),
            onTap: () {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('点击了第 ${index + 1} 项')),
              );
            },
          );
        },
        separatorBuilder: (context, index) {
          return Divider(height: 1);
        },
      ),
    );
  }
}

常见问题

1. ListView 在 Column 中不显示?

dart 复制代码
// ❌ 错误
Column(
  children: [
    ListView(...),  // 会报错
  ],
)

// ✅ 解决1:使用 Expanded
Column(
  children: [
    Expanded(
      child: ListView(...),
    ),
  ],
)

// ✅ 解决2:使用 shrinkWrap
Column(
  children: [
    ListView(
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
      children: [...],
    ),
  ],
)

2. 如何监听滚动?

dart 复制代码
ScrollController _controller = ScrollController();

@override
void initState() {
  super.initState();
  _controller.addListener(() {
    print('滚动位置: ${_controller.position.pixels}');
  });
}

ListView(
  controller: _controller,
  children: [...],
)

3. 如何滚动到指定位置?

dart 复制代码
_controller.animateTo(
  100.0,
  duration: Duration(milliseconds: 500),
  curve: Curves.easeInOut,
);

属性速查表

属性 说明
children 子组件列表
scrollDirection 滚动方向
reverse 是否反向
controller 滚动控制器
physics 滚动物理效果
padding 内边距
shrinkWrap 是否根据子组件调整大小

ListView.builder 参数

参数 说明
itemCount 项目数量
itemBuilder 项目构建器

ListTile 属性

属性 说明
leading 左侧组件
title 标题
subtitle 副标题
trailing 右侧组件
onTap 点击回调
onLongPress 长按回调

总结

ListView 的核心要点:

  1. ListView.builder 适合大量数据
  2. ListView.separated 自动添加分隔符
  3. 使用 ScrollController 控制滚动
  4. RefreshIndicator 实现下拉刷新
  5. Dismissible 实现滑动删除

记住:

  • 大量数据用 ListView.builder
  • 在 Column 中使用需要 Expanded 或 shrinkWrap
  • 使用 ListTile 快速构建列表项
  • 善用 ScrollController 监听和控制滚动

ListView 是最常用的滚动组件,掌握它能构建各种列表界面!

相关推荐
ujainu5 小时前
Flutter + OpenHarmony 游戏开发进阶:用户输入响应——GestureDetector 实现点击发射
flutter·游戏·openharmony
Doro再努力5 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
Daniel李华5 小时前
echarts使用案例
android·javascript·echarts
hudawei9965 小时前
TweenAnimationBuilder和AnimatedBuilder两种动画的比较
flutter·ui·动画·tweenanimation·animatedbuilder
ujainu5 小时前
Flutter + OpenHarmony 实现无限跑酷游戏开发实战—— 对象池化、性能优化与流畅控制
flutter·游戏·性能优化·openharmony·endless runner
做人不要太理性6 小时前
CANN Runtime 运行时组件深度解析:任务调度机制、存储管理策略与维测体系构建逻辑
android·运维·魔珐星云
我命由我123456 小时前
Android 广播 - 静态注册与动态注册对广播接收器实例创建的影响
android·java·开发语言·java-ee·android studio·android-studio·android runtime
ZH15455891316 小时前
Flutter for OpenHarmony Python学习助手实战:自动化脚本开发的实现
python·学习·flutter
朗迹 - 张伟7 小时前
Tauri2 导出 Android 详细教程
android
lpruoyu8 小时前
【Android第一行代码学习笔记】Android架构_四大组件_权限_持久化_通知_异步_服务
android·笔记·学习