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 对话框与弹窗系统

相关推荐
2301_822703203 小时前
Flutter 框架跨平台鸿蒙开发 - 气味记忆唤醒应用
flutter·华为·开源·harmonyos·鸿蒙
空中海3 小时前
2.4 绘制与动画
flutter·dart
空中海3 小时前
2.6 表单与输入处理
flutter·dart
AI_零食4 小时前
开源鸿蒙跨平台Flutter开发:脑筋急转弯应用开发文档
flutter·华为·开源·harmonyos·鸿蒙
一江寒逸4 小时前
零基础从入门到精通MySQL(下篇):精通篇——吃透索引底层、锁机制与性能优化,成为MySQL实战高手
数据库·mysql·性能优化
wang09074 小时前
Linux性能优化之中断
linux·运维·性能优化
2301_822703205 小时前
Flutter 框架跨平台鸿蒙开发 - 家庭时间胶囊应用
算法·flutter·华为·图形渲染·harmonyos·鸿蒙
提子拌饭1335 小时前
Flutter 框架跨平台鸿蒙开发 - 声音风景分享应用
flutter·华为·harmonyos·鸿蒙·风景
独特的螺狮粉5 小时前
开源鸿蒙跨平台Flutter开发:超市购物清单应用
flutter·华为·开源·harmonyos·鸿蒙