【开源鸿蒙跨平台开发训练营】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,只创建一次。
    • :RootPage 用 List<Widget?> _tabPages 按需创建 Tab 页面,在 _onTabTapped 中 _ensureTabCreated(i),首屏仅创建 HomePage。
    • :HomePage 用 ValueNotifier<int> _pageIndexNotifier 保存当前页码,onPageChanged 只写 _pageIndexNotifier.value = index,不调用 setState。
    • :SearchPage 对 _onSearchChanged 做 250ms 防抖,再执行过滤与 setState。

二、修改的文件

文件 说明
lib/main.dart 新增 AppSettings 与 appSettingsNotifier;主题/暗主题改为顶层 final;MyApp 仅一层 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),并在 dispose 中 dispose 该 notifier。
lib/pages/search_page.dart 搜索防抖:Timer? _searchDebounce,_onSearchChanged 内先 cancel 再 Timer(250ms, ...) 执行过滤与 setState;dispose 中 _searchDebounce?.cancel()。

三、优化说明

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

  • AppSettings :不可变类,包含 themeMode、locale、fontScale,提供 copyWith({ themeMode, locale, fontScale })。其中 locale 通过「省略参数」与「显式传 null」区分「不修改」与「设为跟随系统」。
  • appSettingsNotifier :全局 ValueNotifier<AppSettings>,在 main() 中从 SettingsService 读入初始值后创建。
  • MyApp.build :仅一层 ValueListenableBuilder<AppSettings>,内部直接使用 settings.themeMode、settings.locale、settings.fontScale 配置 MaterialApp 与 MediaQuery.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 中由 itemBuilder 的 index 直接得到,无需依赖当前页码状态。
  • 网格切单列 :点击网格项时先 _pageIndexNotifier.value = index,再 setState(() => _isGridView = false) 并 animateToPage。dispose 中调用 _pageIndexNotifier.dispose()。

3.5 发现页搜索防抖

  • 防抖 :Timer? _searchDebounce,常量 _searchDebounceDuration = 250ms。
  • 逻辑 :_onSearchChanged(q) 内先 _searchDebounce?.cancel(),再 _searchDebounce = Timer(_searchDebounceDuration, () { ... }),在回调里做 lower = q.trim().toLowerCase()、过滤 _allWorks、setState 更新 _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.copyWith :locale 使用 Object? locale = _omitLocale,调用方传 null 表示「跟随系统」,不传表示保持当前;设置页切换语言时使用 copyWith(locale: newLocale)。
  • Tab 懒加载 :首次进入发现/消息/我的时才会执行对应页的 initState(如请求、初始化),首屏仅首页会发起首页相关请求。
  • 防抖 :防抖仅减少输入过程中的 setState 次数,列表很大时若单次过滤仍耗时,可后续考虑将过滤放入 compute(isolate)。

六、小结

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

结束语

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

https://openharmonycrossplatform.csdn.net

相关推荐
wflynn13 分钟前
GitHub 今日推荐|Edge0:让大号 MoE 模型在一台 Mac 上流式跑起来
python·开源·github
甘晴void1 小时前
我给 Magpie 做了个 macOS 桌宠:106 段动画,请求一多就亮灯
macos·electron·开源·deepseek·桌面宠物·magpie
miofly3 小时前
Claude Sonnet 5自主选药选坐标完成小鼠脑部手术,更强模型拒绝执行
开源·github
hhhjhl4 小时前
开源鸿蒙平台 KMP_CMP 三方库「Clikt」适配全流程
华为·开源·harmonyos
小杨互联网5 小时前
Rust 现代 CLI 工具离线镜像合集
开源·工具·离线镜像
熊猫钓鱼>_>5 小时前
四种书签,一条时间线:一个读书 App 的记忆颗粒与它的边界案例【鸿蒙心迹】
开发语言·华为·ai编程·harmonyos·鸿蒙·openharmony·鸿蒙pc
Goway_Hui6 小时前
【KMP for OpenHarmony】-- 在鸿蒙工程中使用 Apollo-Kotlin
华为·kotlin·harmonyos
IvorySQL7 小时前
开源数据库进校园| 11 月 3 日,IvorySQL 校园行走进浙大宁波校区:从 PostgreSQL 扩展生态到 AI 实战
数据库·postgresql·开源
正在走向自律7 小时前
HarmonyOS 手机/平板远程控制 Windows 电脑实践指南
智能手机·电脑·harmonyos·todesk·远程工具
深蓝AI8 小时前
Ghostty 1.4 七个月打磨:ASCII 解析快 5.7 倍、终端能放动画,SSH 乱码终结
程序员·开源