13.2 Themes, Splashscreens, Launcher Icons
# Themes
Theming and styling Flutter apps can be a confusing and frustrating experience which has been complicated with the changes made over the last couple years in the names of the properties that are used. To help you out, here is a repo with an incredibly horrendous global theme (opens new window) that will help you to understand the many levels of styling that every widget goes through before being rendered on the screen as an element.
Here are the layers of styling that generally take place.
Your
MaterialApp
widget brings in the default styling properties for EVERY widget. Think of this as the collection of all the available CSS properties for every HTML element that you get in the browser. The default values are provided based on Material Design.Material design provides a global
Theme
widget and two variants of this calleddark
andlight
. Inside yourMaterialApp
widget there is a property calledtheme:
which can be set toThemeData.dark()
orThemeData.light()
. The light version is the default one that you get even without thetheme:
property.If you want to properly build a professional Flutter app, then you should not just accept the default and start adding
style
properties inside all of your Widgets. This would be the Flutter equivalent of adding astyle
attribute to every HTML tag in your website. A nightmare to maintain or edit. So, instead you should create your own theme file that generates a darkThemeData
object and a light one. This will be your styling for your whole app.
// /utils/my_theme.dart
import 'package:flutter/material.dart';
class MyTheme {
static ThemeData buildDark = () {
final ThemeData base = ThemeData.from(
colorScheme: const ColorScheme(
//define your base color properties
),
textTheme: const TextTheme(
//define the text styles for the various types of text
),
);
//add the style for different widgets
//uses the base and adds more specific default styles
final ThemeData dark = = base.copyWith(
scaffoldBackgroundColor: Colors.yellow[900],
appBarTheme: const AppBarTheme(
//style props
),
// looooooong list of different widgets with their styles
);
return dark;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
The
TextTheme
andColorScheme
in thebase
will be used throughout the app. EveryText
widget looks at its context to determine which level of text styling should be applied from the default TextTheme.After you have your base, we use the
base.copyWith()
method to add specific styling for different types of widgets. TheAppBar
,Card
,FloatingActionButton
,ListTile
, and every other kind of widget have their own Theme or ThemeData class that let you set default properties for them. In here you can often override the default colours or text styles set in step 4.With all the combined style properties in your static theme, you will have a base of styling that will reduce the amount of
style:
properties that you need to use in your Widgets by 90% or more. But what about those one-ofs? What about the situations were we need to override the theme properties? There are a couple ways of doing this.
- We can always add a
style
property inside ourText
widget and use aTextStyle
widget to define overriding style properties for the text. These will override the defaults, just like a style attribute in an HTML element. - We can use property values that start with
Theme.of(context)
like thisstyle: Theme.of(context).textTheme.headline3,
, to access values and use them from our defined theme. However, you must wrap your Widget inside of aBuilder
widget. This will go to the theme that we created in themy_theme.dart
file. - We can wrap a widget inside a
Theme
widget which needs adata:
property andchild:
. Thedata
property needs a newThemeData
object that will contain the colorScheme and textTheme to use for your widget. Thechild
property will be aBuilder
widget that has abuilder: (BuildContext context) { }
function. The function will return the widget(s) that you want to be styled with this new overridingThemeData
that you defined. Say that the builder function is returning aText()
widget. TheText
widget will have astyle
property that can access the new values like this:style: Theme.of(context).textTheme.bodyText2
. See the second line of text in theCard
widget in the demo theme repo for an example of this. - If you are trying to override the specific styles from your theme for a Button, then we can override the defined theme properties like this:
style: TextButton.styleFrom()
. There is astyleFrom
method for each of the Text, Elevated, and Outlined buttons.
# DateTime in Dart
Dart has a built-in data-type called DateTime
. In the below code sample 5 of the constructor methods are show. Once you have an object of type DateTime
then you have access to the properties for the parts of the date, like year
, month
, and day
as well as the methods for outputting the DateTime
object as a formatted String.
DateTime dt1 = DateTime.now(); //current timestamp in local timezone
print(dt1.toLocal()); // 2022-03-30 11:07:53.212
DateTime dt2 = DateTime(2020, 12, 25); //local timezone
print(dt2.toIso8601String()); // 2020-12-25T00:00:00.000 - note no time value
DateTime dt3 = DateTime.utc(2020, 12, 25); // UTC timezone
print(dt3.day); // 25
DateTime dt4 = DateTime.parse('1969-07-20 20:18:04Z'); //Moon landing
print(dt4.month); // 7
DateTime dt5 = DateTime.fromMillisecondsSinceEpoch(1600600700800);
print(dt5.year); // 2020
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Here is the reference for the DateTime class (opens new window) and if you go to the pub.dev (opens new window) website you can find the intl
package (opens new window), which includes a DateFormat
class for formatting your DateTime
objects with different locales. Here is thereference to the DateFormat class (opens new window).
# 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
Reference for RefreshIndicator (opens new window)
When you have a scrollable container that exceeds the height of its container then it will automatically apply the physics of letting the user pull the list down further than it should be able to scroll. This action leaves a white gap at the top until the user releases the list. It is generally understood that doing this could result in a refresh of the list.
To add a RefreshIndicator
to our ListView
, we will make the ListView
into the child:
of the RefreshIndicator
.
In Flutter, the way we get the refresh icon to ALWAYS appear is to add physics: const AlwaysScrollableScrollPhysics(),
to the ListView
and then the behaviour will be to slowly fade in a circular refresh icon. When the icon becomes fully opaque, then the onRefresh callback is triggered.
Note: The refresh indicator only works on a vertical ListView.
Scaffold(
body: Padding(
padding: EdgeInsets.all(16.0),
child: RefreshIndicator(
onRefresh: () {
//this runs when refresh is triggered
//call a function to do a new fetch
//or setState()
//or whatever you like
},
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: mylist.length,
itemBuilder: (context, item) {
return Text(mylist[item]);
}
)
)
)
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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
- Finish your Hybrid step six assignment
- Work with your partner to start building your Flutter Final Project