如何在抖动中实现黑暗模式

时间:2020-02-14 18:55:40

标签: flutter dart themes android-dark-theme

我想创建一个具有2个明暗模式主题的flutter应用程序,该主题可以通过应用程序内的开关更改,默认主题为默认android主题。
我需要将一些自定义颜色传递给同一个窗口小部件,并且我不想仅配置材质主题。

  • 如何检测用户设备的默认主题?
  • 第二个问题是如何为整个应用提供主题?
  • 第三是如何通过简单地切换运行时间来更改主题?

11 个答案:

答案 0 :(得分:16)

MaterialApp(
      title: 'App Title',
      theme: ThemeData(
        brightness: Brightness.light,
        /* light theme settings */
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        /* dark theme settings */
      ),
      themeMode: ThemeMode.dark, 
      /* ThemeMode.system to follow system theme, 
         ThemeMode.light for light theme, 
         ThemeMode.dark for dark theme
      */
      debugShowCheckedModeBanner: false,
      home: YourAppHomepage(),
    );

您可以使用scoped_model获得无缝体验。

答案 1 :(得分:8)

import 'package:flutter/material.dart';

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

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

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(), // Provide light theme.
      darkTheme: ThemeData.dark(), // Provide dark theme.
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(),
        body: Container(),
      ),
    );
  }
}

答案 2 :(得分:7)

MaterialApp(
  theme: ThemeData.light(),
  /// theme: ThemeData.dark(),
)

在小部件树下,只需编写Theme.of(context)就可以访问ThemeData。如果要访问当前的ThemeData并为某些字段提供自己的样式,则可以为一个实例做

Widget build(BuildContext context) {
  var themeData = Theme.of(context).copyWith(scaffoldBackgroundColor: darkBlue)

  return Scaffold(
    backgroundColor = themeData.scaffoldBackgroundColor,
  );
}

但是要处理ThemeData状态(更改其值),您需要实施适当的状态管理。

答案 3 :(得分:6)

我认为最简单的方法是使用提供程序来管理应用程序的状态,并使用shared_preferences将主题首选项保存在文件系统上。通过执行此过程,您可以保存主题,这样用户不必每次都切换主题。

输出 enter image description here

您可以轻松地以字符串形式存储主题首选项,然后在应用程序启动时检查文件系统上是否存储了值,如果有,请按如下所示应用该主题。

StorageManager.dart

import 'package:shared_preferences/shared_preferences.dart';

class StorageManager {
  static void saveData(String key, dynamic value) async {
    final prefs = await SharedPreferences.getInstance();
    if (value is int) {
      prefs.setInt(key, value);
    } else if (value is String) {
      prefs.setString(key, value);
    } else if (value is bool) {
      prefs.setBool(key, value);
    } else {
      print("Invalid Type");
    }
  }

  static Future<dynamic> readData(String key) async {
    final prefs = await SharedPreferences.getInstance();
    dynamic obj = prefs.get(key);
    return obj;
  }

  static Future<bool> deleteData(String key) async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.remove(key);
  }
}

在如下所示的主题变量中定义主题属性,并根据存储内部的值初始化_themedata变量。

ThemeManager.dart

import 'package:flutter/material.dart';
import '../services/storage_manager.dart';

class ThemeNotifier with ChangeNotifier {
  final darkTheme = ThemeData(
    primarySwatch: Colors.grey,
    primaryColor: Colors.black,
    brightness: Brightness.dark,
    backgroundColor: const Color(0xFF212121),
    accentColor: Colors.white,
    accentIconTheme: IconThemeData(color: Colors.black),
    dividerColor: Colors.black12,
  );

  final lightTheme = ThemeData(
    primarySwatch: Colors.grey,
    primaryColor: Colors.white,
    brightness: Brightness.light,
    backgroundColor: const Color(0xFFE5E5E5),
    accentColor: Colors.black,
    accentIconTheme: IconThemeData(color: Colors.white),
    dividerColor: Colors.white54,
  );

  ThemeData _themeData;
  ThemeData getTheme() =&gt; _themeData;

  ThemeNotifier() {
    StorageManager.readData('themeMode').then((value) {
      print('value read from storage: ' + value.toString());
      var themeMode = value ?? 'light';
      if (themeMode == 'light') {
        _themeData = lightTheme;
      } else {
        print('setting dark theme');
        _themeData = darkTheme;
      }
      notifyListeners();
    });
  }

  void setDarkMode() async {
    _themeData = darkTheme;
    StorageManager.saveData('themeMode', 'dark');
    notifyListeners();
  }

  void setLightMode() async {
    _themeData = lightTheme;
    StorageManager.saveData('themeMode', 'light');
    notifyListeners();
  }
}

使用themeProvider包装您的应用,然后使用使用者应用主题。这样一来,只要您更改主题的值并致电通知侦听器,即可重新构建小部件以同步更改。

Main.dart

void main() {
  return runApp(ChangeNotifierProvider<ThemeNotifier>(
    create: (_) => new ThemeNotifier(),
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeNotifier>(
      builder: (context, theme, _) => MaterialApp(
        theme: theme.getTheme(),
        home: Scaffold(
          appBar: AppBar(
            title: Text('Hybrid Theme'),
          ),
          body: Row(
            children: [
              Container(
                child: FlatButton(
                  onPressed: () => {
                    print('Set Light Theme'),
                    theme.setLightMode(),
                  },
                  child: Text('Set Light Theme'),
                ),
              ),
              Container(
                child: FlatButton(
                  onPressed: () => {
                    print('Set Dark theme'),
                    theme.setDarkMode(),
                  },
                  child: Text('Set Dark theme'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Here是指向github存储库的链接。

答案 4 :(得分:4)

以下是实现暗模式的三种方式:

  • 始终采用深色模式
  • 设备/平台控制的暗模式
  • 应用控制、运行时可切换的暗模式

始终黑暗模式

仅在深色模式下运行您的应用:

  • MaterialApp中,将ThemeData(...)替换为ThemeData.dark()
  • 重新启动您的应用。它现在将使用 ThemeData.dark()
  • 中定义的颜色在暗模式下运行

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData.dark(), // default dark theme replaces default light theme
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

设备控制的暗模式

  • 仅适用于 Android 10+、iOS 13+(引入暗模式时)
  • 为了让设备/平台设置主题,MaterialApp 需要 3 个参数:
    • theme: ThemeData()
    • darkTheme: ThemeData().dark
    • themeMode: ThemeMode.system
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      darkTheme: ThemeData.dark(), // standard dark theme
      themeMode: ThemeMode.system, // device controls theme
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
  • (您可以使用自定义主题。为了简单起见,以上是默认值)
  • themeMode: ThemeMode.system 告诉 Flutter 使用设备/平台主题设置
  • 使用 Android 10+ 或 iOS 13+ 上的上述设置,通过设备设置切换暗模式现在可以在亮模式和暗模式之间切换您的应用。
  • 每当设备主题发生变化时,您的应用都会立即反映所选的设备主题
  • 要以编程方式获取当前设备主题模式,我们可以检查对应于亮模式和暗模式的设备亮度(Brightness.lightBrightness.dark)。为此,请使用以下内容查询 platformBrightnessMediaQuery.of(context).platformBrightness

应用控制的黑暗模式

  • 我们的应用可以在明暗模式下运行,由用户控制并在应用内运行时自由切换,完全忽略设备的主题设置
  • 和以前一样,向 MaterialApp 提供所有三个主题参数:theme:darkTheme:themeMode:,但我们将调整 themeMode: 以使用状态下面的字段
  • 要在应用内的亮/暗模式之间切换,我们将在 themeMode:ThemeMode.light 之间交换 ThemeMode.dark 参数并重建 MaterialApp 小部件。

如何重建 MaterialApp 小部件

  • 要从任何地方切换我们的应用主题,我们需要从我们应用的任何位置访问 MaterialApp
  • 我们可以在没有任何包的情况下仅使用 StatefulWidget 来完成此操作,或者我们可以使用状态管理包
  • 下面使用 StatefulWidget 在应用中的任意位置切换运行时主题的示例

之前 - 无状态

  • 我们从这个开始,但我们将用 StatefulWidget next 替换它
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      darkTheme: ThemeData.dark(), // standard dark theme
      themeMode: ThemeMode.system, // device controls theme
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

之后 - 有状态

  • 在这里,我们将 MyApp StatelessWidget 替换为 StatefulWidget 及其互补的 State_MyAppState
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      darkTheme: ThemeData.dark(), // standard dark theme
      themeMode: ThemeMode.system, // device controls theme
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

将静态访问器添加到 StatefulWidget

  • 将此静态 of() 方法添加到我们的 StatefulWidget 使其 State 对象可被任何后代小部件访问
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();

  /// ↓↓ ADDED
  /// InheritedWidget style accessor to our State object. 
  static _MyAppState of(BuildContext context) => 
      context.findAncestorStateOfType<_MyAppState>();
}

/// State object hidden ↓. Focusing on ↑ StatefulWidget here.
  • 注意我们的 Type 方法的返回 of()_MyAppState
  • 我们没有得到 StatefulWidget,我们得到的是它的 State 对象:_MyAppState
  • _MyAppState 将保存我们 ThemeMode 设置的“状态”(在下一步中)。这是控制我们应用当前主题的东西。
  • 接下来在我们的 _MyAppState 类中,我们将添加一个 ThemeMode“状态”字段和一个更改主题和重建我们的应用程序的方法

_MyAppState

  • 下面是我们的 State 类修改为:
    1. “状态”字段 _themeMode
    2. MaterialApp themeMode: arg 使用 _themeMode 状态字段值
    3. changeTheme 方法
class _MyAppState extends State<MyApp> {
  /// 1) our themeMode "state" field
  ThemeMode _themeMode = ThemeMode.system;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(),
      darkTheme: ThemeData.dark(),
      themeMode: _themeMode, // 2) ← ← ← use "state" field here //////////////
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }

  /// 3) Call this to change theme from any context using "of" accessor
  /// e.g.:
  /// MyApp.of(context).changeTheme(ThemeMode.dark);
  void changeTheme(ThemeMode themeMode) {
    setState(() {
      _themeMode = themeMode;
    });
  }
}
  • 接下来,我们将展示如何访问 changeTheme() 以更改我们的主题并重新构建应用

更改主题和重建

  • 下面是使用 of() 访问器方法查找我们的 State 对象并从下面的两个按钮调用其 changeTheme 方法的示例,该方法调用:
    • MyApp.of(context).changeTheme(ThemeMode.light)
    • MyApp.of(context).changeTheme(ThemeMode.dark)
class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Choose your theme:',
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                /// //////////////////////////////////////////////////////
                /// Change theme & rebuild to show it using these buttons 
                ElevatedButton(
                    onPressed: () => MyApp.of(context).changeTheme(ThemeMode.light),
                    child: Text('Light')),
                ElevatedButton(
                    onPressed: () => MyApp.of(context).changeTheme(ThemeMode.dark),
                    child: Text('Dark')),
                /// //////////////////////////////////////////////////////
              ],
            ),
          ],
        ),
      ),
    );
  }
}

change theme buttons

要将主题控制返回到设备的暗模式设置,请创建第三个按钮来调用将 themeMode: 设置为 ThemeMode.system

  • MyApp.of(context).changeTheme(ThemeMode.system)

运行此方法会将应用主题的控制权委托给设备当前使用的任何暗模式设置。

代码:完整的复制粘贴代码available in this gist

答案 5 :(得分:2)

截屏:

enter image description here


如果您不想使用任何第三方软件包或插件,则可以使用Flutter随附的ValueListenableBuilder

完整代码:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final _notifier = ValueNotifier<ThemeModel>(ThemeModel(ThemeMode.light));

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ThemeModel>(
      valueListenable: _notifier,
      builder: (_, model, __) {
        final mode = model.mode;
        return MaterialApp(
          theme: ThemeData.light(), // Provide light theme.
          darkTheme: ThemeData.dark(), // Provide dark theme.
          themeMode: mode, // Decides which theme to show.
          home: Scaffold(
            appBar: AppBar(title: Text('Light/Dark Theme')),
            body: RaisedButton(
              onPressed: () => _notifier.value = ThemeModel(mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light),
              child: Text('Toggle Theme'),
            ),
          ),
        );
      },
    );
  }
}

class ThemeModel with ChangeNotifier {
  final ThemeMode _mode;
  ThemeMode get mode => _mode;

  ThemeModel(this._mode);
}

答案 6 :(得分:1)

截屏:

enter image description here


您可以使用provider设置主题。这是完整的代码:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ThemeModel>(
      create: (_) => ThemeModel(),
      child: Consumer<ThemeModel>(
        builder: (_, model, __) {
          return MaterialApp(
            theme: ThemeData.light(), // Provide light theme.
            darkTheme: ThemeData.dark(), // Provide dark theme.
            themeMode: model.mode, // Decides which theme to show. 
            home: Scaffold(
              appBar: AppBar(title: Text('Light/Dark Theme')),
              body: RaisedButton(
                onPressed: () => model.toggleMode(),
                child: Text('Toggle Theme'),
              ),
            ),
          );
        },
      ),
    );
  }
}

class ThemeModel with ChangeNotifier {
  ThemeMode _mode;
  ThemeMode get mode => _mode;
  ThemeModel({ThemeMode mode = ThemeMode.light}) : _mode = mode;

  void toggleMode() {
    _mode = _mode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
    notifyListeners();
  }
}

回答OP问题:

  • 当前主题可以使用:

    bool isDarkMode = MediaQuery.of(context).platformBrightness == Brightness.dark;
    

    bool isDarkMode = SchedulerBinding.instance.window.platformBrightness == Brightness.dark;
    
  • 您可以将theme用于默认主题,将darkTheme用于深色主题(如果系统或您使用themeMode启用了深色模式,则可以为整个应用提供主题)

  • 您可以使用上面的代码中所示的提供程序包。

答案 7 :(得分:1)

您也可以使用可用的插件 day_night_theme_flutter

一个 Flutter 插件,可帮助您根据日出和日落自动更改应用程序的主题。只需指定要使用的明暗主题,就可以了。您也可以使用自定义的日出和日落时间。

如何使用?

  1. 在您的 pubspec.yaml 中添加最新版本的包
  2. 使用 DayNightTheme 小部件包装 MaterialApp。

答案 8 :(得分:1)

  theme: ThemeData.light(), // Provide light theme.
  darkTheme: ThemeData.dark(), // Provide dark theme.
  themeMode: ThemeMode.system,


  //use only these three line for dynamic change theme respect to system theme.

答案 9 :(得分:0)

这是一个代码
在这段代码中,您根据我的要求制作了自定义主题,您可以更改它!!

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Theme',
      debugShowCheckedModeBanner: false,

      /* light theme settings */
      theme: ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: Colors.white,
        brightness: Brightness.light,
        accentColor: Colors.black,
        accentIconTheme: IconThemeData(color: Colors.white),
        dividerColor: Colors.white54,
        scaffoldBackgroundColor: Colors.white,

      ),

      /* Dark theme settings */
      darkTheme: ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: Colors.black,
        brightness: Brightness.dark,
        accentColor: Colors.white,
        accentIconTheme: IconThemeData(color: Colors.black),
        dividerColor: Colors.black12,
        scaffoldBackgroundColor: Color(0xFF131313),

      ),

      /* ThemeMode.system to follow system theme,
         ThemeMode.light for light theme,
         ThemeMode.dark for dark theme */
      themeMode: ThemeMode.system,

      home: MyHomePage(),
    );
  }
}

答案 10 :(得分:0)

您可以使用内置的 ValueNotifier 在没有任何第三方状态管理的情况下实现它。这种方法允许您从应用的任何部分更改整个应用的主题。

这里是 dartpad demo

enter image description here

完整的代码示例

import 'package:flutter/material.dart';

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

final _darkNotifier = ValueNotifier<bool>(false);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<bool>(
        valueListenable: _darkNotifier,
        builder: (BuildContext context, bool isDark, Widget? child) {
          return MaterialApp(
            title: 'Flutter Demo',
            themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
            theme: ThemeData(primaryColor: Colors.blue),
            darkTheme: ThemeData.dark(),
            home: MyHomePage(
              title: 'Homepage',
            ),
          );
        });
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  void dispose() {
    // TODO: implement dispose
    _darkNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    bool isDark = _darkNotifier.value;
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              _darkNotifier.value ? 'DarkMode' : 'LightMode',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          isDark = !isDark;
          _darkNotifier.value = isDark;
        },
        tooltip: 'Increment',
        child: Icon(isDark ? Icons.wb_sunny_outlined : Icons.bubble_chart),
      ),
    );
  }
}