基础入门 Flutter for OpenHarmony:ListView 列表组件详解

🎯欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
📜 本文将带你深入掌握 Flutter 中最常用的滚动列表组件------ListView,从基础用法到高级技巧,全面了解列表展示的各种方式。


一、ListView 组件概述

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

🎯 为什么 ListView 如此重要?

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

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

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


二、ListView 的构造方式

2.1 ListView 默认构造函数

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

dart 复制代码
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 回调按需创建子组件,适合展示大量数据。

dart 复制代码
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 类似,但可以在列表项之间添加分隔符。

dart 复制代码
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 来控制列表的渲染行为。

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

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


三、ListView 的常用属性

3.1 滚动方向 scrollDirection

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

dart 复制代码
// 垂直滚动(默认)
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 属性可以让列表从底部或右侧开始滚动。

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

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

3.3 内边距 padding

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

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

3.4 固定高度 itemExtent

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

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

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

3.5 物理效果 physics

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

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

常用的 ScrollPhysics:

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

四、ScrollController 滚动控制

4.1 基础用法

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

dart 复制代码
class MyListView extends StatefulWidget {
  const MyListView({super.key});

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

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

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

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

  @override
  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 的变化,可以实现滚动位置检测、滚动到底部加载更多等功能。

dart 复制代码
class ScrollListenerExample extends StatefulWidget {
  const ScrollListenerExample({super.key});

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

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

  @override
  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() {
    // 加载更多数据的逻辑
  }

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

  @override
  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 风格的下拉刷新效果。

dart 复制代码
class RefreshExample extends StatefulWidget {
  const RefreshExample({super.key});

  @override
  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}');
    });
  }

  @override
  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 上拉加载更多

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

dart 复制代码
class LoadMoreExample extends StatefulWidget {
  const LoadMoreExample({super.key});

  @override
  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;

  @override
  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;
    });
  }

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

  @override
  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 的标准列表项,支持标题、副标题、图标等多种元素。

dart 复制代码
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 可以为列表项添加卡片效果,提升视觉层次。

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

6.3 自定义列表项

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

dart 复制代码
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 而不是默认构造函数。

dart 复制代码
// ❌ 性能差:创建所有子组件
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 可以显著提升性能。

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

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

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

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

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

7.4 使用 const 构造函数

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

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

八、实际应用场景

8.1 联系人列表

dart 复制代码
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 新闻列表

dart 复制代码
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 设置页面

dart 复制代码
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 组件的各种用法。

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

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

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

  @override
  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});

  @override
  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'},
  ];

  @override
  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': '管理权限'},
  ];

  @override
  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 可以高效地展示成千上万条数据,为用户提供流畅的滚动体验。

相关推荐
萧曵 丶7 小时前
Vue 中父子组件之间最常用的业务交互场景
javascript·vue.js·交互
Amumu121388 小时前
Vue3扩展(二)
前端·javascript·vue.js
NEXT068 小时前
JavaScript进阶:深度剖析函数柯里化及其在面试中的底层逻辑
前端·javascript·面试
微祎_10 小时前
Flutter for OpenHarmony:链迹 - 基于Flutter的会话级快速链接板极简实现方案
flutter
微祎_10 小时前
Flutter for OpenHarmony:魔方计时器开发实战 - 基于Flutter的专业番茄工作法应用实现与交互设计
flutter·交互
牛奶10 小时前
你不知道的 JS(上):原型与行为委托
前端·javascript·编译原理
牛奶10 小时前
你不知道的JS(上):this指向与对象基础
前端·javascript·编译原理
牛奶10 小时前
你不知道的JS(上):作用域与闭包
前端·javascript·电子书
pas13611 小时前
45-mini-vue 实现代码生成三种联合类型
前端·javascript·vue.js
颜酱12 小时前
数组双指针部分指南 (快慢·左右·倒序)
javascript·后端·算法