zoukankan      html  css  js  c++  java
  • 基于 Flutter 以两种方式实现App主题切换

    概述

    App主题切换已经成为了一种流行的用户体验,丰富了应用整体UI视觉效果。例如,白天夜间模式切换。实现该功能的思想其实不难,就是将涉及主题的资源文件进行全局替换更新。说到这里,我想你肯定能联想到一种设计模式:观察者模式。多种观察对象(主题资源)来观察当前主题更新的行为(被观察对象),进行主题的更新。今天和大家分享在 Flutter 平台上如何实现主题更换。
    效果

     
    实现流程

    在 Flutter 项目中,MaterialApp组件为开发者提供了设置主题的api:

         const MaterialApp({
            ...
            this.theme, // 主题
            ...
          })

    通过 theme 属性,我们可以设置在MaterialApp下的主题样式。theme 是 ThemeData 的对象实例:

        ThemeData({
            
            Brightness brightness,
            MaterialColor primarySwatch,
            Color primaryColor,
            Brightness primaryColorBrightness,
            Color primaryColorLight,
            Color primaryColorDark,
            
            ...
         
          })

    ThemeData 中包含了很多主题设置,我们可以选择性的改变其中的颜色,字体等等。所以我们可以通过改变 primaryColor 来实现状态栏的颜色改变。并通过Theme来获取当前 primaryColor 颜色值,将其赋值到其他组件上即可。在触发主题更新行为时,通知 ThemeData 的 primaryColor改变行对应颜色值。 有了以上思路,接下来我们通过两种方式来展示如何实现主题的全局更新。
    主题选项

    在实例中我们以一下主题颜色为主:

        /**
         * 主题选项
         */
        import 'package:flutter/material.dart';
         
        final List<Color> themeList = [
          Colors.black,
          Colors.red,
          Colors.teal,
          Colors.pink,
          Colors.amber,
          Colors.orange,
          Colors.green,
          Colors.blue,
          Colors.lightBlue,
          Colors.purple,
          Colors.deepPurple,
          Colors.indigo,
          Colors.cyan,
          Colors.brown,
          Colors.grey,
          Colors.blueGrey
        ];

    EventBus 方式实现

    Flutter中EventBus提供了事件总线的功能,以监听通知的方式进行主体间通信。我们可以在main.dart入口文件下注册主题修改的监听,通过EventBus发送通知来动态修改 theme。核心代码如下:

         @override
          void initState() {
            super.initState();
            Application.eventBus = new EventBus();
            themeColor = ThemeList[widget.themeIndex];
            this.registerThemeEvent();
          }
          
          /**
           * 注册主题切换监听
           */
          void registerThemeEvent() {
            Application.eventBus.on<ThemeChangeEvent>().listen((ThemeChangeEvent onData)=> this.changeTheme(onData));
          }
          
          /**
           * 刷新主题样式
           */
          void changeTheme(ThemeChangeEvent onData) {
            setState(() {
              themeColor = themeList[onData.themeIndex];
            });
          }
         
         
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              theme:  ThemeData(
                primaryColor: themeColor
              ),
              home: HomePage(),
            );
          }

    然后在更新主题行为的地方来发送通知刷新即可:

          changeTheme() async {
            Application.eventBus.fire(new ThemeChangeEvent(1));
          }

    scoped_model 状态管理方式实现

    了解 React、 React Naitve 开发的朋友对状态管理框架肯定都不陌生,例如 Redux 、Mobx、 Flux 等等。状态框架的实现可以帮助我们非常轻松的控制项目中的状态逻辑,使得代码逻辑清晰易维护。Flutter 借鉴了 React 的状态控制,同样产生了一些状态管理框架,例如 flutter_redux、scoped_model、bloc。接下来我们使用 scoped_model 的方式实现主题的切换。 关于 scoped_model 的使用方式可以参考pub仓库提供的文档:https://pub.dartlang.org/packages/scoped_model

    1. 首先定义主题 Model

        /**
         * 主题Model
         * Create by Songlcy
         */
        import 'package:scoped_model/scoped_model.dart';
         
        abstract class ThemeStateModel extends Model {
         
          int _themeIndex;
          get themeIndex => _themeIndex;
         
          void changeTheme(int themeIndex) async {
            _themeIndex = themeIndex;
            notifyListeners();
          }
        }

     在 ThemeStateModel 中,定义了对应的主题下标,changeTheme() 方法为更改主题,并调用 notifyListeners() 进行全局通知。

    2. 注入Model

          @override
          Widget build(BuildContext context) {
            return ScopedModel<MainStateModel>(
              model: MainStateModel(),
              child: ScopedModelDescendant<MainStateModel>(
                builder: (context, child, model) {
                  return  MaterialApp(
                    theme: ThemeData(
                      primaryColor: themeList[model.themeIndex]
                    ),
                    home: HomePage(),
                  );
                },
              )
            );
          }

    3. 修改主题

          changeTheme(int index) async {
            int themeIndex = index;
            MainStateModel().of(context).changeTheme(themeIndex);
          }

    可以看到,使用 scoped_model 的方式同样比较简单,思路和 EventBus 类似。以上代码我们实现了主题的切换,细心的朋友可以发现,我们还需要对主题进行保存,当下次启动 App 时,要显示上次切换的主题。Flutter中提供了 shared_preferences 来实现本地持久化存储。
    主题持久化保存

    当进行主题更换时,我们可以对主题进行持久化本地存储

          void changeTheme(int themeIndex) async {
            _themeIndex = themeIndex;
            SharedPreferences sp = await SharedPreferences.getInstance();
            sp.setInt("themeIndex", themeIndex);
          }

    然后在项目启动时,取出本地存储的主题下标,设置在theme上即可

        void main() async {
          int themeIndex = await getTheme();
          runApp(App(themeIndex));
        }
         
        Future<int> getTheme() async {
          SharedPreferences sp = await SharedPreferences.getInstance();
          int themeIndex = sp.getInt("themeIndex");
          if(themeIndex != null) {
            return themeIndex;
          }
          return 0;
        }
         
        @override
        Widget build(BuildContext context) {
            return ScopedModel<MainStateModel>(
              model: mainStateModel,
              child: ScopedModelDescendant<MainStateModel>(
                builder: (context, child, model) {
                  return  MaterialApp(
                    theme: ThemeData(
                      primaryColor: themeList[model.themeIndex != null ? model.themeIndex : widget.themeIndex]
                    ),
                    home: HomePage(),
                  );
                },
              )
            );
        }

    以上我们通过两种方式来实现了主题的切换,实现思想都是通过通知的方式来触发组件 build 进行刷新。那么两种方式有什么区别呢?
    区别

    从 print log 中,可以发现,当使用 eventbus 事件总线进行切换主题刷新时,_AppState 下的 build方法 和 home指向的组件界面  整体都会重新构建。而使用scoped_model等状态管理工具,_AppState 下的 build方法不会重新执行,只会刷新使用到了Model的组件,但是home对应的组件依然会重新执行build方法进行构建。所以我们可以得出以下结论:

    两者方式都会导致 home 组件被重复 build。明显区别在于使用状态管理工具的方式可以避免父组件 build 重构。

    源码已上传到 Github,详细代码可以查看

    EventBus 实现整体代码:

        import 'package:flutter/material.dart';
        import 'package:event_bus/event_bus.dart';
        import './config/application.dart';
        import './pages/home_page.dart';
        import './events/theme_event.dart';
        import './constants/theme.dart';
        import 'package:shared_preferences/shared_preferences.dart';
         
        void main() async {
          int themeIndex = await getDefaultTheme();
          runApp(App(themeIndex));
        }
         
        Future<int> getDefaultTheme() async {
          // 从shared_preferences中获取上次切换的主题
          SharedPreferences sp = await SharedPreferences.getInstance();
          int themeIndex = sp.getInt("themeIndex");
          print(themeIndex);
          if(themeIndex != null) {
            return themeIndex;
          }
          return 0;
        }
         
        class App extends StatefulWidget {
         
          int themeIndex;
          App(this.themeIndex);
         
          @override
          State<StatefulWidget> createState() => AppState();
        }
         
        class AppState extends State<App> {
         
          Color themeColor;
         
          @override
          void initState() {
            super.initState();
            Application.eventBus = new EventBus();
            themeColor = ThemeList[widget.themeIndex];
            this.registerThemeEvent();
          }
         
          void registerThemeEvent() {
            Application.eventBus.on<ThemeChangeEvent>().listen((ThemeChangeEvent onData)=> this.changeTheme(onData));
          }
         
          void changeTheme(ThemeChangeEvent onData) {
            setState(() {
              themeColor = ThemeList[onData.themeIndex];
            });
          }
         
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              theme:  ThemeData(
                primaryColor: themeColor
              ),
              home: HomePage(),
            );
          }
         
          @override
          void dispose() {
            super.dispose();
            Application.eventBus.destroy();
          }
        }

          changeTheme() async {
            SharedPreferences sp = await SharedPreferences.getInstance();
            sp.setInt("themeIndex", 1);
            Application.eventBus.fire(new ThemeChangeEvent(1));
          }

    scoped_model 实现整体代码:

        import 'package:flutter/material.dart';
        import 'package:event_bus/event_bus.dart';
        import 'package:scoped_model/scoped_model.dart';
        import 'package:shared_preferences/shared_preferences.dart';
        import './config/application.dart';
        import './pages/home_page.dart';
        import './constants/theme.dart';
        import './models/state_model/main_model.dart';
         
        void main() async {
          int themeIndex = await getTheme();
          runApp(App(themeIndex));
        }
         
         
        Future<int> getTheme() async {
          SharedPreferences sp = await SharedPreferences.getInstance();
          int themeIndex = sp.getInt("themeIndex");
          if(themeIndex != null) {
            return themeIndex;
          }
          return 0;
        }
         
        class App extends StatefulWidget {
         
          final int themeIndex;
         
          App(this.themeIndex);
         
          @override
          _AppState createState() => _AppState();
        }
         
        class _AppState extends State<App> {
         
          @override
          void initState() {
            super.initState();
            Application.eventBus = new EventBus();
          }
         
          @override
          Widget build(BuildContext context) {
            return ScopedModel<MainStateModel>(
              model: MainStateModel(),
              child: ScopedModelDescendant<MainStateModel>(
                builder: (context, child, model) {
                  return  MaterialApp(
                    theme: ThemeData(
                      primaryColor: ThemeList[model.themeIndex != null ? model.themeIndex : widget.themeIndex]
                    ),
                    home: HomePage(),
                  );
                },
              )
            );
          }
        }

          changeTheme() async {
            int themeIndex = MainStateModel().of(context).themeIndex == 0 ? 1 : 0;
            SharedPreferences sp = await SharedPreferences.getInstance();
            sp.setInt("themeIndex", themeIndex);
            MainStateModel().of(context).changeTheme(themeIndex);
          }

  • 相关阅读:
    hdu1003 最大子串和
    cf339d Xenia and Bit Operations
    A + B Problem II
    中国近代史纲要----王洪兵--2016年春季学期----中国海洋大学
    CodeForces 35D Animals
    CodeForces 558D
    Vanya and Brackets
    spfa
    hdu 1217 Arbitrage
    CodeForces 1A Theatre Square
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/10524984.html
Copyright © 2011-2022 走看看