使用ChangeNotifierProvider实现深浅主题切换卡顿如何优化
问题根源
你当前的ChangeNotifier方案卡顿的核心原因是:调用Provider.of<ColorState>(context)默认会将当前组件与整个ColorState状态绑定,每次notifyListeners()触发时,所有引用了ColorState的上百个组件会同时触发重建,全量重建的开销远高于三元运算符的简单布尔判断,所以反而更卡顿。
疑问解答
- 上百处监听Provider确实会产生这么大的性能损耗吗?
是的,未经优化的全量监听会导致大量无差别组件重建,尤其是如果你的组件树层级较深,重建开销会被放大。而原来的三元写法本质只是运行时读取当前主题模式做一次分支判断,CPU开销几乎可以忽略。 - 需要回到原来的三元运算符写法吗?
不需要,有更简洁易维护、性能也更好的方案,完全兼容你需要不同场景手动指定颜色的需求。
最优解决方案
这里推荐两种方案,按需选择即可:
方案1:BuildContext扩展封装(最简单,适配所有Flutter版本)
直接将不同场景的颜色判断逻辑封装为BuildContext的扩展方法,既不需要额外维护状态类,也没有冗余的监听开销:
extension CustomThemeColor on BuildContext { /// 判断当前是否为亮色模式 bool get _isLight => Theme.of(this).brightness == Brightness.light; /// 通用文字颜色 Color get textColor => _isLight ? Colors.black : Colors.white; /// 通用背景色 Color get backgroundColor => _isLight ? Colors.white : const Color(0xff121212); /// 卡片场景颜色 Color get cardColor => _isLight ? Colors.grey.shade100 : Colors.grey.shade900; /// 按钮主色 Color get primaryButtonColor => _isLight ? Colors.blue : Colors.blueAccent; // 所有你需要的自定义场景颜色都可以在这里统一新增管理 }
使用时直接在组件中调用:
Container( color: context.backgroundColor, child: Text('测试', style: TextStyle(color: context.textColor)), )
这种方案的性能和原生Theme调用一致,没有额外的状态监听开销,同时所有颜色统一管理,比散落在各处的三元写法好维护得多。
方案2:ThemeExtension(推荐,适配Flutter 3.0+)
Flutter官方提供了ThemeExtension能力,专门用于自定义主题颜色,支持系统自带的主题切换过渡动画,性能最优:
第一步,定义自定义主题颜色类:
@immutable class CustomThemeColors extends ThemeExtension<CustomThemeColors> { final Color textColor; final Color backgroundColor; final Color cardColor; // 新增其他场景颜色 const CustomThemeColors({ required this.textColor, required this.backgroundColor, required this.cardColor, }); @override CustomThemeColors copyWith({ Color? textColor, Color? backgroundColor, Color? cardColor, }) { return CustomThemeColors( textColor: textColor ?? this.textColor, backgroundColor: backgroundColor ?? this.backgroundColor, cardColor: cardColor ?? this.cardColor, ); } @override CustomThemeColors lerp(ThemeExtension<CustomThemeColors>? other, double t) { if (other is! CustomThemeColors) return this; return CustomThemeColors( textColor: Color.lerp(textColor, other.textColor, t)!, backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t)!, cardColor: Color.lerp(cardColor, other.cardColor, t)!, ); } // 亮色配色 static const light = CustomThemeColors( textColor: Colors.black, backgroundColor: Colors.white, cardColor: Color(0xfff5f5f5), ); // 暗色配色 static const dark = CustomThemeColors( textColor: Colors.white, backgroundColor: Color(0xff121212), cardColor: Color(0xff1f1f1f), ); }
第二步,注入到全局Theme中:
MaterialApp( theme: ThemeData( brightness: Brightness.light, extensions: const [CustomThemeColors.light], ), darkTheme: ThemeData( brightness: Brightness.dark, extensions: const [CustomThemeColors.dark], ), themeMode: yourGlobalThemeMode, // 你的全局主题控制变量 home: const HomePage(), )
使用时调用:
final customColors = Theme.of(context).extension<CustomThemeColors>()!; Container( color: customColors.backgroundColor, child: Text('测试', style: TextStyle(color: customColors.textColor)), )
这种方案完全依托Flutter官方的主题系统,不需要自己维护Stream、ChangeNotifier等状态,主题切换时系统会自动处理颜色过渡,重建范围也只局限于依赖了对应主题属性的组件,性能远优于自己实现的ChangeNotifier方案。
现有ChangeNotifier的优化方案(如果你不想替换现有实现)
如果要保留当前的ColorState方案,可以将所有直接调用Provider.of的地方替换为Selector,只监听你用到的单个颜色属性,避免全量重建:
// 只监听backgroundColor变化,其他属性变化不会触发当前组件重建 Selector<ColorState, Color>( selector: (_, state) => state.backgroundColor, builder: (_, bgColor, __) => Container(color: bgColor), )
这种方式可以大幅降低重建数量,但整体还是不如前面两种官方方案简洁。
内容的提问来源于stack exchange,提问作者Arveni
相关产品推荐
相关产品推荐

