Dark Mode, also known as Dark Mode, is a high-contrast, or invert-color display Mode that can be turned on at night to relieve fatigue, make reading easier, and to a certain extent save energy. Starting with iOS 13 and Android 10 (this varies from vendor to vendor, but some Android 9 supports it as well), dark mode is now available on all browsers. Strong as wechat also finally in iOS client 7.0.12, Android client 7.0.13 support the dark mode, such as web side with dark mode will further improve the consistency of user experience.

Recently, I developed my own App in my spare time. At first, I started to consider the adaptation of dark mode, but at night, the interface was terrible. Although it is possible to manually configure the appearance in the system Settings, global changes will affect other apps (hate to change yourself and affect others, prefer self-perfection).

For me, adapting to dark mode is imperative:

  • Personally, I like the dark mode very much, and it is also a great blessing to independently make an App that conforms to my taste.
  • I don’t know if Apple will someday impose a darker mode. As hardware gets more powerful and memory gets bigger, people’s perception of color is getting stronger. In addition to solving users’ pain points, App interaction and color are becoming more and more important.
  • I’ve written a lot of apps, but I haven’t touched on the topic, so I can use this opportunity to learn a little bit.

demand

Users can actively set the dark mode, light mode, follow the system

To implement this requirement, ask a few questions:

  • How to set a theme
  • How do I switch themes
  • How do I save the switchover state

Analysis of the

We’re gonna go through the top one at a time.

How to set a theme

The Theme component Flutter provides a Theme component that sets the Theme of the Widget. The Theme component defines the Theme data for the Material App. Many components use theme data, such as navigation bar colors, header fonts, Icon styles, and so on. A Theme uses an InheritedWidget to share style data for its subtrees. There are two kinds of it:

  • Global Theme
  • Local Theme

The global Theme is the Theme from the MaterialApp root of the application:

/// Theme property of the global theme in the MaterialApp
// This is valid globally
MaterialApp(
  title: 'demo'.  theme: ThemeData( // Here are the parameters
 brightness: Brightness.dark,  primaryColor: Colors.lightBlue[800]. accentColor: Colors.cyan[600]. ), ); Copy the code

Partial Theme:

/// Suppose we want to set the theme style for the FloatingActionButton
/// Write a Theme around the FloatingActionButton component
/// Then set data. The receive type is still ThemeData, and fill in our parameters
/// (global theme is used by default if local theme is not set)
Theme(
 data: ThemeData(  accentColor: Colors.red,  ),  child: FloatingActionButton(  onPressed: () {},  child: Icon(Icons.add),  ), ); Copy the code

Theme Example

Extend the parent theme:

You don’t need to overwrite all the topic properties when you extend the parent theme; you can do this using the copyWith method.

Theme(
  data: Theme.of(context).copyWith(accentColor: Colors.yellow),
  child: FloatingActionButton(
    onPressed: (){},
    child: new Icon(Icons.add),
 ), ); Copy the code

Theme. Of (context) looks up the Widget tree and returns the nearest Theme in the tree. This value is returned if there is a separate Theme definition on top of the Widget. If not, the App theme is returned.

Platform-specific displays specified topics

We can also use the Platform in the IO package.

MaterialApp(
  theme: defaultTargetPlatform == TargetPlatform.iOS
      ? iOSTheme
      : AndroidTheme,
  title: 'Flutter Theme'. home: new MyHomePage(), ) Copy the code

Specifies the color based on the currently displayed mode

Theme. Of (context).brightness mode to tell whether it is now dark or light.

var isDarkTheme = Theme.of(context).brightness == Brightness.dark;

Text("APP".    color : isDarkTheme ? AppColors.darkPink : AppColors.textBlack,
)
Copy the code

ThemeData interpretation

So much for using themes, but when it comes to adaptation, we still don’t know what style changes will happen after we set the theme. ThemeData is our answer.

ThemeData({
  Brightness brightness, // The brightness of the overall theme of the application. Used by widgets such as buttons to determine which color to select when primary or accent colors are not used
  MaterialColor primarySwatch, // Theme color sample
  Color primaryColor,  // Foreground color (text, button, etc.)
  Brightness primaryColorBrightness, // The brightness of primaryColor
 Color primaryColorLight, // A lighter version of primaryColor  Color primaryColorDark, // Darker version of primaryColor  Color accentColor, // Foreground color (text, button, etc.)  Brightness accentColorBrightness, // The brightness of accentColor. Used to determine the color of text and ICONS placed at the top of the highlighted color (for example, ICONS on FloatingButton)  Color canvasColor, // The default color of the MaterialType.canvas Material  Color scaffoldBackgroundColor, // This is the default Material color that will be used as the basis for the Scaffold. It is the typical Material background color for the Scaffold applied or the inside page of the Scaffold.  Color bottomAppBarColor, // The default color of the BottomAppBar  Color cardColor, // Material is used as the color of Card  Color dividerColor, // The colors of Dividers and PopupMenuDividers are also used in the middle of the ListTiles, and in the middle of each row of DataTables  Color focusColor, For example, some button focus, input field focus.  Color hoverColor, // Click on the color after the hover, for example, button long press and hold after the color  Color highlightColor, // Used for something like an ink splash animation or to indicate that a menu is selected.  Color splashColor, // The color of the ink splash.  InteractiveInkFeatureFactory splashFactory, // Define the ink splash appearance generated by InkWall and InkResponse.  Color selectedRowColor, // Highlight the color when selecting the row  Color unselectedWidgetColor, // The color used for the Widget when it is inactive (but enabled). For example, unchecked check boxes. Often contrasted with accentColor.  Color disabledColor, // Used for colors that are not valid for widgets, regardless of any state. For example, disable check boxes  Color buttonColor, // Material Default fill color used for RaisedButtons  ButtonThemeData buttonTheme, // Defines the default configuration for controls such as buttons  ToggleButtonsThemeData toggleButtonsTheme, // Theme of ToggleButtons, a new component of Flutter 1.9  Color secondaryHeaderColor, // The color of the PaginatedDataTable header when the row is selected  Color textSelectionColor, // select the color of the text in the TextField, such as TextField  Color cursorColor, // Input box cursor color  Color textSelectionHandleColor, // The handle color used to adjust which part of the current text  Color backgroundColor, // Contrast with primaryColor (for example, for the rest of the progress bar)  Color dialogBackgroundColor, // Dialog element background color  Color indicatorColor, // The color of the indicator selected in the TabBar.  Color hintColor, // Used to indicate the color of text or placeholder text, such as in TextField.  Color errorColor, // Used for input validation error color, such as in TextField  Color toggleableActiveColor, // The color used to highlight the active status of toggle widgets such as Switch, Radio, and Checkbox.  String fontFamily, // Font style  TextTheme textTheme, // Contrast the color of the text with the card and canvas  TextTheme primaryTextTheme, // A text theme that contrasts with the main color  TextTheme accentTextTheme, // The theme of the text in contrast to the highlight color  InputDecorationTheme inputDecorationTheme, // InputDecorator, the default InputDecoration values for TextField and TextFormField are based on this theme  IconThemeData iconTheme, // An icon theme that contrasts the colors of the cards and the canvas  IconThemeData primaryIconTheme, // A picture theme in contrast to the main color  IconThemeData accentIconTheme, // The theme of the picture in contrast to the highlight color  SliderThemeData sliderTheme, // To render the Slider's color and shape  TabBarTheme tabBarTheme, // TabBar theme style  TooltipThemeData tooltipTheme, // The theme style for tooltip hints  CardTheme cardTheme, // The theme style of the card  ChipThemeData chipTheme, // To render the Chip's colors and styles  TargetPlatform platform, // The target type for the Widget  MaterialTapTargetSize materialTapTargetSize, // Set the size theme for Chip and other components  bool applyElevationOverlayColor, // Whether to apply elevation overlay color  PageTransitionsTheme pageTransitionsTheme, // Page transition theme style  AppBarTheme appBarTheme, // AppBar theme style  BottomAppBarTheme bottomAppBarTheme, // Bottom navigation theme style  ColorScheme colorScheme, // Scheme group colors, a group of 13 colors, can be used to configure the color properties of most components  DialogTheme dialogTheme, // Dialog theme style  FloatingActionButtonThemeData floatingActionButtonTheme, // FloatingActionButton theme style, which is the Scaffold property  Typography typography, // used to configure the color and geometric TextTheme values for TextTheme, primaryTextTheme, and accentTextTheme  CupertinoThemeData cupertinoOverrideTheme, // Theme styles covered by Cupertino  SnackBarThemeData snackBarTheme, // Popup snackBar theme style  BottomSheetThemeData bottomSheetTheme, // Slide out the theme style of the dialog at the bottom  PopupMenuThemeData popupMenuTheme, // Popup the theme style of the menu dialog box  MaterialBannerThemeData bannerTheme, // Material The Banner theme style of the Material  DividerThemeData dividerTheme, Divider component theme style, that is, the horizontal line component  ButtonBarThemeData buttonBarTheme, }) Copy the code

For more information on the completion, please refer to the source code comments.

Attributes are very many, usually we use about 5 to 10, if you want to highly customized may be more.

PrimarySwatch is a primarySwatch that is a primarySwatch that can be used to generate other properties under certain conditions. For example, if primaryColor is not specified and the current theme is not a dark one, The default primaryColor is the color specified by primarySwatch, and similar properties such as accentColor and indicatorColor are also affected by primarySwatch.

Toggle & Save

We can save user Settings with shared_Preferences and implement state management through the Provider.

Add the dependent

provider: ^4.0. 5
flustars: ^0.26.+1
Copy the code

practice

Defining a light theme

// light_color.dart
import 'package:flutter/material.dart';

const MaterialColor lightColor =
    MaterialColor(_lightColorPrimaryValue, <int, Color>{
 50: Color(0xFFFDEAE7),  100: Color(0xFFFACBC3),  200: Color(0xFFF7A89C),  300: Color(0xFFF48574),  400: Color(0xFFF16B56),  500: Color(_lightColorPrimaryValue),  600: Color(0xFFED4A32),  700: Color(0xFFEB402B),  800: Color(0xFFE83724),  900: Color(0xFFE42717), });  const int _lightColorPrimaryValue = 0xFFEF5138;  const MaterialColor lightColorAccent =  MaterialColor(_lightColorAccentValue, <int, Color>{  100: Color(0xFFFFFFFF),  200: Color(_lightColorAccentValue),  400: Color(0xFFFFB4AF),  700: Color(0xFFFF9C96), }); const int _lightColorAccentValue = 0xFFFFE4E2; Copy the code

Define your theme color 0xFFEF5138 and generate it through the tool. Tool address: Mbitson/MCG

Generic dark mode Provider Model class

// theme_state.dart

class ThemeState with ChangeNotifier {
  /// 0: light mode 1: dark mode 2: follow system
  int _darkMode;
 int get darkMode => _darkMode;   static const Map<int.String> darkModeMap = {0: 'Light color mode'.1: 'Dark mode'.2: 'Follow system'};   ThemeData get lightTheme =>  ThemeData(brightness: Brightness.light, primarySwatch: lightColor);  ThemeData get darkTheme => ThemeData.dark();   ThemeState() {  _init();  }   void _init() async {  await SpUtil.getInstance();  int localModel = SpUtil.getInt('kDarkMode', defValue: 2);  changeMode(localModel);  }   void changeMode(int darkMode) async {  _darkMode = darkMode;  notifyListeners();  SpUtil.putInt("kDarkMode", darkMode);  } } Copy the code

Theme selection page

// theme_page.dart
class ThemePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
 appBar: AppBar(  elevation: 0. title: Text('Theme selection'),  leading: GestureDetector(  onTap: () {  Navigator.of(context).pop();  },  child: Icon(Icons.arrow_back_ios),  ),  ),  body: Consumer<ThemeState>(  builder: (context, themeState, child) {  Map items = ThemeState.darkModeMap;  return ListView.builder(  itemBuilder: (context, index) {  return ListTile(  onTap: () {  themeState.changeMode(items.keys.toList()[index]);  },  title: Text(  items.values.toList()[index],  style: TextStyle(  color: index == themeState.darkMode  ? Colors.red  : Color(0xff333333)),  ),  );  },  itemCount: items.length,  );  },  ));  } } Copy the code

Dart integration call in Main. dart


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

class MyApp extends StatefulWidget {  @override  _MyAppState createState() => _MyAppState(); }  class _MyAppState extends State<MyApp> {   @override  Widget build(BuildContext context) {  return MultiProvider(  providers: [  ChangeNotifierProvider(create: (ctx) => ThemeState()) ]. child: Consumer<ThemeState>(  builder: (context, themeState, child) {  if (themeState.darkMode == 2) { // Follow the system  return MaterialApp(  title: 'Oldbirds'. theme: themeState.lightTheme,  darkTheme: themeState.darkTheme,  onGenerateRoute: generateRoute,  initialRoute: SplashRoute,  debugShowCheckedModeBanner: false. );  } else {  return MaterialApp(  title: 'Oldbirds'. theme: themeState.darkMode == 1 // Dark mode  ? themeState.darkTheme  : themeState.lightTheme,  onGenerateRoute: generateRoute,  initialRoute: SplashRoute,  debugShowCheckedModeBanner: false. );  }  },  ));  } } Copy the code

tips

After the above configuration is completed, the function of the dark color adaptation is completed about 80%, and the remaining ones need to be set locally on demand, and some of them, of course, need to be changed according to the color design.

The global configuration is as generic as possible, requiring a professional-level UI design specification (because there are generally design specifications).

If you have to change, it’s to agree to disagree:

  • For example, if the specified text style is the same as the global configuration, delete it

  • If the text color is the same, but the font size is different. Delete the color configuration and leave the size setting

    Text(
        "Just keep the difference.".    style: Theme.of(context).textTheme.body1.copyWith(fontSize: 14.0)
    )
    Copy the code
  • The color is different, because the dark mode is primarily a color change:

    Text(
        "Just keep the difference.".    style: Theme.of(context).textTheme.body1.copyWith(color: Colors.red, fontSize: 14.0)
    )
    Copy the code

reference

  • Fast fit dark mode of the Flutter
  • The Flutter fits nighttime mode
  • App theme color control
  • Themes in Flutter: Part 1
  • Flutter Dynamic Theme: Dark Mode & Custom Themes

To read more articles, please search our wechat official account: OldBirds