在这里插入图片描述

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

📜 本文将带你深入掌握 Flutter 中最常用的滚动列表组件——ListView,从基础用法到高级技巧,全面了解列表展示的各种方式。


一、ListView 组件概述

在移动应用开发中,列表是最常见的 UI 组件之一。无论是联系人列表、新闻列表还是商品列表,都需要使用滚动列表来展示数据。Flutter 的 ListView 组件提供了强大而灵活的列表展示功能,支持垂直和水平滚动,内置了滚动优化和性能优化机制。

🎯 为什么 ListView 如此重要?

ListView 是 Flutter 中最常用的滚动容器,它的重要性体现在以下几个方面:

  • 内置滚动优化:自动处理滚动事件,支持惯性滚动和回弹效果
  • 懒加载机制:只渲染可见区域的子组件,大幅提升性能
  • 丰富的构造方式:提供多种构造函数,适应不同的使用场景
  • 灵活的布局能力:支持垂直和水平滚动,支持自定义滚动控制器
  • 高性能表现:通过 ItemExtent 等属性优化滚动性能

💡 核心思想:ListView 的核心优势在于其懒加载机制,它只创建和渲染当前可见的子组件,当用户滚动时,会回收不可见的子组件并创建新的可见组件。这种机制使得 ListView 可以高效地展示成千上万条数据,而不会出现性能问题。


二、ListView 的构造方式

2.1 ListView 默认构造函数

最简单的 ListView 使用方式是通过默认构造函数,传入一个子组件列表。

ListView(
  children: const [
    ListTile(title: Text('项目 1')),
    ListTile(title: Text('项目 2')),
    ListTile(title: Text('项目 3')),
    ListTile(title: Text('项目 4')),
    ListTile(title: Text('项目 5')),
  ],
)

这种方式适合数据量较少(通常少于 20 个)的场景,因为所有子组件会被一次性创建。

⚠️ 注意:对于大量数据,不要使用默认构造函数,因为它会创建所有子组件,导致内存占用过高和性能问题。

2.2 ListView.builder 构造函数

ListView.builder 是最常用的构造函数,它通过 itemBuilder 回调按需创建子组件,适合展示大量数据。

ListView.builder(
  itemCount: 100, // 列表项数量
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('项目 ${index + 1}'),
    );
  },
)

ListView.builder 的参数说明:

参数 类型 说明 是否必填
itemCount int? 列表项数量,null 表示无限列表
itemBuilder Widget Function 列表项构建器
scrollDirection Axis 滚动方向(vertical/horizontal)
reverse bool 是否反向滚动
padding EdgeInsets? 内边距
itemExtent double? 固定高度/宽度

2.3 ListView.separated 构造函数

ListView.separatedListView.builder 类似,但可以在列表项之间添加分隔符。

ListView.separated(
  itemCount: 10,
  separatorBuilder: (context, index) => const Divider(),
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('项目 ${index + 1}'),
    );
  },
)

这种方式非常适合需要分隔符的列表,如联系人列表、设置选项等。

2.4 ListView.custom 构造函数

ListView.custom 提供了最底层的自定义能力,可以通过自定义 SliverChildDelegate 来控制列表的渲染行为。

ListView.custom(
  childrenDelegate: SliverChildListDelegate(
    const [
      ListTile(title: Text('项目 1')),
      ListTile(title: Text('项目 2')),
      ListTile(title: Text('项目 3')),
    ],
  ),
)

这种方式适用于需要完全自定义列表渲染逻辑的高级场景。


三、ListView 的常用属性

3.1 滚动方向 scrollDirection

scrollDirection 控制列表的滚动方向,可以是垂直(默认)或水平。

// 垂直滚动(默认)
ListView(
  scrollDirection: Axis.vertical,
  children: const [
    Text('垂直项目 1'),
    Text('垂直项目 2'),
  ],
)

// 水平滚动
ListView(
  scrollDirection: Axis.horizontal,
  children: const [
    Text('水平项目 1'),
    Text('水平项目 2'),
  ],
)
方向 适用场景
垂直 Axis.vertical 常见的列表、设置页面
水平 Axis.horizontal 图片轮播、标签列表

3.2 反向滚动 reverse

reverse 属性可以让列表从底部或右侧开始滚动。

ListView(
  reverse: true, // 从底部开始
  children: const [
    Text('项目 1'),
    Text('项目 2'),
    Text('项目 3'),
  ],
)

💡 小贴士:反向滚动常用于聊天应用,让最新消息显示在底部。

3.3 内边距 padding

padding 属性可以为列表添加内边距,避免内容紧贴屏幕边缘。

ListView(
  padding: const EdgeInsets.all(16),
  children: const [
    Text('有内边距的项目 1'),
    Text('有内边距的项目 2'),
  ],
)

3.4 固定高度 itemExtent

itemExtent 可以指定每个列表项的固定高度,这可以显著提升滚动性能,因为 ListView 不需要测量每个子组件的高度。

ListView.builder(
  itemExtent: 56, // 每个列表项固定高度为 56
  itemCount: 100,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('项目 ${index + 1}'),
    );
  },
)

⚠️ 注意:只有当所有列表项的高度相同时才使用 itemExtent,否则会导致布局错误。

3.5 物理效果 physics

physics 属性控制列表的滚动物理效果,如是否回弹、是否允许滚动等。

ListView(
  physics: const BouncingScrollPhysics(), // iOS 风格回弹
  children: const [
    Text('项目 1'),
  ],
)

常用的 ScrollPhysics:

说明 适用场景
BouncingScrollPhysics iOS 风格,超出边界会回弹 iOS 应用
ClampingScrollPhysics Android 风格,超出边界有发光效果 Android 应用
NeverScrollableScrollPhysics 禁止滚动 静态列表
AlwaysScrollableScrollPhysics 始终可滚动,即使内容不足 需要下拉刷新

四、ScrollController 滚动控制

4.1 基础用法

ScrollController 可以用来控制 ListView 的滚动位置,监听滚动事件。

class MyListView extends StatefulWidget {
  const MyListView({super.key});

  
  State<MyListView> createState() => _MyListViewState();
}

class _MyListViewState extends State<MyListView> {
  final ScrollController _controller = ScrollController();

  
  void dispose() {
    _controller.dispose(); // 释放控制器
    super.dispose();
  }

  void _scrollToTop() {
    _controller.animateTo(
      0,
      duration: const Duration(milliseconds: 500),
      curve: Curves.easeInOut,
    );
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: _scrollToTop,
          child: const Text('回到顶部'),
        ),
        Expanded(
          child: ListView.builder(
            controller: _controller,
            itemCount: 100,
            itemBuilder: (context, index) {
              return ListTile(
                title: Text('项目 ${index + 1}'),
              );
            },
          ),
        ),
      ],
    );
  }
}

4.2 监听滚动事件

通过监听 ScrollController 的变化,可以实现滚动位置检测、滚动到底部加载更多等功能。

class ScrollListenerExample extends StatefulWidget {
  const ScrollListenerExample({super.key});

  
  State<ScrollListenerExample> createState() => _ScrollListenerExampleState();
}

class _ScrollListenerExampleState extends State<ScrollListenerExample> {
  final ScrollController _controller = ScrollController();
  bool _isAtTop = true;

  
  void initState() {
    super.initState();
    _controller.addListener(_scrollListener);
  }

  void _scrollListener() {
    if (_controller.offset <= _controller.position.minScrollExtent &&
        !_controller.position.outOfRange) {
      setState(() {
        _isAtTop = true;
      });
    } else if (_controller.offset >= _controller.position.maxScrollExtent &&
        !_controller.position.outOfRange) {
      // 滚动到底部,可以加载更多
      _loadMore();
    } else {
      setState(() {
        _isAtTop = false;
      });
    }
  }

  void _loadMore() {
    // 加载更多数据的逻辑
  }

  
  void dispose() {
    _controller.removeListener(_scrollListener);
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('滚动监听示例'),
      ),
      floatingActionButton: _isAtTop
          ? null
          : FloatingActionButton(
              onPressed: () {
                _controller.animateTo(0,
                    duration: const Duration(milliseconds: 500),
                    curve: Curves.easeInOut);
              },
              child: const Icon(Icons.arrow_upward),
            ),
      body: ListView.builder(
        controller: _controller,
        itemCount: 100,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('项目 ${index + 1}'),
          );
        },
      ),
    );
  }
}

五、下拉刷新与上拉加载

5.1 RefreshIndicator 下拉刷新

RefreshIndicator 是 Flutter 提供的下拉刷新组件,Material Design 风格的下拉刷新效果。

class RefreshExample extends StatefulWidget {
  const RefreshExample({super.key});

  
  State<RefreshExample> createState() => _RefreshExampleState();
}

class _RefreshExampleState extends State<RefreshExample> {
  final List<String> _items = List.generate(20, (index) => '项目 ${index + 1}');

  Future<void> _refresh() async {
    await Future.delayed(const Duration(seconds: 1));
    setState(() {
      _items.insert(0, '新项目 ${DateTime.now().millisecondsSinceEpoch}');
    });
  }

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

5.2 上拉加载更多

通过监听滚动到底部事件,实现上拉加载更多功能。

class LoadMoreExample extends StatefulWidget {
  const LoadMoreExample({super.key});

  
  State<LoadMoreExample> createState() => _LoadMoreExampleState();
}

class _LoadMoreExampleState extends State<LoadMoreExample> {
  final ScrollController _controller = ScrollController();
  final List<String> _items = List.generate(20, (index) => '项目 ${index + 1}');
  bool _isLoading = false;

  
  void initState() {
    super.initState();
    _controller.addListener(_scrollListener);
  }

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

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

    await Future.delayed(const Duration(seconds: 1));

    setState(() {
      _items.addAll(List.generate(10, (index) => '项目 ${_items.length + index + 1}'));
      _isLoading = false;
    });
  }

  
  void dispose() {
    _controller.removeListener(_scrollListener);
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _controller,
      itemCount: _items.length + (_isLoading ? 1 : 0),
      itemBuilder: (context, index) {
        if (index == _items.length) {
          return const Padding(
            padding: EdgeInsets.all(16),
            child: Center(child: CircularProgressIndicator()),
          );
        }
        return ListTile(
          title: Text(_items[index]),
        );
      },
    );
  }
}

六、常用列表项组件

6.1 ListTile

ListTile 是 Material Design 的标准列表项,支持标题、副标题、图标等多种元素。

ListTile(
  leading: const CircleAvatar(
    backgroundImage: NetworkImage('https://picsum.photos/100/100'),
  ),
  title: const Text('用户名称'),
  subtitle: const Text('用户简介信息'),
  trailing: const Icon(Icons.chevron_right),
  onTap: () {
    // 点击事件
  },
)

ListTile 的主要属性:

属性 类型 说明
leading Widget? 前置图标
title Widget? 标题
subtitle Widget? 副标题
trailing Widget? 后置图标
onTap VoidCallback? 点击事件

6.2 Card

Card 可以为列表项添加卡片效果,提升视觉层次。

Card(
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  child: ListTile(
    title: const Text('卡片列表项'),
    subtitle: const Text('这是卡片样式的列表项'),
  ),
)

6.3 自定义列表项

除了使用标准组件,也可以完全自定义列表项的样式。

Container(
  margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  padding: const EdgeInsets.all(16),
  decoration: BoxDecoration(
    gradient: const LinearGradient(
      colors: [Colors.blue, Colors.purple],
    ),
    borderRadius: BorderRadius.circular(12),
  ),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: const [
      Text(
        '自定义列表项',
        style: TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.bold,
          color: Colors.white,
        ),
      ),
      SizedBox(height: 8),
      Text(
        '这是完全自定义样式的列表项',
        style: TextStyle(
          fontSize: 14,
          color: Colors.white70,
        ),
      ),
    ],
  ),
)

七、性能优化

7.1 使用 ListView.builder

对于大量数据,务必使用 ListView.builder 而不是默认构造函数。

// ❌ 性能差:创建所有子组件
ListView(
  children: List.generate(1000, (index) => ListTile(title: Text('项目 $index'))),
)

// ✅ 性能好:按需创建子组件
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) => ListTile(title: Text('项目 $index')),
)

7.2 使用 itemExtent

如果所有列表项的高度相同,使用 itemExtent 可以显著提升性能。

ListView.builder(
  itemExtent: 56, // 固定高度
  itemCount: 1000,
  itemBuilder: (context, index) => ListTile(title: Text('项目 $index')),
)

7.3 避免在 itemBuilder 中创建复杂对象

将复杂对象的创建移到 itemBuilder 外部,避免重复创建。

class OptimizedListView extends StatelessWidget {
  final List<String> _titles = List.generate(100, (index) => '标题 $index');

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _titles.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(_titles[index]), // 直接使用预先创建的数据
        );
      },
    );
  }
}

7.4 使用 const 构造函数

对于不变的子组件,使用 const 构造函数。

ListView.builder(
  itemCount: 10,
  itemBuilder: (context, index) {
    return const ListTile(
      leading: Icon(Icons.star),
      title: Text('固定内容'),
    );
  },
)

八、实际应用场景

8.1 联系人列表

ListView.builder(
  itemCount: contacts.length,
  itemBuilder: (context, index) {
    final contact = contacts[index];
    return ListTile(
      leading: CircleAvatar(
        child: Text(contact.name[0]),
      ),
      title: Text(contact.name),
      subtitle: Text(contact.phone),
      onTap: () {
        // 打开联系人详情
      },
    );
  },
)

8.2 新闻列表

ListView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: news.length,
  itemBuilder: (context, index) {
    final item = news[index];
    return Card(
      margin: const EdgeInsets.only(bottom: 16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Image.network(item.imageUrl),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  item.title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  item.summary,
                  style: TextStyle(color: Colors.grey[600]),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  },
)

8.3 设置页面

ListView.separated(
  itemCount: settings.length,
  separatorBuilder: (context, index) => const Divider(),
  itemBuilder: (context, index) {
    final setting = settings[index];
    return ListTile(
      leading: Icon(setting.icon),
      title: Text(setting.title),
      trailing: const Icon(Icons.chevron_right),
      onTap: () {
        // 打开设置页面
      },
    );
  },
)

九、完整示例代码

下面是一个完整的 Flutter 应用示例,展示 ListView 组件的各种用法。

import 'package:flutter/material.dart';

void main() {
  runApp(const ListViewDemo());
}

class ListViewDemo extends StatelessWidget {
  const ListViewDemo({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ListView 组件演示',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.dark(
          primary: const Color(0xFF6366F1),
          secondary: const Color(0xFF8B5CF6),
          surface: const Color(0xFF1E293B),
          background: const Color(0xFF0F172A),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const ListViewPage(),
    );
  }
}

class ListViewPage extends StatelessWidget {
  const ListViewPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0F172A),
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(20),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 标题区域
              Container(
                padding: const EdgeInsets.all(24),
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                    colors: [
                      const Color(0xFF6366F1).withOpacity(0.2),
                      const Color(0xFF8B5CF6).withOpacity(0.2),
                    ],
                  ),
                  borderRadius: BorderRadius.circular(20),
                  border: Border.all(
                    color: Colors.white.withOpacity(0.1),
                  ),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text(
                      '📜 ListView',
                      style: TextStyle(
                        fontSize: 28,
                        fontWeight: FontWeight.bold,
                        color: Colors.white,
                        letterSpacing: 0.5,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      '探索 Flutter 中滚动列表的各种用法',
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.white.withOpacity(0.7),
                        height: 1.5,
                      ),
                    ),
                  ],
                ),
              ),

              const SizedBox(height: 32),

              // 基础列表
              _buildSection(
                title: '基础列表',
                icon: Icons.list,
                color: Colors.blue,
                child: _buildListCard([
                  SizedBox(
                    height: 200,
                    child: ListView(
                      children: const [
                        ListTile(
                          leading: Icon(Icons.star, color: Colors.blue),
                          title: Text('基础列表项 1'),
                        ),
                        ListTile(
                          leading: Icon(Icons.star, color: Colors.blue),
                          title: Text('基础列表项 2'),
                        ),
                        ListTile(
                          leading: Icon(Icons.star, color: Colors.blue),
                          title: Text('基础列表项 3'),
                        ),
                        ListTile(
                          leading: Icon(Icons.star, color: Colors.blue),
                          title: Text('基础列表项 4'),
                        ),
                        ListTile(
                          leading: Icon(Icons.star, color: Colors.blue),
                          title: Text('基础列表项 5'),
                        ),
                      ],
                    ),
                  ),
                ]),
              ),

              const SizedBox(height: 24),

              // Builder 列表
              _buildSection(
                title: 'ListView.builder',
                icon: Icons.build,
                color: Colors.green,
                child: _buildListCard([
                  SizedBox(
                    height: 200,
                    child: ListView.builder(
                      itemCount: 20,
                      itemBuilder: (context, index) {
                        return ListTile(
                          leading: CircleAvatar(
                            backgroundColor: Colors.green.withOpacity(0.3),
                            child: Text(
                              '${index + 1}',
                              style: const TextStyle(color: Colors.white),
                            ),
                          ),
                          title: Text('动态生成项 ${index + 1}'),
                          subtitle: Text('这是第 ${index + 1} 个列表项'),
                        );
                      },
                    ),
                  ),
                ]),
              ),

              const SizedBox(height: 24),

              // Separated 列表
              _buildSection(
                title: 'ListView.separated',
                icon: Icons.horizontal_rule,
                color: Colors.purple,
                child: _buildListCard([
                  SizedBox(
                    height: 200,
                    child: ListView.separated(
                      itemCount: 5,
                      separatorBuilder: (context, index) => Divider(
                        color: Colors.white.withOpacity(0.1),
                      ),
                      itemBuilder: (context, index) {
                        return ListTile(
                          title: Text('带分隔符的项 ${index + 1}'),
                          trailing: const Icon(Icons.arrow_forward_ios, size: 16),
                        );
                      },
                    ),
                  ),
                ]),
              ),

              const SizedBox(height: 24),

              // 水平滚动
              _buildSection(
                title: '水平滚动',
                icon: Icons.swap_horiz,
                color: Colors.orange,
                child: _buildListCard([
                  SizedBox(
                    height: 120,
                    child: ListView.builder(
                      scrollDirection: Axis.horizontal,
                      itemCount: 10,
                      itemBuilder: (context, index) {
                        return Container(
                          width: 100,
                          margin: const EdgeInsets.only(right: 12),
                          decoration: BoxDecoration(
                            gradient: LinearGradient(
                              colors: [
                                Colors.orange.withOpacity(0.3),
                                Colors.red.withOpacity(0.3),
                              ],
                            ),
                            borderRadius: BorderRadius.circular(12),
                          ),
                          child: Center(
                            child: Text(
                              '项 ${index + 1}',
                              style: const TextStyle(color: Colors.white),
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                ]),
              ),

              const SizedBox(height: 24),

              // 卡片列表
              _buildSection(
                title: '卡片列表',
                icon: Icons.style,
                color: Colors.pink,
                child: _buildListCard([
                  SizedBox(
                    height: 250,
                    child: ListView.builder(
                      padding: const EdgeInsets.symmetric(horizontal: 8),
                      itemCount: 5,
                      itemBuilder: (context, index) {
                        return Card(
                          margin: const EdgeInsets.only(bottom: 12),
                          child: Padding(
                            padding: const EdgeInsets.all(16),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Row(
                                  children: [
                                    CircleAvatar(
                                      backgroundColor: Colors.pink.withOpacity(0.3),
                                      child: Icon(
                                        Icons.person,
                                        color: Colors.pink,
                                        size: 20,
                                      ),
                                    ),
                                    const SizedBox(width: 12),
                                    const Expanded(
                                      child: Text(
                                        '卡片标题',
                                        style: TextStyle(
                                          fontSize: 16,
                                          fontWeight: FontWeight.bold,
                                        ),
                                      ),
                                    ),
                                  ],
                                ),
                                const SizedBox(height: 8),
                                Text(
                                  '这是第 ${index + 1} 张卡片的内容描述,展示了卡片列表的使用方式。',
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.white.withOpacity(0.7),
                                  ),
                                ),
                              ],
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                ]),
              ),

              const SizedBox(height: 24),

              // 实际应用
              _buildSection(
                title: '实际应用示例',
                icon: Icons.apps,
                color: Colors.cyan,
                child: _buildListCard([
                  const ContactListExample(),
                  const SizedBox(height: 16),
                  const SettingsListExample(),
                ]),
              ),

              const SizedBox(height: 80),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildSection({
    required String title,
    required IconData icon,
    required Color color,
    required Widget child,
  }) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: color.withOpacity(0.2),
                borderRadius: BorderRadius.circular(10),
              ),
              child: Icon(icon, color: color, size: 20),
            ),
            const SizedBox(width: 12),
            Text(
              title,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.w600,
                color: Colors.white,
              ),
            ),
          ],
        ),
        const SizedBox(height: 12),
        child,
      ],
    );
  }

  Widget _buildListCard(List<Widget> children) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        color: Colors.white.withOpacity(0.03),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(
          color: Colors.white.withOpacity(0.05),
        ),
      ),
      child: Column(
        children: children,
      ),
    );
  }
}

class ContactListExample extends StatelessWidget {
  const ContactListExample({super.key});

  final List<Map<String, dynamic>> _contacts = const [
    {'name': '张三', 'phone': '138-0000-0001'},
    {'name': '李四', 'phone': '138-0000-0002'},
    {'name': '王五', 'phone': '138-0000-0003'},
    {'name': '赵六', 'phone': '138-0000-0004'},
  ];

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '联系人列表',
          style: TextStyle(
            fontSize: 14,
            color: Colors.white.withOpacity(0.7),
          ),
        ),
        const SizedBox(height: 12),
        SizedBox(
          height: 200,
          child: ListView.builder(
            itemCount: _contacts.length,
            itemBuilder: (context, index) {
              final contact = _contacts[index];
              return ListTile(
                leading: CircleAvatar(
                  backgroundColor: Colors.cyan.withOpacity(0.3),
                  child: Text(
                    contact['name'][0],
                    style: const TextStyle(color: Colors.white),
                  ),
                ),
                title: Text(contact['name']),
                subtitle: Text(contact['phone']),
                trailing: const Icon(Icons.call, color: Colors.cyan),
              );
            },
          ),
        ),
      ],
    );
  }
}

class SettingsListExample extends StatelessWidget {
  const SettingsListExample({super.key});

  final List<Map<String, dynamic>> _settings = const [
    {'icon': Icons.wifi, 'title': 'Wi-Fi', 'subtitle': '已连接'},
    {'icon': Icons.bluetooth, 'title': '蓝牙', 'subtitle': '已开启'},
    {'icon': Icons.notifications, 'title': '通知', 'subtitle': '已允许'},
    {'icon': Icons.lock, 'title': '隐私', 'subtitle': '管理权限'},
  ];

  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          '设置列表',
          style: TextStyle(
            fontSize: 14,
            color: Colors.white.withOpacity(0.7),
          ),
        ),
        const SizedBox(height: 12),
        SizedBox(
          height: 200,
          child: ListView.separated(
            itemCount: _settings.length,
            separatorBuilder: (context, index) => Divider(
              color: Colors.white.withOpacity(0.1),
              height: 1,
            ),
            itemBuilder: (context, index) {
              final setting = _settings[index];
              return ListTile(
                leading: Icon(
                  setting['icon'],
                  color: Colors.cyan,
                ),
                title: Text(setting['title']),
                subtitle: Text(setting['subtitle']),
                trailing: const Icon(Icons.chevron_right, size: 20),
              );
            },
          ),
        ),
      ],
    );
  }
}

十、总结

ListView 是 Flutter 中最常用的滚动容器组件,掌握它的各种用法对于构建数据密集型应用至关重要。

🎯 核心要点

  • 构造方式:ListView(默认)、builder(大量数据)、separated(带分隔符)、custom(自定义)
  • 懒加载机制:只渲染可见区域,性能优异
  • ScrollController:控制滚动位置,监听滚动事件
  • 下拉刷新:使用 RefreshIndicator 实现
  • 上拉加载:监听滚动到底部事件实现
  • 性能优化:使用 builder、itemExtent、避免重复创建对象

📚 使用建议

场景 推荐方案
少量数据(<20) ListView 默认构造函数
大量数据 ListView.builder
需要分隔符 ListView.separated
联系人列表 ListTile + CircleAvatar
新闻列表 Card + 图片 + 文字
设置页面 ListTile.separated
图片轮播 ListView(水平滚动)
下拉刷新 RefreshIndicator

💡 最佳实践:对于任何可能包含大量数据的列表,都应使用 ListView.builder 而不是默认构造函数。合理使用 ScrollController 可以实现更多交互效果,如回到顶部、滚动监听等。通过合理的设计和优化,ListView 可以高效地展示成千上万条数据,为用户提供流畅的滚动体验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐