Flutter: 动态修改应用主题

应用主题

现在很多应用都提供了手动修改应用主题的功能,一般也就两个主题: dark和light,简单说就是: 夜间和日间模式.部分没有提供手动修改的,则选择了跟随手机系统的主题.而在Flutter中,提供了这两种主题,可以很方便的切换.前提是在应用中,需要减少定制化的部分.同时需要针对两种主题进行测试.

实现说明

主题的修改是存在于: MaterialApp上面的theme属性,而这个属性的值为ThemeData,也就是除了系统自带的.也可以自定义主题.

原理分为以下几步:

  1. 父类: MaterialApp中的theme需要使用属性动态判断,以便进行主题切换;
  2. 父类: 需要定义一个类型方法,以便在其它页面调用并修改主题
  3. 子类: 定义一个类属性,用于接收父类定义的类型方法.并在需要修改主题时,传递参数调用该方法
  4. 父类: 定义具体方法,处理修改主题操作.

说起来好复杂,但是一看代码,一下就明白了.

image-3194

一个简单的”小栗子”

下面是一个简单的例子,只在首页实现了一个切换主题的功能.

例子用了CodePen.io的在线编辑,因为CodePen.io不支持导入第三方包,因此注释了SharedPreferences相关内容.如需使用,在本地导入即可

在线预览

源码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import 'package:flutter/material.dart';
/// import 'package:shared_preferences/shared_preferences.dart';



/// Flutter 动态修改应用主题
///
///
/// [特别说明]
///
/// 因为CodePen不支持引入第三方包,所以此处注释了SharedPreferences相关的内容(SharedPreferences可以使应用重启后设置依然生效).
/// 安装SharedPreferences: https://pub.dev/packages/shared_preferences
///
/// [原理]
///
/// 在MyApp中传递给SettingApp一个回调方法,如果SettingApp需要更新MyApp中的任意值,直接调用此方法即可(目前通过Map<String,Object>传递值,因为无法动态调用setState).
///
/// @version 2020--05-31
/// @author prd(pruidong@gmail.com)(bckf.cn)
///


/// Flutter dynamically modify the application theme
///
///
/// [Special Note]
///
/// Because CodePen does not support the introduction of third-party packages, the contents related to SharedPreferences are annotated here (SharedPreferences can make the settings still effective after the application restarts).
/// Install SharedPreferences: https://pub.dev/packages/shared_preferences
///
/// [Principle]
///
/// Pass a callback method to SettingApp in MyApp. If SettingApp needs to update any value in MyApp, just call this method (currently pass the value through Map <String, Object> because setState cannot be called dynamically).
///
/// @version 2020--05-31
/// @author prd (pruidong@gmail.com)(bckf.cn)



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

typedef MainStateUpdateCall = void Function(Map<String,Object> map);


class MyApp extends StatefulWidget {
  const MyApp();
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _darkTheme = false;
  /// final _ThemeKey = "THEME";


  /// 外部接口调用此方法修改本类的属性.
  ///
  /// 可以通过传递在map中传递值,来修改系统属性.
  /// 使用可以参考:
  ///
  /// The external interface calls this method to modify the properties of this class.
  ///
  /// You can modify system properties by passing values in the map.
  /// Use can refer to:
  ///
  /// [_SettingAppState]的setAndReloadTheme方法.
  void mainStateUpdateMethod(Map<String,Object> map) {
    map.forEach((key, value) {
      setState((){
        switch(key){
          case "_darkTheme":
            _darkTheme=value;
            break;
        }
      });
    });
  }

    @override
  void initState() {
    super.initState();
    initTheme();
  }

  /// CodePen cannot introduce third-party packages, so comment the code below.
  ///
  ///
  void initTheme() async {
    // final prefs = await SharedPreferences.getInstance();
    // bool tempTheme = false;
    // if (prefs.containsKey(_ThemeKey)) {
    //   tempTheme = prefs.getBool(_ThemeKey);
    // }
    // setState(() {
    //   _darkTheme = tempTheme;
    // });
  }



  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme:_darkTheme ? ThemeData.dark() : ThemeData.light(),
      home: SettingApp(mainStateUpdateCall: mainStateUpdateMethod,),
    );
  }
}



/// Setting.
///
///
class SettingApp extends StatefulWidget {
  const SettingApp({this.mainStateUpdateCall});

  final MainStateUpdateCall mainStateUpdateCall;

  @override
  _SettingAppState createState() => _SettingAppState();
}

class _SettingAppState extends State<SettingApp> {
  bool _darkTheme = false;
  /// final _themeKey = "THEME";

  var resData = [];

  @override
  void initState() {
    super.initState();
    initSetting();
  }

  void initSetting() async {
    /// final prefs = await SharedPreferences.getInstance();
    /// bool tempTheme = false;
    ///if (prefs.containsKey(_ThemeKey)) {
    ///  tempTheme = prefs.getBool(_ThemeKey);
    ///  }
    // setState(() {
    //   _darkTheme = tempTheme;
    // });
    var tempResData = [
      Card(
        child: ListTile(
          title: Text('dark'),
          trailing: Switch(
              value: _darkTheme,
              onChanged: (value) {
                setAndReloadTheme(value);
                /// Call this method again to generate the list-so that the displayed value is correct.
                /// 重新调用此方法生成列表-以使显示的值正确.
                initSetting();
              }),
        ),
      ),
      Text("Support: bckf.cn / pruidong@gmail.com")
    ];
    setState(() {
      resData = tempResData;
    });
  }

  /// 保存设置和重新加载主题.
  /// Save the settings and reload the theme.
  ///
  void setAndReloadTheme(bool value) async {
    /// final prefs = await SharedPreferences.getInstance();
    /// prefs.setBool(_ThemeKey, value);
    ///
    ///
    /// 调用全局更换.
    /// Call global replacement.
    setState(() {
       _darkTheme = value;
     });
    print(value);
    print(_darkTheme);
    if (widget.mainStateUpdateCall != null) {
      var paramMap={"_darkTheme":value};
      widget.mainStateUpdateCall(paramMap);
    }
  }

  @override
  Widget build(BuildContext context) {
    var listView = ListView.separated(
      padding: const EdgeInsets.all(2),
      itemCount: resData.length,
      itemBuilder: (BuildContext itemBuildContext, int index) {
        return Padding(
          padding: EdgeInsets.all(5.0),
          child: resData[index],
        );
      },
      separatorBuilder: (BuildContext context, int index) => Divider(
        color: Theme.of(context).dividerColor,
      ),
    );
    return Scaffold(
      appBar: AppBar(title: Text("Flutter Dynamic Update App Theme")),
      body: listView,
    );
  }
}

代码比较简单,就不详细解释了.