Flutter:如何在运行时更改MaterialApp主题

时间:2018-03-08 02:50:39

标签: flutter

我有一个MaterialApp窗口小部件,可以为应用内的所有窗口小部件设置theme。我想在运行时更改MaterialApp s theme值,该子窗口小部件没有直接引用其父MaterialApp

似乎这应该是可能的,因为ThemeDataInheritedWidget提供,但我无法弄清楚如何更改theme批发。有谁知道怎么做?

以下是拥有应用其余部分的MaterialApp

new MaterialApp(
    title: 'App Name',
    theme: initialTheme,
    routes: <String, WidgetBuilder>{
      '/' : ...,
    },
),

9 个答案:

答案 0 :(得分:6)

根据Dan Field的建议,我得出了以下解决方案。如果有人有改进,请随意加入:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorAccent">@android:color/white</item>

        <!-- colorPrimary is used for the default action bar background -->
        <item name="colorPrimary">@color/primaryDark</item>

        <!-- colorPrimaryDark is used for the status bar -->
        <item name="colorPrimaryDark">@color/primaryDark</item>
    </style>

答案 1 :(得分:4)

这是这里回答的问题的具体情况:Force Flutter to redraw all widgets

查看该问题中提到的Stocks样本,特别注意: https://github.com/flutter/flutter/blob/master/examples/stocks/lib/main.dart https://github.com/flutter/flutter/blob/master/examples/stocks/lib/stock_settings.dart

请注意以下事项:

  1. 主题是从_configuration指定的,由configurationUpdater
  2. 更新
  3. configurationUpdater会传递给需要它的应用的子女
  4. 孩子们可以调用那个configurationUpdater,然后在应用程序的根目录设置状态,然后使用指定的主题重新绘制应用程序

答案 2 :(得分:2)

您也可以使用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);
              }),
        ])));
  }
}

答案 3 :(得分:0)

在尝试使用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
);

}

我希望这对您有用。

答案 4 :(得分:0)

您可以结合使用ChangeNotifierProvider后继包和Consumer软件包中的provider / 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;
  },
),

答案 5 :(得分:0)

您可以只使用MaterialApp小部件的themeMode参数,如下所示:

theme: yourLightTheme,
darkTheme: yourDarkTheme,
themeMode: settings.useSystemTheme
     ? ThemeMode.system
     : settings.useDarkTheme ? ThemeMode.dark : ThemeMode.light,
home: Home(),

它很干净,可以在Android和iOS上使用。您只需要在您的状态中拥有这两个布尔值,useSystemTheme和useDarkTheme即可。

答案 6 :(得分:0)

在遵循@SuperDeclarative答案后执行此操作

在main.dart上制作材质应用程序

MaterialApp(
      builder: (context, child) {
        return new AppTheme(
            child: YourAppWidget())
})

在您要更改主题的其他任何班级

setState(() {
    ThemeChanger.of(context).appTheme = appThemeLight;
  });

我的提示:

  1. 保存到共享首选项。当时从其他班级改变主题
  2. 启动之前,请检查此首选项并使用该主题打开应用程序
  3. 打开后,请遵循上述任何其他类的代码

答案 7 :(得分:0)

您可以使用 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();},

答案 8 :(得分:0)

简单示例

在运行时更改主题 /w StatefulWidget

此复制/粘贴示例在运行时使用 StatefulWidget 在浅色/深色主题之间更改应用主题。

(这是从 Android Studio 自动生成的 Flutter 示例应用,已修改。)

发生了什么变化

  1. MyAppStatelessWidget 更改为 StatefulWidget (MyStatefulApp)
  2. 静态 of(context) 方法添加到 MyStatefulApp(从后代中找到我们的 State 对象)
  3. changeTheme() 方法添加到我们的 State 对象
  4. _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),
      ),
    );
  }
}

注意事项

  • 您会看到有关已弃用的 setter 的控制台警告。忽略这个。 Flutter team is aware 他们会在有时间时修复它。
  • 要在亮/暗模式之间切换,我们应该真正为 darkTheme 提供 themeModeMaterialApp 参数,并在 themeMode 和 {{ 之间更改 ThemeMode.light 1}} 而不是每次都更改 ThemeMode.dark 参数。从 iOS 13 / Android 10 开始,使用 theme 将支持设备范围的暗模式。上面的示例是按原样完成的,目的是尽可能简单/直接地回答问题,但对于此特定用例并不理想。