跳到主要内容

flutter getx 多主题 包含深色模式

· 阅读需 6 分钟
Arce
独立游戏开发者

flutter_theme_getx

思路:使用Getx的 changeThemeMode / forceAppUpdate 和 缓存

原理分析

获取缓存中的 主题模式STORAGE_THEME_MODE 主题色 STORAGE_THEME_COLOR 在 ThemeStore的初始化进行赋值 然后在 ThemeService 初始化的时候去转化再赋值 当然也可以减少ThemeService 层的操作 直接从ThemeStore 层进行

案例代码

GetMaterialApp

GetMaterialApp(
title: "Arce App",
// ... 其它配置
theme: ThemeService.to.lightTheme,
darkTheme: ThemeService.to.darkTheme,
themeMode: ThemeService.to.themeMode,
// ...
);

核心代码文件 ThemeStore

class ThemeStore extends GetxController {
static ThemeStore get to => Get.find();
final _themeMode = ThemeMode.light.obs;
final _primaryColor = PrimaryColor.blue.obs;


void onInit() {
super.onInit();
_themeMode.value = ThemeModes.tryParse(StorageService.to.getString(STORAGE_THEME_MODE)) ?? ThemeMode.light;
_primaryColor.value = PrimaryColors.tryParse(StorageService.to.getString(STORAGE_THEME_COLOR)) ?? PrimaryColor.blue;
print("初始化主题和颜色${StorageService.to.getString(STORAGE_THEME_MODE)} , ${StorageService.to.getString(STORAGE_THEME_COLOR)}");
}

ThemeMode getThemeMode(){
return _themeMode.value;
}

PrimaryColor getPrimaryColor(){
return _primaryColor.value;
}

// 获取token
Future<void> setThemeMode(ThemeMode value) async{
await StorageService.to.setString(STORAGE_THEME_MODE,value.name);
print("_themeMode "+ value.toString());
_themeMode.value = value;
}


Future<void> setPrimaryColor(PrimaryColor value) async{
await StorageService.to.setString(STORAGE_THEME_COLOR,value.name);

print("_primaryColor "+ value.toString());
_primaryColor.value = value;
}
}

核心代码文件 ThemeService

class ThemeService extends GetxService {
static ThemeService get to => Get.find();

Future<ThemeService> init() async {
_allTheme = ArceAllTheme();
primaryColor = ThemeStore.to.getPrimaryColor();
lightTheme = _convertTheme(primaryColor, false);
darkTheme = _convertTheme(primaryColor, true);
themeMode = ThemeStore.to.getThemeMode();
return this;
}

late ThemeMode themeMode; // 浅色 / 深色 / 跟随系统
late ThemeData lightTheme; // 浅色模式下的主题
late ThemeData darkTheme; // 暗色模式下的主题
late ArceAllTheme _allTheme; // 主题模式
late PrimaryColor primaryColor;

/// 特殊的背景颜色
/// 少部分页面背景颜色为浅白色
late Color specialBackgroundColor;
late Color offstageColor;

Future<void> changeThemeMode(ThemeMode targetMode, Brightness platformBrightness) async {
await ThemeStore.to.setThemeMode(targetMode);
themeMode = targetMode;
setExtraColor(platformBrightness);
}

Future<void> changePrimaryColor(PrimaryColor color, Brightness platformBrightness) async {
primaryColor = color;
await ThemeStore.to.setPrimaryColor(color);
lightTheme = _convertTheme(color, false);
darkTheme = _convertTheme(color, true);
setExtraColor(platformBrightness);
}

ThemeData _convertTheme(PrimaryColor color, bool dark) {
switch (color) {
case PrimaryColor.blue:
return _allTheme.blueTheme(dark);
default:
return _allTheme.blueTheme(dark);
}
}

void setExtraColor(Brightness platformBrightness) {
if (themeMode == ThemeMode.system) {
_setExtraColorAuto(platformBrightness);
} else if (themeMode == ThemeMode.dark) {
_setExtraColorDarkMode();
} else {
_setExtraColorLightMode();
}
// notifyListeners();
// Get.changeTheme( lightTheme);

print("主题为修改 "+ themeMode.toString());
// (()async{Get.changeThemeMode(themeMode);})(); // 好像有异步问题 数据更新了,但是ui层未更新
// 强制build
Get.forceAppUpdate();
}

void _setExtraColorLightMode() {
specialBackgroundColor = Color.fromRGBO(245, 246, 250, 1);
offstageColor = Colors.black87;
}

void _setExtraColorDarkMode() {
specialBackgroundColor = Colors.black;
offstageColor = Colors.grey[600]!;
}

void _setExtraColorAuto(Brightness platformBrightness) {
if (platformBrightness == Brightness.dark) {
_setExtraColorDarkMode();
} else {
_setExtraColorLightMode();
}
}
}

ArceAllTheme文件存放中 主题数据

/// 主题
class ArceAllTheme {
/// ... 其他主题
/// 默认主题
ThemeData defaultTheme({required MaterialColor mainColor}) {
return ThemeData(
primaryColor: mainColor,
primarySwatch: mainColor,
floatingActionButtonTheme: const FloatingActionButtonThemeData(
foregroundColor: Colors.white,
),
appBarTheme: const AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle.dark,
iconTheme: IconThemeData(color: Colors.black),
elevation: 0,
color: Colors.white,
titleTextStyle: TextStyle(color: Colors.black),
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
elevation: 0,
selectedItemColor: mainColor,
backgroundColor: Colors.white,
unselectedItemColor: Colors.grey,
),
scaffoldBackgroundColor: Colors.white,
buttonTheme: const ButtonThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black),
backgroundColor: MaterialStateProperty.all(Colors.transparent),
alignment: Alignment.center,
shape: MaterialStateProperty.all(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
),
),
),
indicatorColor: mainColor,
textSelectionTheme: TextSelectionThemeData(cursorColor: mainColor),
cardTheme: const CardTheme(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
elevation: 0,
clipBehavior: Clip.antiAlias,
),
inputDecorationTheme: const InputDecorationTheme(
fillColor: Color.fromRGBO(245, 246, 250, 1),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
borderSide: BorderSide.none,
),
),
dialogTheme: const DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
),
bottomSheetTheme: const BottomSheetThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8.0), topRight: Radius.circular(8.0)),
),
),
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
),
listTileTheme: const ListTileThemeData(minLeadingWidth: 32),
);
}
}

主题模式 ThemeMode相关

class ThemeModes {
static ThemeMode? tryParse(String? name) {
if (name == ThemeMode.light.name) {
return ThemeMode.light;
} else if (name == ThemeMode.dark.name) {
return ThemeMode.dark;
} else if (name == ThemeMode.system.name) {
return ThemeMode.system;
}
return null;
}
}

class ThemeModeItem {
final ThemeMode mode;
final String desc;

const ThemeModeItem(this.mode, this.desc);
}

var themeModes = [
const ThemeModeItem(ThemeMode.system, "跟随系统"),
const ThemeModeItem(ThemeMode.light, "浅色"),
const ThemeModeItem(ThemeMode.dark, "深色")
];

主题色 PrimaryColor 相关


enum PrimaryColor { blue, blueGrey }

class PrimaryColors {
static PrimaryColor? tryParse(String? name) {
for (var color in PrimaryColor.values) {
if (color.name == name) {
return color;
}
}
return null;
}
}

class PrimaryColorItem {
final PrimaryColor primaryColor;
final String desc;
final Color color;
const PrimaryColorItem(this.primaryColor, this.desc, this.color);
}

var primaryColors = [
const PrimaryColorItem(PrimaryColor.blue, "蓝色", Colors.blue),
const PrimaryColorItem(PrimaryColor.blueGrey, "蓝灰", Colors.blueGrey),
];

总结:Get中好像只有 changeThemeMode 的方法 所以有一个不好的地方是切换主题颜色后需要调用 Get.forceAppUpdate() 方法才有效果,如果不希望用户频繁的调用 forceAppUpdate方法,可以加一层防抖代码,也可以在setThemeMode方法中用changeThemeMode方法 ,在setPrimaryColor方法中调用forceAppUpdate方法