1. The blind

I didn’t know what to write about. I wanted to write about the integration test of Flutter. I wrote a set for Flutter_deer a while ago, but I felt there was nothing in it. I gave it up after only a few sentences. (Actually, this is not much…)

Write about what you’ve been doing lately. Yes, it is the DarkMode mentioned in the title of the article, which can also be said to achieve the function of night mode. I believe that many students of iOS are paying more attention recently, after all, iOS 13 pushed the update last month.

The reason for this is because it’s a new feature on iOS 13 and Android 10. The purpose of adaptation is to achieve the theme of the application changes with the switch of the theme mode of the system, giving users a better consistent experience. Similar to this is the system language setting. When the system sets a language, the text in the app changes accordingly.

Fortunately, Flutter also provides an adaptive portal, allowing us to adapt two platforms at once. The Mi Mix2s IN my hand is Android 9, but I didn’t expect it to work.

2. Preparation

Here’s my experience adapting Flutter_deer, Flutter version 1.9.1.

It is standard problem above all, the color such as title, subtitle, dividing line, all sorts of background, as well as the corresponding color below brunet mode must be standardized first. Not only will you be surprised by the colors, but there won’t be a uniform style.

3. Start adapting

1. Global adjustment

The Theme and darkTheme entries provided by Flutter in the MaterialApp allow us to set colors and text styles for the two modes. The received ThemeData contains nearly all the colors and themes used in the Material Widgets. (The Cupertino component is officially being adapted, so Flutter 1.9.1 will not support it.)

By configuring theme and darkTheme, we can save a lot of judgment code. For example, my split line is two different colors in different modes. I can’t use it every time I use it in the same place. By configuring the global dividerTheme, we can use the Divider() or BorderSide directly.

	ThemeData(
      dividerTheme: DividerThemeData(
        color: isDarkMode ? Colours.dark_line : Colours.line,
        space: 0.6,
        thickness: 0.6));Copy the code

Similarly our page background color, text style can be configured in this way. Here is the final configuration in Deer.

	ThemeData(
      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,
      // Select text color (input box copy and 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 Specifies the default Text style
        body1: isDarkMode ? TextStyles.textDark : TextStyles.text,
        // This is used 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));Copy the code

Use:

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

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

In fact, here you can also use some “pit”, such as the application of another function text in the size, color and the main text is not the same, there are many places to use, each time to judge is also very troublesome, so you can set to unused properties, such as the code in the subtitle. This can be done by calling theme.of (context).textTheme.subtitle.

Text(
  "Text", 
  style: Theme.of(context).textTheme.subtitle
)
Copy the code

One caveat: After all, this is a global configuration, so try to keep it generic and not affect other widgets.

Once you’ve done that, you need to agree to disagree.

  1. For example, if you specify a text style that matches the global configuration, you need to delete it.

  2. If the text color is the same, but the size is different. Remove the color configuration and leave the font size:

Text(
  "Keep only different information.",
  style: const TextStyle(
    fontSize: 12.0.)
)
Copy the code

Merge global configuration and local configuration. This is actually done by calling copyWith in merge. So you can also write:

Text(
  "Keep only different information.",
  style: Theme.of(context).textTheme.body1.copyWith(fontSize: 12.0))Copy the code
  1. Different colors. Since dark mode is mostly color change, consider the “subtitle” scheme above. If there are only a few, it can encapsulate some methods for unified judgment processing.

2. Local adjustment

After the global configuration, most of the adaptation problems are solved. However, there may be some details to adjust, such as ICONS, individual text colors, background colors. What is needed is how to determine the dark pattern:

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

The Brightness here is what was specified above in the global configuration of ThemeData.

Tips:

  1. Some small ICONS in solid colors can be modified directly using the image. asset color.

  2. Button textColor attribute is best or local processing, because the source “black or white”, I am very painful ah!

  /// 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);
        finalbool 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;
        returnColors.black; }}assert(false);
    return null;
  }
Copy the code

2020.01.01 added:

If the launch page needs to be adapted, consider a brief blank screen during application startup. (For example, a blank screen at startup and a black background on the startup page will not be harmonious.) The optimal way is to use Android and iOS native way to handle the transition between the app startup and the startup page.

Here’s how to make a simple version:

The Android side:

Android -> app -> SRC -> main -> res create a drawable-night folder and add the launch_background. XML file.

<? The 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" / > < - the color of the color values </item> </layer-list>Copy the code

This will use the corresponding color background in dark mode. (Of course, make sure your default styles use this file.)

The iOS side:

Change Background to System Background Color:

Click here to see a code example

3. Function expansion

You can actually extend this function a little bit if you’ve adapted the dark mode. I was thinking of the multilingual feature in wechat, where the default option is “follow the system”, although you can also specify a language.

In accordance with this idea, I added the function of “night mode” in the Settings, which also follows the system by default. Of course, you can also manually turn it on and off.

There is a temporary problem here. When I turn on the dark mode on iOS phone, when I turn off the dark mode in the app, the text in the status bar cannot turn black.

Problem is main or Flutter 1.9.1 iOS version is not adaptation 13 UIStatusBarStyleDarkContent Status Bar increases.

This issue has also been reported in Flutter issues. Look forward to the official adaptation and fix.

These, is basically adapted to the dark mode of the main content. There is nothing complicated in itself, the Lord is a careful work.

Said so much, finally put a few adaption of the effect map for everyone to see:

For detailed code and implementation details, see the flutter_deer code. The blueprints for dark mode have also been updated. Finally, I hope you can support a wave of likes!!