【开源鸿蒙跨平台开发训练营】Flutter框架开发鸿蒙应用的构建与重建优化

发现项目中有很多重复构建的地方需要优化

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

文章目录

  • 实现构建与重建优化
    • 一、目标与方案
    • 二、修改的文件
    • 三、优化说明
      • [3.1 主题/语言/字体合并为单一 Listenable](#3.1 主题/语言/字体合并为单一 Listenable)
      • [3.2 主题数据只创建一次](#3.2 主题数据只创建一次)
      • [3.3 底部 Tab 懒加载](#3.3 底部 Tab 懒加载)
      • [3.4 首页翻页不整页 setState](#3.4 首页翻页不整页 setState)
      • [3.5 发现页搜索防抖](#3.5 发现页搜索防抖)
    • 四、部分代码
      • [4.1 AppSettings 与单一 Notifier(main.dart)](#4.1 AppSettings 与单一 Notifier(main.dart))
      • [4.2 底部 Tab 懒加载(root_page.dart)](#4.2 底部 Tab 懒加载(root_page.dart))
      • [4.3 首页翻页仅更新 notifier(home_page.dart)](#4.3 首页翻页仅更新 notifier(home_page.dart))
      • [4.4 发现页搜索防抖(search_page.dart)](#4.4 发现页搜索防抖(search_page.dart))
    • 五、注意事项
    • 六、小结
    • 结束语

实现构建与重建优化

本次做了如下优化:合并主题/语言/字体为单一 Listenable、主题只创建一次、底部 Tab 懒加载、首页翻页不整页 setState、发现页搜索防抖。


一、目标与方案

  • 目标

    1. 切换主题/语言/字体时,减少从 MaterialApp 起的整树重建,提升帧率与体感。
    2. 主题数据不随每次 build 重新创建,降低 GC 与重算开销。
    3. 底部四个 Tab 仅在首次进入时创建对应页面,降低首屏内存与无关请求。
    4. 首页 PageView 翻页时仅更新必要状态,不触发整页 rebuild。
    5. 发现页搜索输入时通过防抖减少全表过滤与 setState 频率,避免输入卡顿。
  • 大致方向

    • main.dart 中引入 AppSettings(themeMode、locale、fontScale)与单一 ValueNotifier<AppSettings>,只包一层 ValueListenableBuilder;亮/暗主题改为顶层 final,只创建一次。
    • RootPageList<Widget?> _tabPages 按需创建 Tab 页面,在 _onTabTapped_ensureTabCreated(i),首屏仅创建 HomePage
    • HomePageValueNotifier<int> _pageIndexNotifier 保存当前页码,onPageChanged 只写 _pageIndexNotifier.value = index,不调用 setState
    • SearchPage_onSearchChanged 做 250ms 防抖,再执行过滤与 setState

二、修改的文件

文件 说明
lib/main.dart 新增 AppSettingsappSettingsNotifier;主题/暗主题改为顶层 finalMyApp 仅一层 ValueListenableBuilder<AppSettings>
lib/pages/profile_page.dart 设置项与弹窗改为监听/更新 appSettingsNotifier(主题、语言、字体);移除对 appThemeModeNotifier / appLocaleNotifier / appFontScaleNotifier 的引用。
lib/pages/root_page.dart 底部 Tab 懒加载:_tabPages + _ensureTabCreated,仅在首次进入某 Tab 时创建对应页面。
lib/pages/home_page.dart 翻页时用 _pageIndexNotifier.value = index 替代 setState(() => _currentIndex = index),并在 disposedispose 该 notifier。
lib/pages/search_page.dart 搜索防抖:Timer? _searchDebounce_onSearchChanged 内先 cancelTimer(250ms, ...) 执行过滤与 setState;dispose_searchDebounce?.cancel()

三、优化说明

3.1 主题/语言/字体合并为单一 Listenable

  • AppSettings :不可变类,包含 themeModelocalefontScale,提供 copyWith({ themeMode, locale, fontScale })。其中 locale 通过「省略参数」与「显式传 null」区分「不修改」与「设为跟随系统」。
  • appSettingsNotifier :全局 ValueNotifier<AppSettings>,在 main() 中从 SettingsService 读入初始值后创建。
  • MyApp.build :仅一层 ValueListenableBuilder<AppSettings>,内部直接使用 settings.themeModesettings.localesettings.fontScale 配置 MaterialAppMediaQuery.textScaler
  • 设置页 :所有原先读写 appThemeModeNotifier / appLocaleNotifier / appFontScaleNotifier 的地方改为读写 appSettingsNotifier.value(读字段、写 copyWith),并持久化到 SettingsService 不变。

3.2 主题数据只创建一次

  • _lightTheme_darkTheme 由 getter 改为顶层 final 常量,应用生命周期内只创建一次,避免每次 build 重算。

3.3 底部 Tab 懒加载

  • 结构List<Widget?> _tabPages 长度为 4,初始均为 null
  • 创建时机initState 中调用 _ensureTabCreated(0),保证首屏首页存在;用户点击 Tab i 时在 _onTabTapped(i) 内先 _ensureTabCreated(i)setState,保证当前 Tab 对应页面已创建。
  • 展示AnimatedSwitcher 的 child 为 _tabPages[_currentIndex]!。已创建过的 Tab 会一直保留,切换回来不再新建。

3.4 首页翻页不整页 setState

  • 状态 :用 ValueNotifier<int> _pageIndexNotifier 替代原来的 _currentIndex
  • onPageChanged :仅执行 _pageIndexNotifier.value = index 以及原有的「接近末尾时 _loadMore」逻辑,不再 setState。页码指示器在每页的 _buildImagePage 中由 itemBuilderindex 直接得到,无需依赖当前页码状态。
  • 网格切单列 :点击网格项时先 _pageIndexNotifier.value = index,再 setState(() => _isGridView = false)animateToPagedispose 中调用 _pageIndexNotifier.dispose()

3.5 发现页搜索防抖

  • 防抖Timer? _searchDebounce,常量 _searchDebounceDuration = 250ms
  • 逻辑_onSearchChanged(q) 内先 _searchDebounce?.cancel(),再 _searchDebounce = Timer(_searchDebounceDuration, () { ... }),在回调里做 lower = q.trim().toLowerCase()、过滤 _allWorkssetState 更新 _filteredWorks;回调内先 if (!mounted) return
  • dispose_searchDebounce?.cancel(),避免泄漏与 setState 后使用。

四、部分代码

4.1 AppSettings 与单一 Notifier(main.dart)

dart 复制代码
class AppSettings {
  const AppSettings({
    required this.themeMode,
    required this.locale,
    required this.fontScale,
  });

  final ThemeMode themeMode;
  final Locale? locale;
  final double fontScale;

  AppSettings copyWith({
    ThemeMode? themeMode,
    Object? locale = _omitLocale,
    double? fontScale,
  }) {
    return AppSettings(
      themeMode: themeMode ?? this.themeMode,
      locale: identical(locale, _omitLocale) ? this.locale : locale as Locale?,
      fontScale: fontScale ?? this.fontScale,
    );
  }
}
const Object _omitLocale = Object();

late final ValueNotifier<AppSettings> appSettingsNotifier;
// main(): appSettingsNotifier = ValueNotifier<AppSettings>(AppSettings(...));

4.2 底部 Tab 懒加载(root_page.dart)

dart 复制代码
final List<Widget?> _tabPages = [null, null, null, null];

void _ensureTabCreated(int index) {
  if (_tabPages[index] != null) return;
  switch (index) {
    case 0: _tabPages[index] = const HomePage(); break;
    case 1: _tabPages[index] = const SearchPage(); break;
    case 2: _tabPages[index] = const MessagesPage(); break;
    case 3: _tabPages[index] = const ProfilePage(); break;
    default: _tabPages[index] = const HomePage();
  }
}
// initState: _ensureTabCreated(0);
// _onTabTapped(i): _ensureTabCreated(i); setState(...);
// child: _tabPages[_currentIndex]!

4.3 首页翻页仅更新 notifier(home_page.dart)

dart 复制代码
final ValueNotifier<int> _pageIndexNotifier = ValueNotifier<int>(0);

onPageChanged: (index) {
  _pageIndexNotifier.value = index;
  if (!_isGridView && index >= _displayImages.length - 2 && _hasMore && !_isLoadingMore) {
    _loadMore();
  }
},
// dispose: _pageIndexNotifier.dispose();

4.4 发现页搜索防抖(search_page.dart)

dart 复制代码
Timer? _searchDebounce;
static const Duration _searchDebounceDuration = Duration(milliseconds: 250);

void _onSearchChanged(String q) {
  _searchDebounce?.cancel();
  _searchDebounce = Timer(_searchDebounceDuration, () {
    if (!mounted) return;
    setState(() {
      final lower = q.trim().toLowerCase();
      // ... 过滤 _allWorks → _filteredWorks
    });
  });
}
// dispose: _searchDebounce?.cancel();

五、注意事项

  • AppSettings.copyWithlocale 使用 Object? locale = _omitLocale,调用方传 null 表示「跟随系统」,不传表示保持当前;设置页切换语言时使用 copyWith(locale: newLocale)
  • Tab 懒加载 :首次进入发现/消息/我的时才会执行对应页的 initState(如请求、初始化),首屏仅首页会发起首页相关请求。
  • 防抖 :防抖仅减少输入过程中的 setState 次数,列表很大时若单次过滤仍耗时,可后续考虑将过滤放入 compute(isolate)。

六、小结

通过合并主题/语言/字体为单一 AppSettings + 一层 ValueListenableBuilder、主题常量只创建一次、底部 Tab 按需创建、首页翻页仅更新 ValueNotifier、发现页搜索 250ms 防抖,完成了「二、构建与重建」中高优先级优化,缩小了设置切换与翻页时的重建范围,降低了首屏与输入时的开销,体感更顺滑。

结束语

感谢阅读本帖,如对贴中内容有意见和建议的,欢迎与我联系交流,也欢迎加入开源鸿蒙跨平台社区:

https://openharmonycrossplatform.csdn.net

相关推荐
冬奇Lab9 小时前
一天一个开源项目(第20篇):NanoBot - 轻量级AI Agent框架,极简高效的智能体构建工具
人工智能·开源·agent
微祎_10 小时前
Flutter for OpenHarmony:链迹 - 基于Flutter的会话级快速链接板极简实现方案
flutter
微祎_10 小时前
Flutter for OpenHarmony:魔方计时器开发实战 - 基于Flutter的专业番茄工作法应用实现与交互设计
flutter·交互
a11177613 小时前
几何占领 原创网页小游戏(html开源)
前端·开源·html
麟听科技15 小时前
HarmonyOS 6.0+ APP智能种植监测系统开发实战:农业传感器联动与AI种植指导落地
人工智能·分布式·学习·华为·harmonyos
阿杆15 小时前
同事嫌参数校验太丑?SpEL Validator + IDEA 插件,直接让他闭嘴
java·后端·开源
前端不太难15 小时前
HarmonyOS PC 焦点系统重建
华为·状态模式·harmonyos
IvorySQL16 小时前
无需修改内核即可为 PostgreSQL 数据库对象添加自定义属性
数据库·postgresql·开源
空白诗16 小时前
基础入门 Flutter for Harmony:Text 组件详解
javascript·flutter·harmonyos
蚂蚁开源16 小时前
AReaL 团队开源 ASearcher 项目,解锁搜索智能体领域的最新突破
ai·开源