When you receive a design from a UX designer, from for example Zeplin, everything is specified:
- Colors
- Fonts (Name, Size, Style)
- Margins and padding of the elements
As in the Flutter counter-sample project, I started in my project with changing the primary color:
class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } }
But then I quickly realized that the primary color is a specific value in a range of colors, a ColorSwatch.
static const MaterialColor blue = MaterialColor( _bluePrimaryValue, <int, Color>{ 50: Color(0xFFE3F2FD), 100: Color(0xFFBBDEFB), 200: Color(0xFF90CAF9), 300: Color(0xFF64B5F6), 400: Color(0xFF42A5F5), 500: Color(_bluePrimaryValue), 600: Color(0xFF1E88E5), 700: Color(0xFF1976D2), 800: Color(0xFF1565C0), 900: Color(0xFF0D47A1), }, ); static const int _bluePrimaryValue = 0xFF2196F3;
Such a simple task, changing the primary color, was just not so simple anymore. How do I generate a range of colors from one specific color that was specified by the UX designer?
After some googling, I found the website: http://mcg.mbitson.com/. Here you can delegate the burden of creating a ColorSwatch. If you specify the primary color, this website will generate the color variants for you. Great!
I ended up copying the code of Colors.blue and changed it with the values from that website. Together with some other specific colors, I put that into a class:
class CompanyColor { static const MaterialColor primaryColor = MaterialColor( _primaryColor, <int, Color>{ 50: Color(0xffe1e4e8), 100: Color(0xffb3bcc6), 200: Color(0xff8090a0), 300: Color(0xff4d647a), 400: Color(0xff27425e), 500: Color(_primaryColor), 600: Color(0xff011d3b), 700: Color(0xff011832), 800: Color(0xff01142a), 900: Color(0xff000b1c), }, ); static const int _primaryColor = 0xff012141; static const Color companyBlue = Color(0xff063a65); static const Color companyOrange = Color(0xfff98c1b); static const Color companyWhite = Color(0xffffffff); static const Color companyBlueLight = Color(0xff8fa1b7); static const Color companyBlueText = Color(0xff124370); static const Color companyRed = Color(0xffcd3631); static const Color companyGreen = Color(0xff008a57); static const Color companyYellow = Color(0xfffcc634); static const Color companyGrey = Color(0xffe4e8ed); }
This was good. Now I had one place to put all the custom colors. But this was still not good enough. The UX designer has designed a consistent user interface, but I was repeating myself all over the place.
Referencing the colors by hand did not feel like applying the D.R.Y. principle. There had to be a better way. And, of course, there was: “Make a custom theme”. And the good news is that a theme is not only for colors but also for fonts, padding, etc.
Changing the theme is quite easy. Identify the object that you want to customize application-wide. Drill down to the Flutter source code.
For example for changing the cursor color:
/// The color to use when painting the cursor. /// /// Defaults to [ThemeData.cursorColor] or [CupertinoTheme.primaryColor] /// depending on [ThemeData.platform]. final Color cursorColor;
You can change that in a custom theme like this:
ThemeData companyTheme(BuildContext context) { var theme = Theme.of(context); return theme.copyWith( primaryColor: CompanyColor.primaryColor, iconTheme: theme.iconTheme.copyWith( color: CompanyColor.companyWhite, ), cursorColor: CompanyColor.companyOrange, inputDecorationTheme: theme.inputDecorationTheme.copyWith( contentPadding: EdgeInsets.only(bottom: 7.0, top: 4.0), enabledBorder: UnderlineInputBorder( borderSide: BorderSide( color: CompanyColor.underLineInputColor, ), ), focusedBorder: UnderlineInputBorder( borderSide: BorderSide( color: CompanyColor.underLineInputColor, ), ), focusedErrorBorder: UnderlineInputBorder( borderSide: BorderSide( color: CompanyColor.companyPink, ), ), errorBorder: UnderlineInputBorder( borderSide: BorderSide( color: CompanyColor.companyPink, ), ), errorStyle: TextStyle( color: CompanyColor.companyRed, fontFamily: 'Lato-Bold', fontSize: 14.0, )), ); }
Apply this theme in your MaterialApp:
@override Widget build(BuildContext context) { return MultiProvider( providers: providers(isZebra: isZebra, scanWithCamera: scanWithCamera), child: Builder( builder: (BuildContext context) => MaterialApp( localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: [const Locale('nl')], title: 'Ferwerda BV', theme: companyTheme(context), navigatorObservers: [ Provider.of<RouteObserver>( context, listen: false, ), ], home: S005StartupPage(), ), ), ); }
That’s it. Now you have all theming in one place.