Scaffold
, AppBar
, SafeArea
, Container
, Text
, RichText
, Column
, and Row
, as well as different types of buttonsIn this chapter, you'll learn how to use the most common widgets. I call them our base building blocks for creating beautiful UIs and UXs. You'll learn how to load images locally or over the Web via a uniform resource locator (URL), use the included rich Material Components icons, and apply decorators to enhance the look and feel of widgets or use them as input guides to entry fields. You'll also explore how to take advantage of the Form
widget to validate text field entry widgets as a group, not just individually. Additionally, to account for the variety of device sizes, you'll see how using the MediaQuery
or OrientationBuilder
widget is a great way to detect orientation—because using the device orientation and layout widgets accordingly based on portrait or landscape is extremely important. For example, if the device is in portrait mode, you can show a row of three images, but when the device is turned to landscape mode, you can show a row of five images since the width is a larger area than in portrait mode.
When building a mobile app, you'll usually implement certain widgets for the base structure. Being familiar with them is necessary.
Scaffold
As you learned in Chapter 4, “Creating a Starter Project Template,” the Scaffold
widget implements the basic Material Design visual layout, allowing you to easily add various widgets such as AppBar
, BottomAppBar
, FloatingActionButton
, Drawer
, SnackBar
, BottomSheet
, and more.AppBar
The AppBar
widget usually contains the standard title
, toolbar
, leading
, and actions
properties (along with buttons), as well as many customization options.title
The title
property is typically implemented with a Text
widget. You can customize it with other widgets such as a DropdownButton
widget.leading
The leading
property is displayed before the title
property. Usually this is an IconButton
or BackButton
.actions
The actions
property is displayed to the right of the title
property. It's a list of widgets aligned to the upper right of an AppBar
widget usually with an IconButton
or PopupMenuButton
.flexibleSpace
The flexibleSpace
property is stacked behind the Toolbar
or TabBar
widget. The height is usually the same as the AppBar
widget's height. A background image is commonly applied to the flexibleSpace
property, but any widget, such as an Icon
, could be used.SafeArea
The SafeArea
widget is necessary for today's devices such as the iPhone X or Android devices with a notch (a partial cut‐out obscuring the screen usually located on the top portion of the device). The SafeArea
widget automatically adds sufficient padding to the child widget to avoid intrusions by the operating system. You can optionally pass a minimum amount of padding or a Boolean value to not enforce padding on the top, bottom, left, or right.Container
The Container
widget is a commonly used widget that allows customization of its child
widget. You can easily add properties such as color
, width
, height
, padding
, margin
, border
, constraint
, alignment
, transform
(such as rotating or sizing the widget), and many others. The child
property is optional, and the Container
widget can be used as an empty placeholder (invisible) to add space between widgets.Text
The Text
widget is used to display a string of characters. The Text
constructor takes the arguments string
, style
, maxLines
, overflow
, textAlign
, and others. A constructor is how the arguments are passed to initialize and customize the Text
widget.RichText
The RichText
widget is a great way to display text using multiple styles. The RichText
widget takes TextSpan
s as children to style different parts of the strings.Column
A Column
widget displays its children vertically. It takes a children
property containing an array of List<Widget>
, meaning you can add multiple widgets. The children align vertically without taking up the full height of the screen. Each child widget can be embedded in an Expanded
widget to fill the available space. CrossAxisAlignment
, MainAxisAlignment
, and MainAxisSize
can be used to align and size how much space is occupied on the main axis.Row
A Row
widget displays its children horizontally. It takes a children
property containing an array of List<Widget>
. The same properties that the Column
contains are applied to the Row
widget with the exception that the alignment is horizontal, not vertical.RaisedButton
, FloatingActionButton
, FlatButton
, IconButton
, PopupMenuButton
, and ButtonBar
.In the next section, you'll learn how to customize the Scaffold
body
property by nesting widgets to build the page content.
The SafeArea
widget is a must for today's devices such as the iPhone X or Android devices with a notch (a partial cut‐out obscuring the screen usually located on the top portion of the device). The SafeArea
widget automatically adds sufficient padding to the child widget to avoid intrusions by the operating system. You can optionally pass minimum padding or a Boolean value to not enforce padding on the top, bottom, left, or right.
The Container
widget has an optional child
widget property and can be used as a decorated widget with a custom border, color, constraint, alignment, transform (such as rotating the widget), and more. This widget can be utilized as an empty placeholder (invisible), and if a child is omitted, it sizes to the full available screen size.
You've already used the Text
widget in the preceding examples; it's an easy widget to use but also customizable. The Text
constructor takes the arguments string
, style
, maxLines
, overflow
, textAlign
, and others.
Text(
'Flutter World for Mobile',
style: TextStyle(
fontSize: 24.0,
color: Colors.deepPurple,
decoration: TextDecoration.underline,
decorationColor: Colors.deepPurpleAccent,
decorationStyle: TextDecorationStyle.dotted,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold,
),
maxLines: 4,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.justify,
),
The RichText
widget is a great way to display text using multiple styles. The RichText
widget takes TextSpan
as children to style different parts of the strings (Figure 6.1).
A Column
widget (Figures 6.2 and 6.3) displays its children vertically. It takes a children
property containing an array of List<Widget>
. The children align vertically without taking up the full height of the screen. Each child widget can be embedded in an Expanded
widget to fill available space. You can use CrossAxisAlignment
, MainAxisAlignment
, and MainAxisSize
to align and size how much space is occupied on the main axis.
Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text('Column 1'),
Divider(),
Text('Column 2'),
Divider(),
Text('Column 3'),
],
),
A Row
widget (Figures 6.4 and 6.5) displays its children horizontally. It takes a children
property containing an array of List<Widget>
. The same properties that the Column
contains are applied to the Row
widget with the exception that the alignment is horizontal, not vertical.
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
children: <Widget>[
Text('Row 1'),
Padding(padding: EdgeInsets.all(16.0),),
Text('Row 2'),
Padding(padding: EdgeInsets.all(16.0),),
Text('Row 3'),
],
),
],
),
A great way to create unique layouts is to combine Column
and Row
widgets for individual needs. Imagine having a journal page with Text
in a Column
with a nested Row
containing a list of images (Figures 6.6 and 6.7).
Add a Row
widget inside the Column
widget. Use mainAxisAlignment:
MainAxisAlignment.spaceEvenly
and add three Text
widgets.
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text('Columns and Row Nesting 1',),
Text('Columns and Row Nesting 2',),
Text('Columns and Row Nesting 3',),
Padding(padding: EdgeInsets.all(16.0),),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('Row Nesting 1'),
Text('Row Nesting 2'),
Text('Row Nesting 3'),
],
),
],
),
There are a variety of buttons to choose from, depending on the situation, such as FloatingActionButton
, FlatButton
, IconButton
, RaisedButton
, PopupMenuButton
, and ButtonBar
.
The FloatingActionButton
widget is usually placed on the bottom right or center of the main screen in the Scaffold
floatingActionButton
property. Use the FloatingActionButtonLocation
widget to either dock (notch) or float above the navigation bar. To dock a button to the navigation bar, use the BottomAppBar
widget. By default, it's a circular button but can be customized to a stadium shape by using the named constructor FloatingActionButton.extended
. In the example code, I commented out the stadium shape button for you to test.
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.play_arrow),
backgroundColor: Colors.lightGreen.shade100,
),
// or
// This creates a Stadium Shape FloatingActionButton
// floatingActionButton: FloatingActionButton.extended(
// onPressed: () {},
// icon: Icon(Icons.play_arrow),
// label: Text('Play'),
// ),
bottomNavigationBar: BottomAppBar(
hasNotch: true,
color: Colors.lightGreen.shade100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(Icons.pause),
Icon(Icons.stop),
Icon(Icons.access_time),
Padding(
padding: EdgeInsets.all(32.0),
),
],
),
),
Figure 6.8 shows the FloatingActionButton
widget on the bottom right of the screen with the notch enabled.
The FlatButton
widget is the most minimalist button used; it displays a text label without any borders or elevation (shadow). Since the text label is a widget, you could use an Icon
widget instead or another widget to customize the button. color
, highlightColor
, splashColor
, textColor
, and other properties can be customized.
// Default - left button
FlatButton(
onPressed: () {},
child: Text('Flag'),
),
// Customize - right button
FlatButton(
onPressed: () {},
child: Icon(Icons.flag),
color: Colors.lightGreen,
textColor: Colors.white,
),
Figure 6.9 shows the default FlatButton
widget on the left and the customized FlatButton
widget on the right.
The RaisedButton
widget adds a dimension, and the elevation (shadow) increases when the user presses the button.
// Default - left button
RaisedButton(
onPressed: () {},
child: Text('Save'),
),
// Customize - right button
RaisedButton(
onPressed: () {},
child: Icon(Icons.save),
color: Colors.lightGreen,
),
Figure 6.10 shows the default RaisedButton
widget on the left and the customized RaisedButton
widget on the right.
The IconButton
widget uses an Icon
widget on a Material Component widget that reacts to touches by filling with color (ink). The combination creates a nice tap effect, giving the user feedback that an action has started.
// Default - left button
IconButton(
onPressed: () {},
icon: Icon(Icons.flight),
),
// Customize - right button
IconButton(
onPressed: () {},
icon: Icon(Icons.flight),
iconSize: 42.0,
color: Colors.white,
tooltip: 'Flight',
),
Figure 6.11 shows the default IconButton
widget on the left and the customized IconButton
widget on the right.
The
PopupMenuButton
widget displays a list of menu items. When a menu item is pressed, the value passes to theonSelected
property. A common use of this widget is placing it on the top right of theAppBar
widget for the user to select different menu options. Another example is to place thePopupMenuButton
widget in the middle of theAppBar
widget showing a list of search filters.
The ButtonBar
widget (Figure 6.12) aligns buttons horizontally. In this example, the ButtonBar
widget is a child of a Container
widget to give it a background color.
Container(
color: Colors.white70,
child: ButtonBar(
alignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
icon: Icon(Icons.map),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.airport_shuttle),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.brush),
onPressed: () {},
),
],
),
),
Images can make an app look tremendous or ugly depending on the quality of the artwork. Images, icons, and other resources are commonly embedded in an app.
The AssetBundle
class provides access to custom resources such as images, fonts, audio, data files, and more. Before a Flutter app can use a resource, you must declare it in the pubspec.yaml
file.
// pubspec.yaml file to edit
# To add assets to your application, add an assets section, like this:
assets:
—assets/images/logo.png
—assets/images/work.png
—assets/data/seed.json
Instead of declaring each asset, which can get very long, you can declare all the assets in each directory. Make sure you end the directory name with a forward slash, /
. Throughout the book, I'll use this approach when adding assets to the projects.
// pubspec.yaml file to edit
# To add assets to your application, add an assets section, like this:
assets:
—assets/images/
—assets/data/
The Image
widget displays an image from a local or URL (web) source. To load an Image
widget, there are a few different constructors to use.
Image()
—Retrieves image from an ImageProvider
classImage.asset()
—Retrieves image from an AssetBundle
class using a keyImage.file()
—Retrieves image from a File
classImage.memory()
—Retrieves image from a Uint8List
classImage.network()
—Retrieves image from a URL
pathPress Ctrl+Spacebar to invoke the code completion for the available options (Figure 6.13).
As a side note, the Image
widget also supports animated GIFs.
The following sample uses the default Image
constructor to initialize the image
and fit
arguments. The image
argument is set by using the AssetImage()
constructor with the default bundle location of the logo.png
file. You can use the fit
argument to size the Image
widget with the BoxFit
options, such as contain
, cover
, fill
, fitHeight
, fitWidth
, or none
(Figure 6.14).
// Image - on the left side
Image(
image: AssetImage("assets/images/logo.png"),
fit: BoxFit.cover,
),
// Image from a URL - on the right side
Image.network(
'https://flutter.io/images/catalog-widget-placeholder.png',
),
If you add color to the image, it colorizes the image portion and leaves any transparencies alone, giving a silhouette look (Figure 6.15).
// Image
Image(
image: AssetImage("assets/images/logo.png"),
color: Colors.deepOrange,
fit: BoxFit.cover,
),
The Icon
widget is drawn with a glyph from a font described in IconData
. Flutter's icons.dart
file has the full list of icons available from the font MaterialIcons
. A great way to add custom icons is to add to the AssetBundle
fonts containing glyphs. Once example is Font Awesome, which has a high‐quality list of icons and a Flutter package. Of course, there are many other high‐quality icons available from other sources.
The Icon
widget allows you to change the Icon
widget's color
, size
, and other properties (Figure 6.16).
Icon(
Icons.brush,
color: Colors.lightBlue,
size: 48.0,
),
Decorators help to convey a message depending on the user's action or customize the look and feel of a widget. There are different types of decorators for each task.
Decoration
—The base class to define other decorations.BoxDecoration
—Provides many ways to draw a box with border
, body
, and boxShadow
.InputDecoration
—Used in TextField
and TextFormField
to customize the border, label, icon, and styles. This is a great way to give the user feedback on data entry, specifying a hint, an error, an alert icon, and more.A BoxDecoration
class (Figure 6.17) is a great way to customize a Container
widget to create shapes by setting the borderRadius
, color
, gradient
, and boxShadow
properties.
// BoxDecoration
Container(
height: 100.0,
width: 100.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
color: Colors.orange,
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 10.0,
offset: Offset(0.0, 10.0),
)
],
),
),
The InputDecoration
class (Figure 6.18) is used with a TextField
or TextFormField
to specify labels, borders, icons, hints, errors, and styles. This is helpful in communicating with the user as they enter data. For the border property shown here, I am implementing two ways to customize it, with UnderlineInputBorder
and with OutlineInputBorder
:
// TextField
TextField(
keyboardType: TextInputType.text,
style: TextStyle(
color: Colors.grey.shade800,
fontSize: 16.0,
),
decoration: InputDecoration(
labelText: "Notes",
labelStyle: TextStyle(color: Colors.purple),
//border: UnderlineInputBorder(),
border: OutlineInputBorder(),
),
),
// TextFormField
TextFormField(
decoration: InputDecoration(
labelText: 'Enter your notes',
),
),
There are different ways to use text field widgets to retrieve, validate, and manipulate data. The Form
widget is optional, but the benefits of using a Form
widget are to validate each text field as a group. You can group TextFormField
widgets to manually or automatically validate them. The TextFormField
widget wraps a TextField
widget to provide validation when enclosed in a Form
widget.
If all text fields pass the FormState
validate
method, then it returns true
. If any text fields contain errors, it displays the appropriate error message for each text field, and the FormState
validate
method returns false
. This process gives you the ability to use FormState
to check for any validation errors instead of checking each text field for errors and not allowing the posting of invalid data.
The Form
widget needs a unique key to identify it and is created by using GlobalKey
. This GlobalKey
value is unique across the entire app.
In the next example, you'll create a form with two TextFormField
s (Figure 6.19) to enter an item and quantity to order. You'll create an Order
class to hold the item and quantity and fill the order once the validation passes.
Under certain scenarios, knowing the device orientation helps in laying out the appropriate UI. There are two ways to figure out orientation, MediaQuery.of(context).orientation
and OrientationBuilder
.
A huge note on OrientationBuilder
: it returns the amount of space available to the parent to figure out orientation. This means it does not guarantee the actual device orientation. I prefer using MediaQuery
to obtain the actual device orientation because of its accuracy.
In this chapter, you learned about the most commonly used (basic) widgets. These basic widgets are the building blocks to designing mobile apps. You also explored different types of buttons to choose depending on the situation. You learned how to add assets to your app via AssetBundle
by listing items in the pubspec.yaml
file. You used the Image
widget to load images from the local device or a web server through a URL string. You saw how the Icon
widget gives you the ability to load icons by using the MaterialIcons font library.
To modify the appearance of widgets, you learned how to use BoxDecoration
. To improve giving users feedback on data entry, you implemented InputDecoration
. Validating multiple text field data entries can be cumbersome, but you can use the Form
widget to manually or automatically validate them. Lastly, using MediaQuery
to find out the current device orientation is extremely powerful in any mobile app to lay out widgets depending on the orientation.
In the next chapter, you'll learn how to use animations. You'll start by using widgets such as AnimatedContainer
, AnimatedCrossFade
, and AnimatedOpacity
and finish with the powerful AnimationController
for custom animation.
TOPIC | KEY CONCEPTS |
Using basic widgets | You learned to use Scaffold , SafeArea , AppBar , Container , Text , RichText , Column , Row , Column and Row Nesting , Buttons , FloatingActionButton , FlatButton , RaisedButton , IconButton , PopupMenuButton , and ButtonBar . |
Using images | You learned to use AssetBundle , Image , and Icon . |
Using decorators | You learned to use Decoration , BoxDecoration , and InputDecoration . |
Using forms for text field validation | You learned to use the Form widget to validate each TextFormField as a group. |
Detecting orientation | You learned to use MediaQuery.of(context).orientation and OrientationBuilder to detect device orientation. |