恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Flutter + OpenHarmony 鸿蒙记事本夜间模式完整实现指南
首页
资讯中心
/
Flutter + OpenHarmony 鸿蒙记事本夜间模式完整实现指南
Flutter + OpenHarmony 鸿蒙记事本夜间模式完整实现指南
发布时间:2026/9/10 6:25:22
做完了记事本的基础功能和数据存储之后我盯着白晃晃的界面看了整整一周总觉得哪里不对劲。直到某天晚上躺在床上用手机改笔记的时候才反应过来问题出在哪这个应用最密集的使用场景压根就是睡前记想法、半夜补计划、随手存第二天要办的事。在灯光昏暗的卧室里一个亮白色背景的记事本简直是在跟自己的眼睛过不去。所以就有了这个系列第四篇的初衷给 Flutter OpenHarmony 的鸿蒙记事本加上一键夜间模式既要护眼又不能丑得让人不想开。这一篇不打算只贴几个颜色变量而是把从方案选型、颜色体系设计、状态管理、持久化恢复到 OpenHarmony 真机适配的整条链路完整走一遍。最终效果是点击一个按钮整个应用从背景、文字、输入框到状态栏图标同步切换重启应用之后还能记住用户的选择。如果你正在用 Flutter 开发 OpenHarmony 应用或者单纯想给自己现有的项目加一套夜间模式这篇内容应该能帮你省下不少自己踩坑的时间。1. 需求拆解与方案选型1.1 交互目标与边界确认夜间模式听起来是个很简单的事情但真正动手之前我还是把需求边界列了一遍避免做着做着开始失控。首先明确交互形式一键切换不搞渐变滑块、不搞定时自动切换、不搞跟随系统亮度那种复杂逻辑。原因很简单记事本是一个工具型应用用户对它的预期是打开就能用任何需要多一步操作的设置都是在增加使用成本。所以首版我只做单选切换当前是日间模式就切到夜间当前是夜间模式就切回日间按钮就放在首页的 AppBar 右上角一个图标搞定。其次是影响范围夜间模式不是只换一个背景色那么简单。在我的记事本里至少包含以下这些元素需要跟着变主背景、AppBar 背景、文字颜色、输入框边框和填充色、分割线、图标颜色、弹窗背景、按钮颜色、光标颜色、键盘适配后的页面背景、状态栏字体颜色。漏掉任何一个切到夜间模式后都会出现一块很不协调的亮色区域。然后是持久化问题用户切到夜间模式后关掉应用下次打开如果又变回白色这种体验非常糟糕。所以切换结果必须持久化存储。最后是平台适配OpenHarmony 上的 Flutter 版本和社区版本有差异有些插件并没有完全跟上主流版本所以方案选型时必须考虑 OpenHarmony 生态的插件支持现状。1.2 主流主题切换方案横评在 Flutter 里做夜间模式网上一搜能出来一堆方案我用一个下午把主流的几种都试了一遍逐个说下实测感受。第一种是直接用ThemeData.light()和ThemeData.dark()两个内置主题通过MaterialApp的theme和darkTheme两个参数切换。这个方案最省事但缺点也很明显内置主题太通用风格跟应用自身的视觉语言完全不搭尤其深色主题的默认背景色是偏灰的放在记事本场景里看起来有点像系统设置页没有记事本该有的专注感。第二种是ColorScheme.fromSeed配合 Material 3 的动态配色。这种方案适合做个性化主题的应用能根据用户的主色自动生成整套色板但动态颜色在不同平台上的渲染效果不完全一致而且调试时很难精确控制某个组件的具体颜色。我自己的项目里主色调已经定死了不需要这种灵活性。第三种是用第三方状态管理库来管理主题状态比如provider、riverpod、bloc。这类方案适合大型项目状态多、逻辑复杂用统一的框架来管理是合理的。但记事本这个项目本身引入的第三方依赖已经不少了为了一个布尔值的主题状态再引入一套状态管理框架属于杀鸡用牛刀还会增加维护成本。最终我选择的是ValueNotifierThemeModelValueListenableBuilderThemeData组合。理由很简单主题状态本质上就是isDark这一个布尔值Flutter 自带的ValueNotifier就能完美承担状态通知的职责ValueListenableBuilder负责局部刷新不需要让整棵组件树感知主题变化。ThemeData负责承载全部颜色配置组件通过Theme.of(context)来取颜色保证颜色来源统一。1.3 为什么用 ValueNotifier 而不是其它方案展开说一下这个选型背后的逻辑。我见过不少项目为了一个夜间模式问题强行引入provider然后整个应用的状态都被塞进同一个ChangeNotifier里导致每次 setState 都触发大范围 rebuild性能问题反而不如不引入状态管理。ValueNotifier的优势在于它足够轻内部实现就是一个ChangeNotifier加上一个value值只能在值变化时通知监听者。配合ValueListenableBuilder它会把 builder 限定在某个局部范围内切换主题时只有依赖了ValueListenableBuilder的那段内容会重建其它组件不受影响。在主题切换这个场景里这就够了。当然如果项目后续要扩展到多主题、跟随系统、定时切换等复杂需求届时再引入provider或者riverpod也不迟。技术选型永远是为当前需求服务的不要为了未来的可能性提前引入复杂度。2. 颜色体系设计与 ThemeData 构建2.1 从设计稿到代码的色板定义夜间模式做得丑九成是颜色没设计好直接拿纯黑做背景、纯白做文字是绝对不行。纯黑背景下纯白文字会产生极高的对比度在暗光环境里依然刺激眼睛。夜间的核心逻辑是降低整体亮度和对比度而不是简单地把颜色反转。我按照 Material 3 的深色配色原则结合 OpenHarmony 设计规范中推荐的深色背景色值定义了一套记事本专属的色板。先看日间模式的日间模式主背景用的是偏冷的浅灰白色色值为#F7F8FA不是纯白。原因是在室内光线下纯白的反射率太高看久了眼睛容易累。AppBar 背景和页面背景保持一致让视觉上有整体感文字颜色用深灰色而不是纯黑。输入框边框用了浅灰色描边配合圆角精致感就出来了。夜间模式背景换成了深蓝灰色#121212这是 Material 3 深色主题的标准背景色比纯黑柔和许多。文本颜色定义为#E0E0E0不是纯白亮度在80%左右确保可读性的同时降低刺眼程度。输入框的边框颜色从浅灰变成中灰色分割线从浅灰变成深灰。这样整套颜色过渡下来对比度控制在一个舒适的区间。我花了不少时间调整一个关键细节日间模式和夜间模式的语义色。比如删除按钮的红色日间模式用的#E53935夜间模式就不能照搬这个红色在深色背景上会显得更加刺眼我改成了#EF9A9A饱和度降低看起来温和很多但依然能明确传达删除的语义。2.2 集中管理颜色杜绝硬编码之前版本里所有颜色都是直接写在Color(0xFF...)形式混在代码里的比如某个按钮的 background 直接写了一个色值另一个页面要用同一个颜色时又复制了一份。这导致夜间模式改造时我要像捕鱼一样把所有硬编码颜色的位置都捞出来。虽然编辑器支持全局搜索但颜色值这么多每处都要判断语义效率极低。这次改造我做一个中间层创建了一个AppColors类把所有的语义色统一管理每个颜色只定义一次。日间模式和夜间模式各定义一组颜色。这样做的好处是组件代码里不再出现Colors.white这种具体颜色而是通过Theme.of(context).colorScheme.surface之类的方式获取语义色。实际项目里你会发现 Flutter 内置的ColorScheme并不能覆盖所有场景。比如记事本首页列表项的按下水波纹颜色ColorScheme里没有直接对口的字段。这时候就通过ThemeExtension来扩展。我会建一个AppColorsExtension类继承ThemeExtension里面放上ColorScheme中没有的自定义字段然后注册到ThemeData.extensions里。方案比较清晰基础色板ThemeData自带的ColorScheme承载全部通用颜色扩展色板AppColorsExtension承载记事本特有的颜色比如首页卡片背景色、列表分割线色、标记标签的背景色核心原则只有一条组件里禁止直接写具体色值全部从Theme取。日间和夜间只是配置不同组件自身完全无感和透明。2.3 ThemeData 构建的核心实现理解了色板之后来看ThemeData是怎么构建的。我封装一个AppTheme类提供两个静态方法一个生成日间ThemeData一个生成夜间ThemeData。生成日间主题时通过ColorScheme.fromSeed生成基础色板seedColor是记事本的主色调。这里要特别说明ColorScheme.fromSeed生成的颜色都是动态计算的不会完全等于你传入的 seed 值。它会把 seed 作为基准推导出一整套协调的颜色组合。但它的推导算法不一定符合你的视觉预期所以最终还是要手动覆盖关键字段。夜间主题同理但brightness要设置为Brightness.dark。这很重要ColorScheme的brightness字段会影响 Flutter 内部大量组件的默认渲染逻辑比如输入框光标颜色、AppBar 文字颜色、Dialog 背景色等等。如果只改颜色不改 brightness会出现大量组件配色异常。构建完ThemeData之后还有个关键配置字体在日夜间切换时的渲染效果。TextTheme我保持统一的字号字重不做夜间专属调整。但我在实测中发现某些低端设备上切换夜间模式后Text 的渲染效果会有细微差异这是 Flutter 引擎层对深色背景上的文字做了抗锯齿优化导致的属于正常现象。3. 主题状态管理与一键切换的核心实现3.1 主题模型与切换入口颜色定义好之后就进入状态管理环节。我建了一个ThemeModel类继承ChangeNotifier内部持有一个ThemeMode枚举值初始值为ThemeMode.light。对外暴露一个toggleTheme方法切换isDark状态然后通知监听者。这时候MaterialApp需要监听ThemeModel当主题切换时重建整棵组件树。实现方式是用ListenableBuilder包裹MaterialApp这样它会在ThemeModel变化时重新 build把新的theme和darkTheme传下去同时通过themeMode告诉 Flutter 当前应该采用哪套主题。这里有个常见误区很多人会把ThemeModel放进InheritedWidget或者Provider里然后每个组件都去读一遍主题状态。实际上主题切换是全局性的只有MaterialApp真正需要感知它。组件只需要通过Theme.of(context)就能拿到当前主题下的颜色不需要感知ThemeModel本身。这正是 Flutter 主题系统的聪明之处颜色配置集中消费方式分布化。一键切换按钮的逻辑就简单了点击后调用_themeModel.toggleTheme()ListenableBuilder监听到变化MaterialApp带着新的themeMode重新 build整棵树自动用到新主题。从用户角度看整个界面在一帧内完成切换几乎感知不到卡顿。3.2 切换动画AnimatedTheme 的妙用夜间模式的切换如果毫无过渡生硬地从一个主题跳到另一个主题体验说实话不算差但缺少提精致感。这里要介绍一个平时容易忽略的组件AnimatedTheme。AnimatedTheme是Theme的动画版本。当传入的ThemeData发生变化时它会在新旧主题之间做一个隐式动画过渡时长和曲线都可以配置。把MaterialApp内部的Theme换成AnimatedTheme颜色、背景、文字颜色就会在几百毫秒内平滑过渡观感上会有一种非常柔和自然的切换效果。我在真机上分别测试了无动画和 400ms 动画的效果实话实说动画版本在观感上确实明显更好。但要注意两点第一是过渡时间不能太长超过 600ms 就会显得拖沓400ms 是一个比较平衡的值感知得到过渡又不会耽误操作第二是 TransitionBuilder 需要正确处理否则会出现列表项在切换动画期间闪白的情况。3.3 持久化让用户的选择被记住主题切换完了如果重启后被打回原形这个功能基本算白做。所以就轮到持久化上场了。Flutter 生态里做本地持久化最常用的是shared_preferences它是一个轻量级键值对存储方案适用于存储这种简单的布尔值。但这里有一个 OpenHarmony 平台特有的坑Flutter 官方版本的shared_preferences插件对 OpenHarmony 的支持并不完整第一次直接引用官方版本编译时报错了。查了一下OpenHarmony 的 Flutter 适配仓库提供了一个专门的插件系列其中就包括shared_preferences的 OpenHarmony 适配版。这个问题的根源在于 Flutter 插件体系里Android 平台的实现是有一套标准 API 的OpenHarmony 如果要求完全兼容 Android API 也可以但那样会依赖 Android 特有的机制。OpenHarmony 团队选择的是通过自有的ohos目录结构提供原生实现所以插件要同步使用适配仓库里的版本。持久化逻辑实现初始化时从SharedPreferences里读isDarkMode这个键如果有值就恢复对应的ThemeMode。切换主题时先更新内存中的状态再异步写入SharedPreferences。写的时候要注意异步失败的问题SharedPreferences.setBool返回的是一个 Future如果用户快速连续点击切换按钮存在并发写入导致状态不一致的可能。我加了一个简单的锁或者直接利用async/await顺序执行确保每次写入都以前一次的完成为前提。另外补充一个细节不要把SharedPreferences的初始化放在main()里阻塞等待因为SharedPreferences.getInstance()在部分机型和平台上有一定的 IO 延迟放在启动流程里会拉长启动耗时。我一般是在启动页异步初始化主题恢复则放在SplashScreen阶段去读。这样不会阻塞首帧也不会出现启动后黑白屏闪跳。4. 页面适配实战从首页到输入页的全面改造4.1 用 ThemeData 统一替换硬编码颜色整体配色框架就绪之后最细碎也最耗时的就是逐页排查硬编码颜色。我前两版里随手写的Colors.white、Colors.black、Color(0xFFEEEEEE)之类的全部要替换成语义色。打开 IDE 的全局搜索输入Color(0xFF搜索结果里大概有三四十处。每一处都要判断它的语义是什么是背景、文字、边框、还是水波纹颜色。然后对应改成Theme.of(context).colorScheme.surface、textTheme.bodyLarge?.color、dividerColor等等。这里建议用 VS Code 的批量替换加手动确认双保险。全自动替换容易引出问题比如有人把两个空格的颜色合并成一个字符串导致报错。手动确认能保证每一处改动都是可控的。排查完这些颜色之后还有一个容易被忽略的重灾区自定义的TextStyle。直接写在TextStyle(color: Colors.grey[600])里的颜色如果不改在夜间模式下会变成深灰色文字趴在深色背景上几乎看不清。正确的做法是把颜色交给组件层TextStyle里只定义字号、字重、行高等颜色通过Theme.of(context).textTheme统一控制或者从Theme.of(context).colorScheme取。4.2 首页卡片和列表项的夜间适配首页是这个记事本的颜面适配做得怎么样直接决定用户的第一印象。首页主要包含几个部分AppBar、列表、新建按钮。AppBar 在日间模式下是浅色背景配深色文字夜间模式下需要变成深色背景配浅色文字。ThemeData的appBarTheme已经帮我处理了这个逻辑但有一点要特别声明设置scrolledUnderElevation属性解决列表滚动到 AppBar 下方时出现的阴影叠加问题。在夜间模式下这种阴影会显得格外突兀。列表项卡片在夜间模式下要跟背景有层次区分但又不能太白。我用ColorScheme.surfaceContainerHigh作为卡片背景色夜间模式下它会自动变为比背景色稍微亮一点的深色这样视觉上依然能分辨出卡片和背景的边界。一个让我当时调试了很久的细节列表项里我用了ListTile它的selected状态默认会渲染一层半透明的覆盖色。夜间模式下这层覆盖色如果太明显会干扰列表项的正常阅读。解决办法是给ListTile的selectedTileColor设置为Colors.transparent或者不设置selected值改用自定义的背景色方案。4.3 编辑页输入框和键盘区域的特殊处理编辑页是夜间模式最重要的战场因为用户在这个页面停留的时间最长。输入框在日间模式用的是浅灰边框加白色背景如果夜间模式直接改成深色背景加亮色边框反而会很刺眼。我采用的是暗色背景加比背景色稍微亮一点的中性色边框光标颜色保持主题色不变这样视觉引导会比较清晰。键盘区域的问题可能很多人没注意到在 Flutter 中键盘弹出和收起时页面底部的padding会通过MediaQuery.viewInsets.bottom动态变化。在夜间模式下键盘弹出瞬间页面底部如果出现白条、白块大概率是因为聚焦时的背景色没设置成主题背景。排查方法是观察键盘弹出动画的每一帧看哪里出现白色闪烁。我当时定位到问题出在Scaffold的resizeToAvoidBottomInset行为以及一个自定义的SafeArea背景色没有跟随主题。去掉硬编码后正常了。4.4 状态栏与系统导航栏适配状态栏和系统导航栏是主题切换时容易出现视觉断裂的部分。因为在 Flutter 的页面边界之外状态栏和导航栏实际上是系统 UI不归 Flutter 直接控制。夜间模式下如果状态栏的图标依然是深色的趴在深色背景上会完全看不见。Flutter 里控制状态栏样式用SystemChrome.setSystemUIOverlayStyle通过状态栏图标亮度来区分深色背景用浅色图标浅色背景用深色图标。但由于 Flutter 的 UI 线程和系统 UI 不是同步渲染的切换主题时会出现一个时间片状态栏颜色已经变了但页面还在动画中或者页面已经变了但状态栏延迟更新视觉上看起来就是闪烁。我的处理方案是在AnimatedTheme的动画完成回调里再设置状态栏样式或者用SystemChrome.setSystemUIOverlayStyle加上较短的延迟让状态栏的颜色与页面主体颜色同步完成切换。OpenHarmony 系统对窗口状态栏的处理方式和 Android 略有差异在 OpenHarmony 真机上单纯设置状态栏图标亮度可能对某些系统版本不生效需要结合窗口的FullScreen状态设置来配合。我实测在标准 OpenHarmony 版本上通过SystemChrome.setSystemUIOverlayStyle能够正常控制状态栏图标颜色但个别预览版系统会出现失效需要在设置里同步配置系统导航栏的对比度。5. OpenHarmony 真机适配与验证5.1 OpenHarmony Flutter 版本的特有适配在 OpenHarmony 上跑 Flutter 应用最先需要确认的就是 Flutter SDK 的版本。OpenHarmony 的 Flutter 适配走的是一个独立的仓库版本跟进比 Google 官方 Flutter 慢不少。所以在开始主题改造之前我确认了当前的 Flutter SDK 是 OpenHarmony 适配版。某个版本的差异导致一个问题ColorScheme中一些新字段在旧版 Flutter 的ThemeData中不存在。如果你按最新 Flutter 的 API 写代码然后拿到 OpenHarmony 适配版去编译会发现一堆 undefined getter 的报错。解决办法是参考 OpenHarmony 适配仓库的 API 变更说明找出版本差异用兼容写法来规避。比如surfaceContainerHigh这个字段在旧版中可能不存在我就只能用surfaceVariant加透明度模拟。还有一个值得注意的适配点OpenHarmony 系统的MediaQuery在某些场景下返回的textScaleFactor行为和 Android 不同。夜间模式下如果开启了系统字体加粗会影响页面里的文字布局。我在设计时统一用TextTheme的字号不使用绝对值这样就避免了系统字体调节带来的布局错乱。5.2 真机验证从模拟器到 RK3568 开发板开发调试阶段我主要用模拟器但最终适配验证必须在真机上做。我手上的真机是一块 RK3568 开发板OpenHarmony 版本是标准 release。这里直接说结论模拟器上表现正常的夜间模式到真机上可能会出现颜色渲染偏差比如深色背景出现色带文字边缘出现紫边。用 RK3568 开机实测下来问题集中在两块一个是部分颜色主题在 GPU 合成阶段被做了色彩空间转换导致色值稍微偏移另一个是动画性能不足时主题切换过程中会出现短暂的白屏闪烁。这些问题的处理经验颜色偏差在 OpenHarmony 上验证Color的 ARGB 值不要使用带透明度的叠加色。如果发现某区域颜色偏淡把半透明遮罩改为不透明的纯色填充。白屏闪烁把主题切换动画的时长缩短到 300ms同时避免在动画期间执行耗时操作比如读写数据库。GPU 合成问题在AndroidManifest.xml在 OpenHarmony 项目中的等效配置文件里为页面开启硬件加速同时关闭开关层合成减少合成层数。5.3 不同 OpenHarmony 设备上的表现差异OpenHarmony 是一个跨设备系统手机、平板、开发板、甚至 PC 都能跑。不同设备的屏幕色域、GPU 能力、系统版本都有差异所以主题适配不能只在一个设备上验证。我在 RK3568 开发板和另一台设备上都做了测试发现夜间模式的体验差距主要来自屏幕素质低端屏幕往往无法准确还原深色背景的色阶导致灰色过渡出现明显的条纹感。这里没法通过代码完全规避但可以让深色背景颜色的色阶跨度小一点不做纯黑到纯白的极端对比减少屏幕的压力。我最终的夜间模式背景色不是纯#000000而是#121212其中一个原因就在这。还有一个跨设备差异点是前面提到的SystemUIOverlayStyle。在不同设备上系统 UI 对状态栏样式的接管程度不同有的设备需要你主动设置有的设备会跟随 Flutter 的AppBarTheme自动变化。为了避免行为不一致我统一的策略是在 page 入口处主动调用一次SystemChrome.setSystemUIOverlayStyle不依赖默认行为。6. 常见问题与排查技巧实录6.1 问题速查表做夜间模式功能我整理了一份出现问题时的排查顺序按频率排序问题现象可能原因排查方法切换后部分页面仍是白色该页面组件硬编码了颜色搜索Color(0xFF和Colors.white状态栏图标看不清没有设置SystemUIOverlayStyle在页面入口主动设置图标亮度切换时白屏闪烁主题动画期间触发了耗时操作缩短动画时长或把耗时操作放到动画结束后重启后主题没有恢复持久化插件未适配 OpenHarmony确认使用的是 OpenHarmony 适配版插件深色背景出现色带颜色过渡范围过大或 GPU 色深不足使用#121212之类的渐进色替代纯黑某组件颜色不随主题变使用了Theme.of(context)之外的常量检查是否使用了全局常量颜色排查顺序的核心思想是先看是不是硬编码颜色的问题再看是不是状态管理没有正确触发 rebuild最后看是不是平台适配差异。绝大多数问题都集中在第一类。6.2 硬编码颜色排查秘诀全局搜索Color(0xFF是最基本的办法。但实际项目中还有一些更隐蔽的硬编码方式比如Colors.grey[300]、Colors.white.withOpacity(0.8)这类写满了整份代码的表达式。我总结了一个更系统的排查办法把应用切换到夜间模式后利用 Flutter 的 debug 模式启动Widgets Inspector逐层审查各个组件的颜色属性。看到哪个组件显示的颜色不对劲直接点进去就能看到它的渲染代码然后顺着代码往上找很快就能定位到颜色来源。另一个更彻底的办法是禁止在状态类组件里写死颜色。我会给自己的代码定一个规则组件中凡是涉及颜色的代码必须通过Theme.of(context)或者它派生的语义色来获取。这个约束靠人肉执行不现实需要通过代码评审去检查我就在提交前用一个脚本扫描所有Color(关键字检测到直接调用的场景就打回修改。6.3 AnimatedTheme 引发的动画异常排查我还遇到过一个有意思的问题夜间模式切换时首页列表的项在动画中间出现了一层淡淡的白色覆盖像是一张半透明的纸被盖在上面动画结束后又恢复。排查过程比较曲折。一开始怀疑是AnimatedTheme的动画参数问题后来逐步缩小范围发现是列表项里的图标使用了IconTheme的默认色在主题过渡过程中IconTheme的颜色解析提前结束导致图标暂时采用了一个不正确的主题颜色。这个问题在日间模式完全看不出来因为默认色和最终色的差异不大但深色模式下就非常明显。解决方案是在列表项构建时显式设置图标颜色不依赖IconTheme的隐式继承。这样虽然多写了几行代码但避免了主题切换动画期间的不确定性。6.4 关于动态主题的扩展建议代码写完之后我又考虑了几个扩展方向给有兴趣深入的读者留个思路。第一个是跟随系统主题。OpenHarmony 的系统设置里可以切换深色模式如果你的应用想跟随系统自动切换可以在MaterialApp的themeMode里设置为ThemeMode.system然后利用MediaQuery的platformBrightness来获取当前系统亮度模式。需要注意的事情是platformBrightness在 OpenHarmony 上的数据来源和更新时机需要确保配置了相关权限或者在生命周期回调里做监听。第二个是自定义主题色。现在用户的记事本可以只切换 dark/light但未来如果想支持用户自定义主色调可以在ColorScheme.fromSeed的seedColor上做文章把用户选择的颜色作为一个参数传递进去。这里的架构已经天然支持了颜色配置层已经抽整洁了加一个用户选项就可以。第三个是三套主题以上的场景。比如护眼模式、夜间模式和阅读模式。三套主题本质上还是靠ThemeMode枚举去控制只是枚举值增加颜色配置增加。但要注意ThemeMode本身只有light/dark/system三个值要支持三套以上的主题需要自己维护一个主题索引而不是单纯依赖ThemeMode。同时MaterialApp的theme和darkTheme不够用了得自己动态构建ThemeData。7. 最终效果与一点经验心得功能全部开发完、真机测试通过之后我把应用部署到 RK3568 开发板上在傍晚昏暗的光线下实际体验了十来分钟。说句实话夜间模式带来的护眼效果远远超过切换一个深色图标带来的表面观感。原本白色背景在强光下产生的眩目感消失了编辑笔记时眼睛的疲惫感明显下降。而且切换动画让整个应用的质感也跟着上了一个台阶不再是那种生硬的主题翻转。这次改造工程量并不大但给我最大的启发是主题系统跟状态管理是一样的逻辑要么一开始就设计好要么后面用数倍的精力去补救。如果我第一版就建好AppColors语义色体系而不是把颜色散落在各组件里这次改造最多需要一天。因为前面偷懒实际耗时三天其中一半以上时间花在排查硬编码颜色上。另外一个小经验主题切换的持久化一定要考虑好版本升级场景。我第一版持久化只存了isDark一个字段后来如果加了自定义主色就面临键名变更、默认值偏移等问题。建议从一开始就设计成版本化的配置实体后续扩展时只需要处理迁移逻辑。如果你也在做类似的功能建议先花半小时把色板和组件清单列齐再开始动代码。否则切到一半发现漏了某个页面又得重新翻一遍效率很低。希望这篇内容能帮你少走弯路。