2.7 列表与滚动性能优化

列表是移动 App 中最核心的 UI 组件。掌握 ListView、ScrollController、分页加载和下拉刷新,是构建流畅用户体验的基础。


一、ListView 的几种构建方式

1.1 ListView(直接构建,适合少量数据)

dart 复制代码
// ❌ 一次性构建所有子 Widget(数据量大时会卡顿)
ListView(
  children: allProducts.map((p) => ProductCard(product: p)).toList(),
)

1.2 ListView.builder(懒加载,推荐)

dart 复制代码
// ✅ 按需构建可见区域内的子 Widget
ListView.builder(
  itemCount: products.length,
  itemExtent: 80, // 固定高度 → 性能更好(跳过 Layout 计算)
  itemBuilder: (context, index) {
    return ProductListTile(product: products[index]);
  },
)

1.3 ListView.separated(带分割线)

dart 复制代码
ListView.separated(
  itemCount: products.length,
  separatorBuilder: (_, __) => const Divider(height: 1),
  itemBuilder: (context, index) {
    return ProductListTile(product: products[index]);
  },
)

1.4 GridView.builder(网格列表)

dart 复制代码
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
    childAspectRatio: 0.75,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ProductGridCard(product: products[index]);
  },
)

二、ScrollController

dart 复制代码
class InfiniteListPage extends StatefulWidget { ... }

class _InfiniteListPageState extends State<InfiniteListPage> {
  final _scrollController = ScrollController();
  bool _showBackToTop = false;

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

  void _onScroll() {
    // 显示/隐藏"回到顶部"按钮
    final showButton = _scrollController.offset > 500;
    if (showButton != _showBackToTop) {
      setState(() => _showBackToTop = showButton);
    }

    // ⭐ 滚动到底部时加载更多
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent - 200) {
      _loadMore();
    }
  }

  void _scrollToTop() {
    _scrollController.animateTo(
      0,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeOut,
    );
  }

  // 滚动到指定位置
  void _scrollToItem(int index) {
    const itemHeight = 80.0;
    _scrollController.animateTo(
      index * itemHeight,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeOut,
    );
  }

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

三、分页加载(Pagination)

dart 复制代码
class PaginatedProductList extends StatefulWidget { ... }

class _PaginatedProductListState extends State<PaginatedProductList> {
  final List<Product> _products = [];
  int _currentPage = 1;
  bool _isLoading = false;
  bool _hasMore = true;

  @override
  void initState() {
    super.initState();
    _loadPage(_currentPage);
  }

  Future<void> _loadPage(int page) async {
    if (_isLoading || !_hasMore) return;

    setState(() => _isLoading = true);

    try {
      final result = await ProductApi.fetchProducts(page: page, pageSize: 20);
      setState(() {
        _products.addAll(result.items);
        _hasMore = result.hasMore;
        _currentPage = page;
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('加载失败:$e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  void _loadMore() => _loadPage(_currentPage + 1);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _products.length + (_hasMore ? 1 : 0),
      itemBuilder: (context, index) {
        // 最后一个 item 显示加载指示器
        if (index == _products.length) {
          _loadMore();
          return const Padding(
            padding: EdgeInsets.all(16),
            child: Center(child: CircularProgressIndicator()),
          );
        }
        return ProductListTile(product: _products[index]);
      },
    );
  }
}

四、下拉刷新

4.1 RefreshIndicator(Material 风格)

dart 复制代码
RefreshIndicator(
  onRefresh: () async {
    setState(() {
      _currentPage = 1;
      _products.clear();
      _hasMore = true;
    });
    await _loadPage(1);
  },
  displacement: 50,
  color: Theme.of(context).colorScheme.primary,
  child: ListView.builder(
    physics: const AlwaysScrollableScrollPhysics(), // 确保可滚动才能下拉
    itemCount: _products.length,
    itemBuilder: (_, index) => ProductListTile(product: _products[index]),
  ),
)

4.2 CupertinoSliverRefreshControl(iOS 风格)

dart 复制代码
CustomScrollView(
  slivers: [
    CupertinoSliverRefreshControl(
      refreshTriggerPullDistance: 100,
      refreshIndicatorExtent: 60,
      onRefresh: () async {
        await _refreshData();
      },
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) => ProductListTile(product: _products[index]),
        childCount: _products.length,
      ),
    ),
  ],
)

4.3 easy_refresh(功能完整,推荐)

yaml 复制代码
dependencies:
  easy_refresh: ^3.3.4
dart 复制代码
EasyRefresh(
  header: const ClassicHeader(
    dragText: '下拉刷新',
    armedText: '释放刷新',
    readyText: '刷新中...',
    processingText: '刷新中...',
    processedText: '刷新完成',
  ),
  footer: const ClassicFooter(
    dragText: '上拉加载更多',
    readyText: '加载中...',
    processedText: '加载完成',
    noMoreText: '没有更多了',
  ),
  onRefresh: () async {
    await _refresh();
  },
  onLoad: () async {
    await _loadMore();
    return _hasMore ? IndicatorResult.success : IndicatorResult.noMore;
  },
  child: ListView.builder(
    itemCount: _products.length,
    itemBuilder: (_, index) => ProductCard(product: _products[index]),
  ),
)

五、列表性能优化

5.1 指定 itemExtent(固定高度)

dart 复制代码
// ❌ 不指定:Flutter 需要逐个计算每个 Item 高度
ListView.builder(
  itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
)

// ✅ 指定 itemExtent:跳过 Layout 计算,直接定位
ListView.builder(
  itemExtent: 72, // 每个 Item 高度固定 72
  itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
)

// ✅ 或使用 prototypeItem(由第一个 Item 推断高度)
ListView.builder(
  prototypeItem: const ListTile(title: Text('Prototype')),
  itemBuilder: (_, index) => ListTile(title: Text('Item $index')),
)

5.2 const + Key

dart 复制代码
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ProductCard(
      key: ValueKey(items[index].id), // ✅ 用 ValueKey 帮助 diff
      product: items[index],
    );
  },
)

5.3 AutomaticKeepAlive(保持 Tab 内列表状态)

dart 复制代码
class _ProductListState extends State<ProductList>
    with AutomaticKeepAliveClientMixin {

  @override
  bool get wantKeepAlive => true; // 切换 Tab 后保留状态

  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用 super.build
    return ListView.builder(...);
  }
}

5.4 缓存区域(cacheExtent)

dart 复制代码
ListView.builder(
  cacheExtent: 500, // 额外缓存 500 像素区域的 Item
  // 默认 250,增大可减少滚动时白屏,但增加内存
  itemBuilder: ...,
)

六、SliverAppBar + 复杂滚动布局

dart 复制代码
CustomScrollView(
  slivers: [
    // 折叠头部
    SliverAppBar(
      expandedHeight: 250,
      pinned: true,
      stretch: true,
      flexibleSpace: FlexibleSpaceBar(
        title: const Text('商品列表'),
        background: Image.network(bannerUrl, fit: BoxFit.cover),
        stretchModes: const [StretchMode.zoomBackground],
      ),
      actions: [IconButton(icon: const Icon(Icons.search), onPressed: _search)],
    ),

    // 固定的吸顶 Tab 栏
    SliverPersistentHeader(
      pinned: true,
      delegate: _StickyTabBarDelegate(
        child: TabBar(
          controller: _tabController,
          tabs: const [Tab(text: '推荐'), Tab(text: '新品'), Tab(text: '热销')],
        ),
      ),
    ),

    // 商品网格
    SliverGrid(
      delegate: SliverChildBuilderDelegate(
        (_, index) => ProductCard(product: products[index]),
        childCount: products.length,
      ),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        childAspectRatio: 0.7,
      ),
    ),

    // 底部加载指示器
    if (_isLoading)
      const SliverToBoxAdapter(
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Center(child: CircularProgressIndicator()),
        ),
      ),
  ],
)

小结

技术点 推荐做法
少量数据 ListView(children: [...])
大量数据 ListView.builder + itemExtent
分页加载 监听 ScrollController 或用 easy_refresh
下拉刷新 RefreshIndicator / EasyRefresh
列表性能 const + ValueKey + cacheExtent + itemExtent
复杂滚动 CustomScrollView + Sliver 系列

👉 下一节:2.8 对话框与弹窗系统

相关推荐
SoaringHeart1 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter
码云之上4 小时前
万星入坞·其三:SDK 轻量组件如何优雅地"点亮"
性能优化·架构·前端框架
这个DBA有点耶5 小时前
SQL改写实战:子查询、CTE、窗口函数性能对比
数据库·mysql·性能优化
程序员老刘5 小时前
Flutter 3.44 有哪些变化?(官方blog完整翻译)
flutter·ai编程·客户端
Gauss松鼠会6 小时前
GaussDB(DWS) 日常维护命令
服务器·数据库·postgresql·性能优化·gaussdb·经验总结
山屿落星辰7 小时前
Flutter 企业级架构设计实战:Clean Architecture + 分层模块化 + 依赖注入全解析
flutter
山屿落星辰9 小时前
Flutter 高级特性实战:动画、自定义绘制、平台通道与 Web 优化
前端·flutter
程序软件分享9 小时前
2026旗舰版 Java+Flutter 期货微交易系统源码全开源多语言平台
flutter·交易所源码·微盘源码·微交易源码
飞龙147756574675010 小时前
Flutter 安全存储插件全面解析:从入门到进阶
flutter
MU在掘金9169510 小时前
从一把梭 SQL 到维度注册:性能分析采集的工程化之路
性能优化