13.2 Themes, Splashscreens, Launcher Icons
# Themes
When building a Flutter app we create a ThemeData()
object and set it as the value of the theme
property inside the MaterialApp()
for the app.
Prior to version 3.16.0 of Flutter Material Design version 2 was the default configuration used for the theming. Starting with 3.16.0 Material Design 3 became the default configuration. If you
want to change from the default then you can add a boolean value for the useMaterial3
property.
MaterialApp(
theme: ThemeData(
useMaterial3: false,
)
)
2
3
4
5
A ThemeData
object contains many properties, beyond the userMaterial3 property, which together set all the theme settings for all the Widgets in your app. The three most common settings that you
should define are textTheme
, colorScheme
, and iconThemeData
.
# ColorScheme
ColorSchemes in Material Design are a set of 30 colour values from the Material Design Palette that together create your color scheme.
- Each property in the ColorScheme class represents one color role.
- The main accent color groups in the scheme are primary, secondary, and tertiary.
- Colours come in pairs. Eg:
Primary
andonPrimary
where the first value is a background colour and theon
property is the text colour for that background. Primary
colors are used for key components across the UI, such as the FAB, prominent buttons, and active states.Secondary
colors are used for less prominent components in the UI, such as filter chips, while expanding the opportunity for color expression.Tertiary
colors are used for contrasting accents that can be used to balance primary and secondary colors or bring heightened attention to an element, such as an input field. The tertiary colors are left for makers to use at their discretion.
So, inside our MaterialApp(theme: ThemeData())
, we can set the value for the colorScheme
property, which will build a ColorScheme
to apply to the whole app.
The default constructor for a ColorScheme
object works like the following snippet. You would set a colour value for each of the Material Design color properties plus a brightness value of
Brightness.dark
or Brightness.light
.
MaterialApp(
//call the ColorScheme default constructor and provide all the required properties
//there are 30+ properties that you can define. This example is just the required ones.
colorScheme: ColorScheme(
brightness: Brightness.light,
primary: Colors.green.shade200,
onPrimary: Colors.black,
secondary: Colors.yellow.shade200,
onSecondary: Colors.black,
error: Colors.red,
onError: Colors.red.shade900,
background: Colors.white,
onBackground: Colors.black87,
surface: Colors.purple.shade100,
onSurface: Colors.purple.shade900,
),
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
There are also a couple of alternative ways to create a ColorScheme object at the top level.
// alternate approaches to building a ColorScheme
// 1. use the fromSwatch named constructor
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.green,
brightness: Brightness.light,
accentColor: Colors.pink,
/* and a few other props */
),
// 2. use the default colorscheme and overwrite just the color properties you want to change
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: Colors.green.shade200,
onPrimary: Colors.black,
secondary: Colors.yellow.shade200,
onSecondary: Colors.black,
),
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Then, inside of any object where you want to use one of your ColorScheme values we would use the Theme.of(context)
approach. Here is an example, using the primary background colour as the background
for a Container
widget.
child: Container(
width: 100,
height: 100,
color: Theme.of(context).colorScheme.primary,
),
2
3
4
5
# TextTheme
Inside the MaterialApp
constructor we can create a default textTheme
object which will then be applied to every Text()
widget within the app. Depending on which Widget the Text()
is a child
of, a different one of these TextStyle
properties will be applied. Material Design defines which widgets get which of the TextStyles.
MaterialApp(
//provide all the settings to create a TextTheme object
textTheme: TextTheme(
displayLarge: TextStyle(fontSize: 57, /* and all the other text style properties */),
displayMedium: TextStyle(fontSize: 45),
displaySmall: TextStyle(fontSize: 36),
headlineLarge: TextStyle(fontSize: 32),
headlineMedium: TextStyle(fontSize: 28),
headlineSmall: TextStyle(fontSize: 24),
titleLarge: TextStyle(fontSize: 22),
titleMedium: TextStyle(fontSize: 16),
titleSmall: TextStyle(fontSize: 14),
bodyLarge: TextStyle(fontSize: 16),
bodyMedium: TextStyle(fontSize: 14),
bodySmall: TextStyle(fontSize: 12),
labelLarge: TextStyle(fontSize: 14),
labelMedium: TextStyle(fontSize: 12),
labelSmall: TextStyle(fontSize: 11),
),
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Each of the TextStyle
objects would define all the style properties like fontSize
, fontWeight
, color
, decoration
, letterSpacing
, etc.
There are a couple other ways that you can create your base TextTheme to apply to the rest of your app.
//alternate approaches for creating a textTheme
// 1. a predefined TextTheme object
textTheme: Typography.blackHelsinki,
// 2. override specific properties
textTheme: Theme.of(context).textTheme.copyWith(
//just override the ones you want to change from the default
//if you have included the google_fonts package you can use that here
displayMedium: GoogleFonts.lobster(
color: Colors.red,
fontSize: 20,
),
//the GoogleFonts.lobster() method returns a TextStyle object
//all the google_font methods do this.
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
We can access the TextTheme
values in the same way we would access colour values from a ColourScheme
.
child: Text('some sample text',
style: Theme.of(context).textTheme.displayMedium,
),
2
3
# IconThemeData
The IconThemeData
creates a set of properties that will apply to every Icon()
that you create within your whole app. Obviously, this top level object will only include properties that can apply to
every icon, not things like semanticLabel
, which will only apply to a single icon.
MaterialApp(
iconTheme: IconThemeData(
size: 24,
opticalSize: 48,
opacity: 1.0,
weight: 400,
color: Colors.black,
),
)
2
3
4
5
6
7
8
9
If you want to access one of these properties, you can with this - Theme.of(context).iconTheme.size
. However, using this inside of an Icon()
would be redundant because the size
property of your
Icon
will already be using that value from the top level.
# Overriding Properties
Many of the properties inside of ThemeData are properties specifically to create a style object for a type of Widget. An example would be appBarTheme: AppBarTheme()
. Each of these widget styles
contain all the style properties that would be specific to that type of Widget and those styles will be applied to every widget of that type throughout your application.
If you want to override one of those properties inside of a specific widget, then you would use Theme.of(context)
to access the root themeData object value, and then target the specific theme style
object you want to modify, and add a call to .copyWith()
. The copyWith()
method creates a copy of the style object and lets you override any of the individual properties.
Let's use AppBar widgets as an example.
In the MaterialApp(theme: )
we set up the initial values for the AppBar widget styles. This code will take all the default settings for the Material Design 3 AppBarTheme, create a copy, and then
overwrite the listed values.
MaterialApp(
theme: ThemeData(
appDataTheme: AppBarTheme.of(context).copyWith(
backgroundColor: Colors.orange,
centerTitle: true,
titleTextStyle: const TextStyle(
color: Color.fromARGB(255, 92, 135, 93),
fontSize: 40,
),
), //AppBarTheme
), //ThemeData
)
2
3
4
5
6
7
8
9
10
11
12
Now, any AppBar()
that you create will use the appDataTheme value we created above.
If we want to overwrite ONE of our AppBar's for some reason then we can repeat the process, inside of the specific AppBar
.
Scaffold(
appBar: AppBar(
centerText: false, //overrides the property set in the ThemeData
title: Text('Sample Title'),
backgroundColor: Colors.red, //overrides the property set in the ThemeData
)
)
2
3
4
5
6
7
# Mapping of ColorScheme properties to Widgets
Here's an overview of how some of the color properties from ColorScheme are typically used by different widgets.
This main.dart Gist (opens new window) is a sample page with color values set in the ColorScheme in and with content on the page that you can rearrange to test.
primary
: This color is often used for elements that need to stand out, like aFloatingActionButton
(FAB),CircleAvatar
background, and text in a button. Also forTextField
labels, and ripple effects on buttons.onPrimary
: The icon on a FAB and theCircleAvatar
text or icon.secondary
: Applied to elements that need a contrasting color to the primary, like sliders, progress indicators, and the active link background for a navigation bar button.onSecondary
: Used for elements that require a darker shade of the secondary color, similar to how primaryVariant is used. Applied to the activeIcon
in the navigation bar.surface
: Typically used for background colors of material surfaces likeAppBar
,NavigationBar
,Card
s,ElevatedButton
, dialogs, and bottom sheets.background
: Represents the background color of the overall app, in other words, theMaterialApp
background.error
: Indicates error-related elements such as error messages, error outlines, and validation errors. If a TextField has a value for errorText then all the associated text, labels, messages, etc will be this color.tertiary
: this color is there to create contrasts to balance the primary and secondary colors or to bring heightened attention to an element. In general, this color is left to the developer to apply from the color scheme.
Widget Styles
Remember that in addition to the ColorScheme property values, you have specialized theme objects that you can define in the ThemeData
which can override the TextTheme
or the ColorScheme
defaults.
Here is a list of available specialized ThemeData
properties. Note that the constructor functions are not consistent in their naming. Some end with ...Theme()
and others end with ...ThemeData()
.
ThemeData(
actionIconTheme: ActionIconThemeData(),
appBarTheme: AppBarTheme(),
buttonTheme: ButtonThemeData(),
cardTheme: CardTheme(),
datePickerTheme: DatePickerThemeData(),
dividerTheme: DividerThemeData(),
dropdownMenuTheme: DropdownMenuThemeData(),
floatingActionButtonTheme: FloatingActionButtonThemeData(),
iconTheme: IconThemeData(),
inputDecorationTheme: InputDecorationTheme(),
listTileTheme: ListTileThemeData(),
navigationBarTheme: NavigationBarThemeData(),
scaffoldBackgroundColor: Colors.white,
shadowColor: Colors.black87,
snackBarTheme: SnackBarThemeData(),
elevatedButtonTheme: ElevatedButtonThemeData(),
iconButtonTheme: IconButtonThemeData(),
textButtonTheme: TextButtonThemeData(),
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Splashscreen (aka Launch Screens)
Here is the official guide to adding a SplashScreen in Flutter (opens new window). This can be a fairly involved process and is different for both iOS and Android.
# Launcher Icons
Here is the quick general guide to adding custom Launcher Icons in Flutter (opens new window). It covers what names and locations to use when manually adding your new Launcher Icons.
The page in the Material Design guidelines with the different Launcher Icon sizes (opens new window).
The page in the Human Interface Guidelines with the different App Icon sizes (opens new window) for iOS.
Make sure to create the proper range of sizes for your custom launcher icon.
Here is a CLI Tool for Generating your launcher icons for Flutter (opens new window) and here is a guide to using the flutter_launcher_icons tool (opens new window).
# RefreshIndicator
This gives you access to the standard OS version of a spinner.
Reference for RefreshIndicator (opens new window)
# Spinners and Loaders
The Flutter_spinkit package gives you a selection of pre-built spinners that you can use when refreshing content. If you use the RefreshIndicator
, discussed above, you will get the default
spinner from the OS.
# What to do this week
TODO Things to do before next week