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

相关推荐
风华圆舞7 小时前
在 Flutter 鸿蒙项目里接入文本转语音的完整思路
flutter·华为·harmonyos
想ai抽7 小时前
Spark Executor 因节点内存超限被杀的分析与应对
大数据·性能优化·spark
青春喂了后端8 小时前
Go Sidecar Status 性能优化
开发语言·性能优化·golang
不喝水就会渴9 小时前
HarmonyOS惰性加载性能优化技术详解(喵屿项目案例)
华为·性能优化·harmonyos
勤劳打代码10 小时前
翻江倒海——滚动布局下拉视图管理
flutter·前端框架·开源
spmcor10 小时前
Flutter 学习笔记 (6):路由与导航 —— 从基础 push/pop 到 go_router
flutter
喵叔哟12 小时前
Week 3 --Day 5:性能优化与监控
人工智能·python·性能优化·langchain
小小工匠1 天前
Redis - 如何使用 Redis 实现分布式锁
redis·性能优化·集群·并发
放下华子我只抽RuiKe51 天前
FastAPI 全栈后端(三):数据库与 ORM
前端·数据库·react.js·oracle·性能优化·前端框架·fastapi