Flutter adapts DarkMode: Look!Is this the black you want?

1. Causes

Recently, adapting DarkMode, or night mode, has been done.

I believe that many iOS students have been interested recently, after all, the iOS 13 was updated last month.

The reason for this is that it is new on both iOS 13 and Android 10 systems.The purpose of adapting is to achieve a more consistent user experience as the theme of the application changes as the system theme mode switches.Similar to it is the system language setting, when the system sets a language, the text in the application changes accordingly.

Fortunately, Flutter also provides an entry for adapting, allowing us to adapt to both platforms at once.My millet mix2s, although Android 9, didn't expect to fit.

2. Preparations

First of all, the standardization issues, such as the color of the title, subtitle, dividing line, various backgrounds, and the corresponding color in the dark mode must be standardized first.Otherwise, you will not only be spotted by these colors, but there is no uniform style for your application.

####3. Adaptation Start

1. Global adjustment

Flutter provides theme and darkTheme entries in MaterialApp to let us set color and text styles in both modes.The ThemeData received covers nearly all the colors and themes used in the Material Widget.(Cupertino family components are officially still being adapted, so Flutter version 1.9.1 is not supported.)

Configuring theme s and darkTheme can save us a lot of judgment code, such as my dividing line is two different colors in different modes, and I can't make judgments every time I use it, just where I use it.By configuring the global dividerTheme, we can use Divider() or BorderSide directly.

         dividerTheme: DividerThemeData(
            color: isDarkMode ? Colours.dark_line : Colours.line,
            space: 0.6,
            thickness: 0.6

We can also configure the page background color and text style in this way.Here is the final configuration sorted out in deer.

  errorColor: isDarkMode ? Colours.dark_red : Colours.red,
  brightness: isDarkMode ? Brightness.dark : Brightness.light,
  primaryColor: isDarkMode ? Colours.dark_app_main : Colours.app_main,
  accentColor: isDarkMode ? Colours.dark_app_main : Colours.app_main,
  // Tab Indicator Color
  indicatorColor: isDarkMode ? Colours.dark_app_main : Colours.app_main,
  // Page Background Color
  scaffoldBackgroundColor: isDarkMode ? Colours.dark_bg_color : Colors.white,
  // Mainly used for Material background color
  canvasColor: isDarkMode ? Colours.dark_material_bg : Colors.white,
  // Text Selection Color (Input Box Copy Paste Menu)
  textSelectionColor: Colours.app_main.withAlpha(70),
  textSelectionHandleColor: Colours.app_main,
  textTheme: TextTheme(
    // TextField Input Text Color
    subhead: isDarkMode ? TextStyles.textDark : TextStyles.text,
    // Text Default Text Style
    body1: isDarkMode ? TextStyles.textDark : TextStyles.text,
    // Here for small text styles
    subtitle: isDarkMode ? TextStyles.textDarkGray12 : TextStyles.textGray12,
  inputDecorationTheme: InputDecorationTheme(
    hintStyle: isDarkMode ? TextStyles.textHint14 : TextStyles.textDarkGray14,
  appBarTheme: AppBarTheme(
    elevation: 0.0,
    color: isDarkMode ? Colours.dark_bg_color : Colors.white,
    brightness: isDarkMode ? Brightness.dark : Brightness.light,
  dividerTheme: DividerThemeData(
    color: isDarkMode ? Colours.dark_line : Colours.line,
    space: 0.6,
    thickness: 0.6


MaterialApp (
  title: 'Flutter Deer',
  theme: getTheme(),
  darkTheme: getTheme(isDarkMode: true),
  home: TestPage()

Of course, some widgets are not used, so they are not adapted.The specific use of these color s and theme s requires you to look through the source code and comments to know, so this is a relatively laborious process.

In fact, here you can also use some "pits". For example, another function text in the application is different in font size and color from the main text, there are many places to use, it is also difficult to judge each time, so you can set to unused attributes, such as subtitle in the code above.This can be done by calling Theme.of(context).textTheme.subtitle.

  "Keep only different information",
  style: const TextStyle(
    fontSize: 12.0,

It is important to note that, after all, it is a global configuration, try to be as generic as possible, and do not affect other widget s as well.

When this assignment is complete, what you need is to "reserve differences with the same".

For example, when you specify a text style that is the same as the global configuration, you need to delete it.

If the text color is the same, but the font size is different.Then delete the color configuration information and keep the font size settings:

  "Keep only different information",
  style: const TextStyle(
    fontSize: 12.0,

Text's source code is to merge global and local configurations using the merge method.In merge, copyWith is actually called to implement it.So you can also write as follows:

  "Keep only different information",
  style: Theme.of(context).textTheme.body1.copyWith(fontSize: 12.0)

Colors are different.Since the dark mode is primarily a color change, consider the subtitle scheme above.If there are only a few, some methods can be encapsulated to unify the judgement.
2. Local adjustment
After global configuration, most of the adapting problems have been solved.But there may be some details to adjust, such as icons, individual text colors, and background colors.What you need now is how to determine the dark mode:

  bool isDarkMode(BuildContext context){
    return Theme.of(context).brightness == Brightness.dark;

Here, brightness is the brightness specified above in the Global Configuration ThemeData.


Some small solid color icons can be modified directly using the color of Image.asset.

Button's textColor property is best handled locally, because "black is white" in the source code makes me very painful!

  /// The foreground color of the [button]'s text and icon.
  /// If [button] is not [MaterialButton.enabled], the value of
  /// [getDisabledTextColor] is returned. If the button is enabled and
  /// [buttonTextColor] is non-null, then [buttonTextColor] is returned.
  /// Otherwise the text color depends on the value of [getTextTheme]
  /// and [getBrightness].
  ///  * [ButtonTextTheme.normal]: [Colors.white] is used if [getBrightness]
  ///    resolves to [Brightness.dark]. [Colors.black87] is used if
  ///    [getBrightness] resolves to [Brightness.light].
  ///  * [ButtonTextTheme.accent]: [colorScheme.secondary].
  ///  * [ButtonTextTheme.primary]: If [getFillColor] is dark then [Colors.white],
  ///    otherwise if [button] is a [FlatButton] or an [OutlineButton] then
  ///    [colorScheme.primary], otherwise [Colors.black].
  Color getTextColor(MaterialButton button) {
    if (!button.enabled)
      return getDisabledTextColor(button);

    if (button.textColor != null)
      return button.textColor;

    switch (getTextTheme(button)) {
      case ButtonTextTheme.normal:
        return getBrightness(button) == Brightness.dark ? Colors.white : Colors.black87;

      case ButtonTextTheme.accent:
        return colorScheme.secondary;

      case ButtonTextTheme.primary: {
        final Color fillColor = getFillColor(button);
        final bool fillIsDark = fillColor != null
          ? ThemeData.estimateBrightnessForColor(fillColor) == Brightness.dark
          : getBrightness(button) == Brightness.dark;
        if (fillIsDark)
          return Colors.white;
        if (button is FlatButton || button is OutlineButton)
          return colorScheme.primary;
        return Colors.black;

    return null;


If the startup page needs to be adapted, consider the brief white screen phenomenon at application startup.(For example, a white screen at startup and a black background on the startup page make it inconsistent) The best way to handle the transition between application startup and startup pages is to use Android and iOS native methods.

Here's how I'm going to use the simple version:

Android side:

Create a new drawable-night folder in the Android -> app -> SRC -> main -> res directory and add the launch_background.xml file.

<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item >
        <color android:color="#FF18191A "/> <--Specific color value

This uses the corresponding color background in dark mode.(Make sure your default style uses this file, of course)

iOS side:

Modify Background to System Background Color:

###3. Functional expansion
If you have adapted the dark mode, you can actually expand this function a little.I think about the multilingual function in WeChat. In such functions as multilingual, the default option is "Follow the System", and of course you can specify a language as well.

In this way, I added the function of "Night mode" to the settings. By default, it also follows the system. Of course, you can turn it on and off manually.

There's a temporary problem here. Turn on Dark mode on your iOS phone. When I turn off Dark mode in the app, the status bar text can't turn black.

The main problem is that the version of Flutter 1.9.1 does not match the UIStatusBarStyleDarkContent added by iOS 13 Status Bar.

There has also been feedback from Flutter's issues on this issue, so look forward to an official fix for the adapter.

These are the main content of adapting dark mode.There is nothing intricate about it, but it is a very careful work.

So much said, let's show you the effect maps of the adapter at the end:

Now a lot of company projects are using flutter. Don't hesitate to let your friends stand by the pit. It's good to play.

You can contribute a set of Flutte learning videos, trust me to pick them up, or share them out to study together

Finally, I hope you can support a wave!!!

Tags: Android iOS xml encoding

Posted on Mon, 13 Jan 2020 09:59:49 -0800 by melittle