[Flutter 基础] - Flutter核心布局组件 - ListView

491 阅读4分钟

在Flutter 中 ListView 组件是Flutter中的核心组件之一,在日常开发中也是使用比较频繁的一个组件,相比Columnrow,它自带滚动效果,而且还支持动态加载数据。它非常适合展示长列表数据,因为它只构建那些当前可见的项,这大大提高了性能。


一、基本概念

ListView 是 Flutter 中最常用的滚动列表组件,用于高效展示大量数据。支持垂直和水平滚动,提供多种构造方法适应不同场景。


二、核心构造方法

1. 默认构造 ListView()

直接传入子组件列表,适合少量静态数据:

image.png

ListView(
  children: <Widget>[
    ListTile(title: Text("Item 1")),
    ListTile(title: Text("Item 2")),
    // ...
  ],
)

2. 动态构建 ListView.builder()

通过索引动态生成子项,适合大数据集(懒加载):

image.png

ListView.builder(
  itemCount: 100, // 总项数
  itemBuilder: (context, index) {
    return ListTile(title: Text("Item $index"));
  },
)

3. 带分隔线 ListView.separated()

自动添加自定义分隔线,适合需要间隔的列表:

image.png

ListView.separated(
  itemCount: 50,
  separatorBuilder: (_, __) => Divider(height: 1),
  itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
)

4. 自定义布局 ListView.custom()

完全控制子组件布局,需指定 childrenDelegate: 通过自己的逻辑去展示子组件,尤其是针对一些包含多种不同类型布局的列表。

image.png

ListView.custom(
  childrenDelegate: SliverChildBuilderDelegate((_, index) {
    if (index % 2 == 0) {
      return Container(
        color: Colors.green,
        alignment: Alignment.centerLeft,
        height: 60,
        child: Text('Item$index'),
      );
    } else {
      return ListTile(title: Text("Item $index"));
    }
  }, childCount: 100),
),

三、核心属性

1. 滚动方向:scrollDirection

  • Axis.vertical(默认垂直滚动)
  • Axis.horizontal(水平滚动)
ListView(
  scrollDirection: Axis.horizontal,
  children: [Container(width: 100), Container(width: 100)],
)

2. 滚动控制:controller

通过 ScrollController 实现滚动监听或跳转:

final controller = ScrollController();

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

// 跳转到底部
controller.jumpTo(controller.position.maxScrollExtent);

3. 边界效果:physics

控制滚动行为:

  • BouncingScrollPhysics():iOS 弹性效果

scrollview_bouncing-ezgif.com-video-to-gif-converter.gif

  • ClampingScrollPhysics():Android 夹紧效果

scrollview_clamping-ezgif.com-video-to-gif-converter.gif

  • NeverScrollableScrollPhysics():禁用滚动

4. 内边距:padding

设置列表内容的内边距:

ListView(padding: EdgeInsets.all(16))

5. 自适应尺寸:shrinkWrap

解决嵌套滚动时的尺寸冲突(默认 false): 有些场景,比如要在column里使用ListView的时候,必须把这个参数设置为true,否则会报错。

Column(
  children: [
    ListView(
     // shrinkWrap: true,  这个注释掉就报错了
      children: <Widget>[
        ListTile(title: Text("Item 1")),
        ListTile(title: Text("Item 2")),
        // ...
      ],
    ),
  ],
),

报错信息

image.png

6. 预加载:cacheExtent

设置预加载区域的高度(像素):

ListView(cacheExtent: 500) // 提升滚动流畅度

四、动态数据加载

1. 配合 RefreshIndicatorScrollController 使用实现分页加载下拉刷新

loadmore-ezgif.com-video-to-gif-converter.gif

  • 分页加载
// 通过监听滚动位置判断是否要加载更多数据。
  void _scrollListener() {
    if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_isLoading) {
      _loadData();
    }
  }
  • 下拉刷新

refresh-ezgif.com-video-to-gif-converter.gif

RefreshIndicator(
  onRefresh: () => _loadData(isRefresh: true), //刷新数据的关键方法
  child: ListView.builder(
    controller: _scrollController,
    itemCount: _items.length + (_isLoading ? 1 : 0), // 如果正在加载,增加一个额外的item用于显示加载指示器
    itemBuilder: (context, index) {
      if (index == _items.length) {
        return Center(child: CircularProgressIndicator()); // 加载更多时的指示器
      }
      return ListTile(
        title: Text(_items[index]),
      );
    },
  ),
),
  • 完整实例
class _SearchPageState extends State<SearchPage> {

  final ScrollController _scrollController = ScrollController();
  final List<String> _items = List.generate(20, (index) => "Item ${index + 1}");
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    // 监听滚动事件以便实现上拉加载更多
    _scrollController.addListener(_scrollListener);
  }

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

  // 模拟数据加载
  Future<void> _loadData({bool isRefresh = false}) async {
    if (!isRefresh) {
      setState(() {
        _isLoading = true;
      });
    }
    // 模拟网络请求延迟
    await Future.delayed(Duration(seconds: 2));
    if (isRefresh) {
      setState(() {
        _items.clear();
        _items.addAll(List.generate(20, (index) => "Refreshed Item ${index + 1}"));
      });
    } else {
      setState(() {
        _items.addAll(List.generate(10, (index) => "Item ${_items.length + index + 1}"));
      });
    }
    setState(() {
      _isLoading = false;
    });
  }

// 通过监听滚动位置判断是否要加载更多数据。
  void _scrollListener() {
    if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && !_isLoading) {
      _loadData();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('下拉刷新与上拉加载示例'),
      ),
      body: RefreshIndicator(
        onRefresh: () => _loadData(isRefresh: true),
        child: ListView.builder(
          controller: _scrollController,
          itemCount: _items.length + (_isLoading ? 1 : 0), // 如果正在加载,增加一个额外的item用于显示加载指示器
          itemBuilder: (context, index) {
            if (index == _items.length) {
              return Center(child: CircularProgressIndicator()); // 加载更多时的指示器
            }
            return ListTile(
              title: Text(_items[index]),
            );
          },
        ),
      ),
    );
  }
}

五、性能优化技巧

1. 使用 const 构造函数

减少 Widget 重建开销:

ListView(
  children: [
    const ListTile(title: Text("Fixed Item 1")),
    const ListTile(title: Text("Fixed Item 2")),
  ],
)

2. 懒加载图片

使用 ListView.builder + Image.networkloadingBuilder

ListView.builder(
  itemBuilder: (_, index) {
    return Image.network(
      imageUrls[index],
      loadingBuilder: (_, child, progress) {
        return progress == null ? child : CircularProgressIndicator();
      },
    );
  },
)

3. 保持列表项状态

使用 AutomaticKeepAliveClientMixin

class KeepAliveItem extends StatefulWidget {
  @override
  _KeepAliveItemState createState() => _KeepAliveItemState();
}

class _KeepAliveItemState extends State<KeepAliveItem>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true; // 保持状态

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListTile(...);
  }
}

扩展用法

聊天列表(反向滚动)

让数据从下往上展示

image.png

ListView.builder(
  reverse: true, // 从底部开始
  itemCount: messages.length,
  itemBuilder: (_, index) => ChatBubble(message: messages[index]),
)

Widget ChatBubble({Map? message}) {
  if(message?['type'] == '2') {
    return Container(
      width: 200,
      height: 60,
      decoration: BoxDecoration(
        color: Colors.green,
        border: Border.all(color: Colors.green, width: 1)
      ),
      alignment: Alignment.centerLeft,
      child: Text(message?['message'], style: TextStyle(color: Colors.white),),
    );
  } else {
    return Container(
      width: 300,
      height: 60,
      alignment: Alignment.centerRight,
      child: Text(message?['message'], textAlign: TextAlign.right,),
    );
  }
}

总结

ListView 是 Flutter 中处理滚动列表的核心组件,通过合理选择构造方法(builder/separated/custom)和优化手段(const/AutomaticKeepAlive),可以高效处理动态数据和大数据集。关键注意点:

  • 大数据集必须使用 ListView.builder 避免内存溢出
  • 嵌套滚动时优先使用 CustomScrollView
  • 通过 cacheExtentitemExtent 提升性能
  • 结合 RefreshIndicator 和分页逻辑实现完整数据流