Flutter主题实践 2022-12-13 周二

2022-12-13  本文已影响0人  勇往直前888

简介

自从引入了深色模式(暗黑模式),主题切换就一直是一个话题。
虽然,关于主题的知识点并不多,但是真的要落到实处,还是有很多细节要注意。
这里记一下流水账,万一有需要的时候,可以翻翻。

本地缓存

主题的选择跟后台无关,是纯客户端的事。
另外,一般主题切换都用一个单选列表,所以一般可以用通用的单选数据结构。比如字段如下:

class SelectModel {
  String name;
  int id;
  String code;
  bool select;
  bool enable;

  SelectModel({
    required this.name,
    this.id = 0,
    this.code = '',
    this.select = false,
    this.enable = true,
  });

  SelectModel copyWith({
    String? name,
    int? id,
    String? code,
    bool? select,
    bool? enable,
  }) {
    return SelectModel(
      name: name ?? this.name,
      id: id ?? this.id,
      code: code ?? this.code,
      select: select ?? this.select,
      enable: enable ?? this.enable,
    );
  }

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'name': name,
      'id': id,
      'code': code,
      'select': select,
      'enable': enable,
    };
  }

  factory SelectModel.fromMap(Map<String, dynamic> map) {
    return SelectModel(
      name: map['name'] as String,
      id: map['id'] as int,
      code: map['code'] as String,
      select: map['select'] as bool,
      enable: map['enable'] as bool,
    );
  }

  String toJson() => json.encode(toMap());

  factory SelectModel.fromJson(String source) =>
      SelectModel.fromMap(json.decode(source) as Map<String, dynamic>);

  @override
  String toString() {
    return 'SelectModel(name: $name, id: $id, code: $code, select: $select, enable: $enable)';
  }

  @override
  bool operator ==(covariant SelectModel other) {
    if (identical(this, other)) return true;

    return other.name == name &&
        other.id == id &&
        other.code == code &&
        other.select == select &&
        other.enable == enable;
  }

  @override
  int get hashCode {
    return name.hashCode ^
        id.hashCode ^
        code.hashCode ^
        select.hashCode ^
        enable.hashCode;
  }
}

主题数据

/// 自定义颜色
class CustomColor extends ThemeExtension<CustomColor> {
  final bool isDarkMode;
  CustomColor(this.isDarkMode);

  /// 这两个方法都不实现,简单返回自身
  @override
  ThemeExtension<CustomColor> copyWith() {
    return this;
  }

  @override
  ThemeExtension<CustomColor> lerp(
      ThemeExtension<CustomColor>? other, double t) {
    return this;
  }

  /// 颜色定义,分为深色,浅色两套
  Color get backgrouderIcon => isDarkMode
      ? ColorUtil.hexStringColor('#666666')
      : ColorUtil.hexStringColor('#DDDDDD');
}
class CustomIcon extends ThemeExtension<CustomIcon> {
  final bool isDarkMode;
  CustomIcon(this.isDarkMode);

  /// 这两个方法都不实现,简单返回自身
  @override
  ThemeExtension<CustomIcon> copyWith() {
    return this;
  }

  @override
  ThemeExtension<CustomIcon> lerp(ThemeExtension<CustomIcon>? other, double t) {
    return this;
  }

  /// 图标名称,分为深色,浅色两套
  String get order40 =>
      isDarkMode ? R.assetsImagesOrder40Dark : R.assetsImagesOrder40;
  String get warehouse40 =>
      isDarkMode ? R.assetsImagesWarehouse40Dark : R.assetsImagesWarehouse40;
  String get rehearsal40 =>
      isDarkMode ? R.assetsImagesRehearsal40Dark : R.assetsImagesRehearsal40;
  String get parcel40 =>
      isDarkMode ? R.assetsImagesParcel40Dark : R.assetsImagesParcel40;
}
/// 浅色主题
ThemeData lightTheme = ThemeData.light().copyWith(
  extensions: <ThemeExtension<dynamic>>[
    CustomColor(false),
    CustomIcon(false),
  ],
);
/// 深色主题
ThemeData darkTheme = ThemeData.dark().copyWith(
  extensions: <ThemeExtension<dynamic>>[
    CustomColor(true),
    CustomIcon(true),
  ],
);

主题数据封装类

class ThemeConfig {
  /// 颜色
  static CustomColor get _customColor =>
      Theme.of(Get.context!).extension<CustomColor>()!;
  static Color get backgrouderIcon => _customColor.backgrouderIcon;
  static Color get backgrouderButton => Theme.of(Get.context!).primaryColor;

  /// 图标
  static CustomIcon get _customIcon =>
      Theme.of(Get.context!).extension<CustomIcon>()!;
  static String get order40 => _customIcon.order40;
  static String get warehouse40 => _customIcon.warehouse40;
  static String get rehearsal40 => _customIcon.rehearsal40;
  static String get parcel40 => _customIcon.parcel40;
}

主题工具类

class ThemeUtil {
  /// 切换主题;主题模式和主题数据一并切换
  static changeTheme() {
    ThemeMode mode = getCachedThemeModel();
    ThemeData themeData = getThemeData();
    Get.changeThemeMode(mode);
    Get.changeTheme(themeData);
    updateTheme();
  }

  /// 更新app,使主题切换生效
  /// ThemeData中的预定义字段不需要这个,会自动更新;自定义字段需要这个,不然不会更新;
  static updateTheme() {
    Future.delayed(const Duration(milliseconds: 300), () {
      Get.forceAppUpdate();
    });
  }

  /// 获取本地存储; 返回对应的主题模式
  static getCachedThemeModel() {
    SelectModel? theme = CacheService.of.getTheme();
    ThemeMode themeMode = ThemeMode.light;
    if (theme?.code == 'light') {
      themeMode = ThemeMode.light;
    } else if (theme?.code == 'dark') {
      themeMode = ThemeMode.dark;
    } else if (theme?.code == 'system') {
      themeMode = ThemeMode.system;
    }
    return themeMode;
  }

  /// 获取本地存储; 返回对应的主题数据
  static getThemeData() {
    SelectModel? theme = CacheService.of.getTheme();
    ThemeData themeData = lightTheme;
    if (theme?.code == 'light') {
      themeData = lightTheme;
    } else if (theme?.code == 'dark') {
      themeData = darkTheme;
    } else if (theme?.code == 'system') {
      if (isDark()) {
        themeData = darkTheme;
      } else {
        themeData = lightTheme;
      }
    }
    return themeData;
  }

  /// 这里要切换主题;所以跟随系统的时候(ThemeMode.system),要得到系统设置的主题模式
  static isDark() {
    SelectModel? theme = CacheService.of.getTheme();
    if (theme?.code == 'system') {
      return MediaQuery.of(Get.context!).platformBrightness == Brightness.dark;
    } else {
      return Get.isDarkMode;
    }
  }
}

测试界面

企业微信截图_e10e85c6-e859-4291-b89b-00b4c095290c.png 企业微信截图_684896ba-149a-40ac-adb1-38bf4155e9ad.png

界面代码

class ThemeTestPage extends GetView<ThemeTestController> {
  const ThemeTestPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<ThemeTestController>(
      builder: (context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('主题测试'),
            centerTitle: true,
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                color: ThemeConfig.backgrouderButton,
                margin: const EdgeInsets.symmetric(
                  horizontal: 10,
                  vertical: 10,
                ),
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 10,
                ),
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () {
                    controller.setDartTheme();
                  },
                  child: const Text(
                    '设置深色模式',
                    style: TextStyle(fontSize: 20),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
              Container(
                color: ThemeConfig.backgrouderButton,
                margin: const EdgeInsets.symmetric(
                  horizontal: 10,
                  vertical: 10,
                ),
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 10,
                ),
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () {
                    controller.setLightTheme();
                  },
                  child: const Text(
                    '设置浅色模式',
                    style: TextStyle(fontSize: 20),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
              Container(
                color: ThemeConfig.backgrouderButton,
                margin: const EdgeInsets.symmetric(
                  horizontal: 10,
                  vertical: 10,
                ),
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 10,
                ),
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () {
                    controller.setSystemTheme();
                  },
                  child: const Text(
                    '跟随系统',
                    style: TextStyle(fontSize: 20),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
              Container(
                color: ThemeConfig.backgrouderButton,
                margin: const EdgeInsets.symmetric(
                  horizontal: 10,
                  vertical: 10,
                ),
                padding: const EdgeInsets.symmetric(
                  horizontal: 20,
                  vertical: 10,
                ),
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () {
                    controller.checkTheme();
                  },
                  child: const Text(
                    '检查模式',
                    style: TextStyle(fontSize: 20),
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
              Container(
                margin: const EdgeInsets.symmetric(
                  horizontal: 10,
                  vertical: 60,
                ),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceAround,
                  children: [
                    Container(
                      width: 80,
                      height: 80,
                      color: ThemeConfig.backgrouderIcon,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Image.asset(
                            ThemeConfig.order40,
                          ),
                          const Text('我的订单'),
                        ],
                      ),
                    ),
                    Container(
                      width: 80,
                      height: 80,
                      color: ThemeConfig.backgrouderIcon,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Image.asset(
                            ThemeConfig.warehouse40,
                          ),
                          const Text('我的仓库'),
                        ],
                      ),
                    ),
                    Container(
                      width: 80,
                      height: 80,
                      color: ThemeConfig.backgrouderIcon,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Image.asset(
                            ThemeConfig.rehearsal40,
                          ),
                          const Text('我的预演'),
                        ],
                      ),
                    ),
                    Container(
                      width: 80,
                      height: 80,
                      color: ThemeConfig.backgrouderIcon,
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Image.asset(
                            ThemeConfig.parcel40,
                          ),
                          const Text('我的包裹'),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

按钮响应代码

class ThemeTestController extends GetxController {
  /// 浅色模式
  void setLightTheme() {
    SelectModel light = SelectModel(
      name: '浅色模式',
      code: 'light',
    );
    CacheService.of.setTheme(light);
    ThemeUtil.changeTheme();
  }

  /// 深色模式
  void setDartTheme() {
    SelectModel dark = SelectModel(
      name: '深色模式',
      code: 'dark',
    );
    CacheService.of.setTheme(dark);
    ThemeUtil.changeTheme();
  }

  /// 跟随系统
  void setSystemTheme() {
    SelectModel system = SelectModel(
      name: '跟随系统',
      code: 'system',
    );
    CacheService.of.setTheme(system);
    ThemeUtil.changeTheme();
  }

  /// 检查主题; 这里只检查应用当前的模式
  void checkTheme() {
    if (Get.isDarkMode) {
      EasyLoading.showToast('深色模式');
    } else {
      EasyLoading.showToast('浅色模式');
    }
  }
}

监听系统变化

class AppLifecycleService extends GetxService with WidgetsBindingObserver {
  /// 需要先Get.put,不然会报错;ApplicationBindings统一初始化
  static AppLifecycleService get of => Get.find();

  /// 具体的初始化方法
  Future<AppLifecycleService> init() async {
    log('CacheService initial finished.');
    return this;
  }

  @override
  void onInit() {
    super.onInit();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.resumed:
        ThemeUtil.changeTheme();
        break;
      default:
    }
  }

  @override
  void onClose() {
    WidgetsBinding.instance.removeObserver(this);
    super.onClose();
  }
}

启动初始化

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      debugShowCheckedModeBanner: false,
      initialBinding: ApplicationBindings(),
      initialRoute: AppPages.INITIAL,
      getPages: AppPages.routes,
      builder: EasyLoading.init(),
      translations: MultiLanguage(),
      locale: Get.deviceLocale,
      fallbackLocale: const Locale('zh'),
      theme: lightTheme,
      darkTheme: darkTheme,
      themeMode: ThemeMode.light,
    );
  }
}
class ApplicationBindings extends Bindings {
  @override
  void dependencies() async {
    /// 初始化服务
    await initServices();

    /// 自动运行
    await autoRunItems();
  }

  /// 服务集中初始化
  Future<void> initServices() async {
    log('starting services ...');
    await Get.putAsync(() => StorageService().init());
    await Get.putAsync(() => CacheService().init());
    await Get.putAsync(() => AppLifecycleService().init());
    log('All services started...');
  }

  /// 自动运行
  Future<void> autoRunItems() async {
    log('starting autoRunItems ...');
    ThemeUtil.changeTheme();
    log('finish autoRunItems ...');
  }
}
上一篇 下一篇

猜你喜欢

热点阅读