![](/img/trans.png)
[英]Diagnosing why Flutter MaterialApp theme runtime change is slow on device only
[英]Flutter: How to change the MaterialApp theme at runtime
我有一個MaterialApp
小部件,它為應用程序中的所有小部件設置theme
。 我想在運行時從沒有任何直接引用其父MaterialApp
的子 Widget 更改MaterialApp
的theme
值。
看起來這應該是可能的,因為ThemeData
是由InheritedWidget
提供的,但我不知道如何批量更改theme
。 有誰知道如何做到這一點?
這是擁有應用程序其余部分的MaterialApp
:
new MaterialApp(
title: 'App Name',
theme: initialTheme,
routes: <String, WidgetBuilder>{
'/' : ...,
},
),
根據丹菲爾德的建議,我得出了以下解決方案。 如果有人有改進,請隨時加入:
// How to use: Any Widget in the app can access the ThemeChanger
// because it is an InheritedWidget. Then the Widget can call
// themeChanger.theme = [blah] to change the theme. The ThemeChanger
// then accesses AppThemeState by using the _themeGlobalKey, and
// the ThemeChanger switches out the old ThemeData for the new
// ThemeData in the AppThemeState (which causes a re-render).
final _themeGlobalKey = new GlobalKey(debugLabel: 'app_theme');
class AppTheme extends StatefulWidget {
final child;
AppTheme({
this.child,
}) : super(key: _themeGlobalKey);
@override
AppThemeState createState() => new AppThemeState();
}
class AppThemeState extends State<AppTheme> {
ThemeData _theme = DEV_THEME;
set theme(newTheme) {
if (newTheme != _theme) {
setState(() => _theme = newTheme);
}
}
@override
Widget build(BuildContext context) {
return new ThemeChanger(
appThemeKey: _themeGlobalKey,
child: new Theme(
data: _theme,
child: widget.child,
),
);
}
}
class ThemeChanger extends InheritedWidget {
static ThemeChanger of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ThemeChanger);
}
final ThemeData theme;
final GlobalKey _appThemeKey;
ThemeChanger({
appThemeKey,
this.theme,
child
}) : _appThemeKey = appThemeKey, super(child: child);
set appTheme(AppThemeOption theme) {
switch (theme) {
case AppThemeOption.experimental:
(_appThemeKey.currentState as AppThemeState)?.theme = EXPERIMENT_THEME;
break;
case AppThemeOption.dev:
(_appThemeKey.currentState as AppThemeState)?.theme = DEV_THEME;
break;
}
}
@override
bool updateShouldNotify(ThemeChanger oldWidget) {
return oldWidget.theme == theme;
}
}
這是此處回答的問題的一個具體案例: Force Flutter to redraw all widgets
看看那個問題中提到的股票樣本,特別注意: https : //github.com/flutter/flutter/blob/master/examples/stocks/lib/main.dart https://github.com/顫振/顫振/blob/master/examples/stocks/lib/stock_settings.dart
請注意以下事項:
_configuration
指定,由configurationUpdater
更新configurationUpdater
傳遞給需要它的應用程序的子級您還可以使用StreamController
。
只需復制並粘貼此代碼。 這是一個工作樣本。 你不需要任何圖書館,它超級簡單
import 'dart:async';
import 'package:flutter/material.dart';
StreamController<bool> isLightTheme = StreamController();
main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
initialData: true,
stream: isLightTheme.stream,
builder: (context, snapshot) {
return MaterialApp(
theme: snapshot.data ? ThemeData.light() : ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text("Dynamic Theme")),
body: SettingPage()));
});
}
}
class SettingPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: <
Widget>[
RaisedButton(
color: Colors.blue,
child: Text("Light Theme", style: TextStyle(color: Colors.white)),
onPressed: () {
isLightTheme.add(true);
}),
RaisedButton(
color: Colors.black,
child: Text("Dark Theme", style: TextStyle(color: Colors.white)),
onPressed: () {
isLightTheme.add(false);
}),
])));
}
}
您可以使用提供程序包中的ChangeNotifierProvider / Consumer與ChangeNotifier后繼者的組合。
/// Theme manager
class ThemeManager extends ChangeNotifier {
ThemeManager([ThemeData initialTheme]) : _themeData = initialTheme ?? lightTheme;
ThemeData _themeData;
/// Returns the current theme
ThemeData get themeData => _themeData;
/// Sets the current theme
set themeData(ThemeData value) {
_themeData = value;
notifyListeners();
}
/// Dark mode theme
static ThemeData lightTheme = ThemeData();
/// Light mode theme
static ThemeData darkTheme = ThemeData();
}
/// Application
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ThemeManager(),
child: Consumer<ThemeManager>(
builder: (_, manager, __) {
return MaterialApp(
title: 'Flutter Demo',
theme: manager.themeData,
home: HomePage(),
);
},
),
);
}
}
// Somewhere in GUI
FlatButton(
child: Text(isDarkMode ? 'Light Mode' : 'Dark Mode'),
onPressed() {
Provider.of<ThemeManager>(context, listen:false)
.themeData = isDarkMode ? ThemeManager.darkTheme : ThemeManager.lightTheme;
},
),
經過各種嘗試,我用 BLoC 模式做了它,我不知道這是否是一個好方法,但它似乎沒有問題:
應用主題模型:
class MyTheme {
Brightness brightness;
Color backgroundColor;
Color scaffoldBackgroundColor;
Color primaryColor;
Brightness primaryColorBrightness;
Color accentColor;
MyTheme({
this.brightness,
this.backgroundColor,
this.scaffoldBackgroundColor,
this.primaryColor,
this.primaryColorBrightness,
this.accentColor
});
}
class AppTheme {
String name;
MyTheme theme;
AppTheme(this.name, this.theme);
}
List<AppTheme> myThemes = [
AppTheme(
'Default',
MyTheme(
brightness: Brightness.light,
backgroundColor: Colors.blue[50],
scaffoldBackgroundColor: Colors.blue[50],
primaryColor: Colors.blue,
primaryColorBrightness: Brightness.dark,
accentColor: Colors.blue[50],
)),
AppTheme(
'Teal',
MyTheme(
brightness: Brightness.light,
backgroundColor: Colors.teal[50],
scaffoldBackgroundColor: Colors.teal[50],
primaryColor: Colors.teal[600],
primaryColorBrightness: Brightness.dark,
accentColor: Colors.teal[50],
),
),
];
應用 BLoC 類。 這里我使用了 RxDart 的 BehaviorSubject。
class AppBloc {
final _theme = BehaviorSubject<AppTheme>();
Function(AppTheme) get inTheme => _theme.sink.add;
Stream<AppTheme> get outTheme => _theme.stream;
AppBloc() {
print('-------APP BLOC INIT--------');
// Send to stream the initial theme
inTheme(myThemes[0]);
}
dispose() {
print('---------APP BLOC DISPOSE-----------');
_theme.close();
}
}
在應用程序的設置頁面中,我使用 _theme 流來設置帶有主題列表的下拉菜單的當前主題。 使用 onChanged 處理程序,當用戶單擊主題時,它會被發送到流:
StreamBuilder(
stream: widget.bloc.outTheme,
builder: (context, AsyncSnapshot<AppTheme> snapshot) {
return snapshot.hasData
? DropdownButton<AppTheme>(
hint: Text("Status"),
value: snapshot.data,
items: myThemes.map((AppTheme appTheme) {
return DropdownMenuItem<AppTheme>(
value: appTheme,
child: Text(appTheme.name),
);
}).toList(),
onChanged: widget.bloc.inTheme,
)
: Container();
}),
最后在主頁中,使用 StreamBuilder 我使用 _theme 流來設置選定的 ThemeData:
StreamBuilder(
stream: _bloc.outTheme,
builder: (context, AsyncSnapshot<AppTheme> snapshot) {
return MaterialApp(
theme: snapshot.hasData ? _buildThemeData(snapshot.data) : ThemeData(),
home: HomePage());
}),
_BuildThemeData 方法從主題模型中獲取 ThemeData:
_buildThemeData(AppTheme appTheme) {
return ThemeData(
brightness: appTheme.theme.brightness,
backgroundColor: appTheme.theme.backgroundColor,
scaffoldBackgroundColor: appTheme.theme.scaffoldBackgroundColor,
primaryColor: appTheme.theme.primaryColor,
primaryColorBrightness: appTheme.theme.primaryColorBrightness,
accentColor: appTheme.theme.accentColor
);
}
我希望這對你有用。
您可以只使用 MaterialApp 小部件的 themeMode 參數,如下所示:
theme: yourLightTheme,
darkTheme: yourDarkTheme,
themeMode: settings.useSystemTheme
? ThemeMode.system
: settings.useDarkTheme ? ThemeMode.dark : ThemeMode.light,
home: Home(),
它很干凈,適用於 Android 和 iOS。 你只需要在你所在州的某個地方擁有這兩個布爾值,useSystemTheme 和 useDarkTheme,就是這樣。
在遵循@SuperDeclarative Answer 之后執行此操作
在 main.dart 制作材料應用程序時
MaterialApp(
builder: (context, child) {
return new AppTheme(
child: YourAppWidget())
})
在您想要更改主題的任何其他課程中
setState(() {
ThemeChanger.of(context).appTheme = appThemeLight;
});
我的提示:
您可以使用Provider來更改它。
1- 您必須在 pubspec.yaml 文件中添加 Provider
dependencies:
flutter:
sdk: flutter
provider: ^4.3.2+2
2- 從ChangeNotifier擴展一個類以更改主題並保持當前主題
import 'package:flutter/material.dart';
var darkTheme = ThemeData.dark();
var lightTheme= ThemeData.light();
enum ThemeType { Light, Dark }
class ThemeModel extends ChangeNotifier {
ThemeData currentTheme = darkTheme;
ThemeType _themeType = ThemeType.Dark;
toggleTheme() {
if (_themeType == ThemeType.Dark) {
currentTheme = lightTheme;
_themeType = ThemeType.Light;
return notifyListeners();
}
if (_themeType == ThemeType.Light) {
currentTheme = darkTheme;
_themeType = ThemeType.Dark;
return notifyListeners();
}
}
}
3- 添加 ChangeNotifierProvider 作為 runApp 的子項
void main() {
runApp(
ChangeNotifierProvider<ThemeModel>(
create: (context) => ThemeModel(),
child: MyApp(),
),
);
}
4- 在啟動應用程序時獲取當前主題
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MyApp',
initialRoute: '/',
theme: Provider.of<ThemeModel>(context).currentTheme,
routes: {
'/': (context) => FirstPage(),
'/SecondPage': (context) => SecondPage(),
},
);
}
5- 在其他班級中切換您的主題
onTap: () {Provider.of<ThemeModel>(context,listen: false).toggleTheme();},
此復制/粘貼示例在運行時使用StatefulWidget
在淺色/深色主題之間更改應用主題。
(這是從 Android Studio 自動生成的 Flutter 示例應用程序,已修改。)
MyApp
從StatelessWidget
更改為StatefulWidget
( MyStatefulApp
)MyStatefulApp
static of(context)
方法(從后代中查找我們的State
對象)changeTheme()
方法添加到我們的State
對象_incrementCounter
FAB 按鈕調用將setState
重建委托給MyStatefulApp.of(context).changeTheme()
。 無需在此處調用setState
。import 'package:flutter/material.dart';
void main() {
runApp(MyStatefulApp());
}
/// Change MyApp from StatelessWidget to StatefulWidget
class MyStatefulApp extends StatefulWidget {
@override
_MyStatefulAppState createState() => _MyStatefulAppState();
/// Add an InheritedWidget-style static accessor so we can
/// find our State object from any descendant & call changeTheme
/// from anywhere.
static _MyStatefulAppState of(BuildContext context) =>
context.findAncestorStateOfType<_MyStatefulAppState>();
}
class _MyStatefulAppState extends State<MyStatefulApp> {
// define a state field for theme
ThemeData _theme = ThemeData();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Themes',
theme: _theme, // use theme field here
home: MyHomePage(title: 'Change App Theme at Runtime'),
);
}
/// Call changeTheme to rebuild app with a new theme
void changeTheme({ThemeData theme}) {
setState(() {
_theme = theme;
});
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
_counter++;
// alternate light / dark themes with each FAB press, for illustration
ThemeData _theme = _counter.isOdd ? ThemeData.dark() : ThemeData();
/// Find the State object and change the theme, can be done anywhere with
/// a context
MyStatefulApp.of(context).changeTheme(theme: _theme);
// we're rebuilding with changeTheme, so don't duplicate setState call
/*setState(() {
_counter++;
});*/
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You switched themes this many times, happy yet?:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
darkTheme
和themeMode
參數傳遞給MaterialApp
,只是改變themeMode
之間ThemeMode.light
和ThemeMode.dark
改變,而不是theme
每次ARG。 從 iOS 13 / Android 10 開始,使用themeMode
將支持設備范圍的暗模式。 上面的示例按原樣完成,以盡可能簡單/直接地回答問題,但對於此特定用例並不理想。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.