Flutter for OpenHarmony二手物品置换App实战 - 列表性能优化实现

列表是App中最常见的UI组件,商品列表、消息列表、搜索结果等都是列表。当列表数据量大时,性能优化就变得很重要。今天我们来讲解列表性能优化的实现方式。

列表性能问题的来源

列表性能问题主要来自两个方面:一是创建了太多Widget,二是图片加载消耗资源。优化的核心思路是懒加载和缓存。Flutter的渲染机制决定了Widget数量直接影响内存占用和渲染速度,所以控制Widget数量是优化的关键。

使用ListView.builder

dart 复制代码
// 不推荐:一次性创建所有Widget
ListView(
  children: products.map((p) => ProductCard(product: p)).toList(),
)

// 推荐:懒加载,只创建可见区域的Widget
ListView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) => ProductCard(product: products[index]),
)

普通ListView会一次性创建所有子Widget,如果有1000条数据就创建1000个Widget,内存直接爆炸。ListView.builder采用懒加载策略,只创建屏幕可见区域的Widget,滚动时动态创建新的、销毁离开屏幕的。这是列表优化最基本也是最重要的一步。

使用GridView.builder

商品列表用网格布局:

dart 复制代码
GridView.builder(
  padding: const EdgeInsets.all(12),
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    childAspectRatio: 0.65,
    crossAxisSpacing: 10,
    mainAxisSpacing: 10,
  ),
  itemCount: _products.length,
  itemBuilder: (context, index) => _buildProductCard(_products[index]),
)

GridView.builder和ListView.builder原理一样,都是懒加载。gridDelegate定义网格的布局规则,crossAxisCount设置每行显示2个,childAspectRatio是宽高比,spacing是间距。这些参数都用const修饰,避免每次build都创建新对象。

使用SliverGrid

CustomScrollView中使用SliverGrid

dart 复制代码
CustomScrollView(
  slivers: [
    SliverToBoxAdapter(child: _buildBanner()),
    SliverToBoxAdapter(child: _buildCategories()),
    SliverPadding(
      padding: const EdgeInsets.all(12),
      sliver: SliverGrid(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 0.65,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
        delegate: SliverChildBuilderDelegate(
          (context, index) => _buildProductCard(_products[index]),
          childCount: _products.length,
        ),
      ),
    ),
  ],
)

首页通常有banner、分类导航、商品列表等多个部分,用CustomScrollView可以把它们组合成一个可滚动的整体。SliverChildBuilderDelegate实现懒加载,效果和GridView.builder一样。这种写法更灵活,可以在列表前后插入任意内容。

图片缓存

使用cached_network_image缓存网络图片:

dart 复制代码
CachedNetworkImage(
  imageUrl: product['image'],
  fit: BoxFit.cover,
  placeholder: (context, url) => Container(
    color: Colors.grey[200],
    child: const Center(child: CircularProgressIndicator()),
  ),
  errorWidget: (context, url, error) => Container(
    color: Colors.grey[200],
    child: Icon(Icons.image, size: 60, color: Colors.grey[400]),
  ),
)

网络图片是列表性能的大敌,每次滚动都重新下载会很卡。CachedNetworkImage会把下载的图片缓存到本地,下次显示直接从缓存读取。placeholder在加载时显示占位内容,errorWidget在加载失败时显示错误提示,用户体验更好。

图片尺寸优化

加载适合显示尺寸的图片,不要加载原图:

dart 复制代码
// 假设服务器支持图片尺寸参数
String getImageUrl(String url, {int width = 200}) {
  return '$url?w=$width';
}

CachedNetworkImage(
  imageUrl: getImageUrl(product['image'], width: 200),
  // ...
)

商品卡片可能只有200像素宽,但原图可能是2000像素。加载原图不仅浪费流量,还会占用大量内存。让服务器返回合适尺寸的图片,或者使用CDN的图片处理功能,能大幅减少资源消耗。

使用const构造函数

能用const的地方都用const

dart 复制代码
// 不推荐
Container(
  padding: EdgeInsets.all(16),
  child: Text('文字'),
)

// 推荐
Container(
  padding: const EdgeInsets.all(16),
  child: const Text('文字'),
)

const对象在编译期就创建好了,运行时直接复用,不需要每次build都重新创建。这个优化看起来微不足道,但在列表中累积起来效果很明显。养成习惯,能加const的地方都加上。

避免在build中创建对象

dart 复制代码
// 不推荐:每次build都创建新的列表
@override
Widget build(BuildContext context) {
  final categories = [
    {'icon': Icons.phone_android, 'name': '数码'},
    {'icon': Icons.checkroom, 'name': '服饰'},
    // ...
  ];
  return // ...
}

// 推荐:在State中定义,只创建一次
class _HomePageState extends State<HomePage> {
  final List<Map<String, dynamic>> _categories = [
    {'icon': Icons.phone_android, 'name': '数码'},
    {'icon': Icons.checkroom, 'name': '服饰'},
    // ...
  ];
  
  @override
  Widget build(BuildContext context) {
    return // ...
  }
}

build方法可能会被频繁调用,每次调用都创建新对象会产生大量垃圾回收压力。把不变的数据定义在State中,或者用final修饰,只创建一次就够了。

使用RepaintBoundary

对于复杂的Widget,用RepaintBoundary隔离重绘:

dart 复制代码
RepaintBoundary(
  child: _buildProductCard(product),
)

Flutter默认会把相邻的Widget合并到一个图层一起绘制,一个Widget变化可能导致整个区域重绘。RepaintBoundary创建独立的图层,内部变化不会影响外部,外部变化也不会导致内部重绘。对于复杂的商品卡片,这个优化很有效。

使用AutomaticKeepAliveClientMixin

PageViewTabBarView中保持页面状态:

dart 复制代码
class _HomePageState extends State<HomePage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;
  
  @override
  Widget build(BuildContext context) {
    super.build(context);  // 必须调用
    return // ...
  }
}

默认情况下,切换Tab时不可见的页面会被销毁,再切回来要重新加载。使用AutomaticKeepAliveClientMixin可以保持页面状态,滚动位置、加载的数据都会保留。注意build方法里必须调用super.build(context)。

分页加载

不要一次加载所有数据,分页加载:

dart 复制代码
class _HomePageState extends State<HomePage> {
  List<Map<String, dynamic>> _products = [];
  int _page = 1;
  bool _hasMore = true;
  bool _isLoadingMore = false;

  Future<void> _loadMoreProducts() async {
    if (_isLoadingMore || !_hasMore) return;
    
    setState(() => _isLoadingMore = true);
    
    try {
      _page++;
      final data = await Api.getProducts(page: _page);
      setState(() {
        _products.addAll(data);
        _hasMore = data.length >= 20;
        _isLoadingMore = false;
      });
    } catch (e) {
      _page--;
      setState(() => _isLoadingMore = false);
    }
  }
}

一次加载1000条数据,不管是网络传输还是内存占用都是灾难。分页加载每次只请求20条,滚动到底部时再加载下一页。_isLoadingMore防止重复请求,_hasMore判断是否还有更多数据,加载失败时回退页码。

使用itemExtent

如果列表项高度固定,指定itemExtent

dart 复制代码
ListView.builder(
  itemExtent: 100,  // 每项高度100
  itemCount: products.length,
  itemBuilder: (context, index) => ProductCard(product: products[index]),
)

指定itemExtent后,Flutter不需要调用每个子Widget的layout来计算高度,可以直接通过数学计算确定哪些项在可见区域。这对于长列表的滚动性能提升很明显。

小结

这篇讲解了列表性能优化的实现方式,包括使用builder懒加载、图片缓存、const构造函数、RepaintBoundary、分页加载等。性能优化能让App更流畅,提升用户体验。记住优化的核心思路:减少Widget数量、减少重绘、减少内存占用。


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

相关推荐
前端不太难15 分钟前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 小时前
flutter列表中实现置顶动画
flutter
始持2 小时前
第十二讲 风格与主题统一
前端·flutter
始持2 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持2 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜2 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
我是唐青枫2 小时前
深入理解 C#.NET Task.Run:调度原理、线程池机制与性能优化
性能优化·c#·.net
ん贤3 小时前
首屏优化实践:如何将 Vue3 + Vite 项目的加载速度提升3倍
性能优化·vue·vite
黄林晴3 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
海山数据库3 小时前
移动云大云海山数据库分页查询性能优化时间:从16s到2ms
数据库·oracle·性能优化·he3db·大云海山数据库