This is the seventh part of my journey to build and publish a Flutter based app for the App Store and Google Play.
Adding all the Material Components widgets to our app page
Adding all the MDC-Flutter widgets to the app is a giid way to learn about them. We have already build an Appbar
, MaterialApp
, Scaffold
, ButtonBar
, and, FlatButton
, (which is inside the Card
. There are a lot more.
TL:DR – In this article lets add Buttons, Floating Action Button, Chips, Data table, Dialogs, List View, Popup Menu and Navigation Drawer
Contents
Buttons
There are three main types of button to think about for our MDC-Flutter app. "Text Button", "Elevated Button" and "Outlined Button".
This is a text label displayed on a widget in a theme colour that reacts to taps by change of color. | |
This has a colour from the theme and increases elevation when tapped. | |
This is a button with a thin border. |
Flutter 1.22.0 added a group of changes affecting buttons - "New Button Universe #59702" which make them far easier to understand and theme effectively, especially for people like me who are relatively new to Flutter.
// This shows new names for buttons released in Flutter 1.22.0 (See Issue #61262)
TextButton(
onPressed: onPressed ?? () { },
child: Text('TEXT'),
),
ElevatedButton(
onPressed: onPressed ?? () { },
child: Text('CONTAINED'),
),
OutlinedButton(
onPressed: onPressed ?? () { },
child: Text('OUTLINED'),
),
The buttons pick up the underlying theme colours and auto adjust for dark appearance.
Floating action Button
A floating action button (FAB) represents the primary action of a screen. This is an extended one with text and an icon. The onPressed
method doesnt do anything yet. The colour is themed. It uses the accentColor
, so it needs to be set for theme and for dark theme. Not sure where or how it decides to style the text but it looks OK.
theme: ThemeData(
accentColor: lightwhichisdarker[200], // Accent colour is also known as the secondary colour.
...
darkTheme: ThemeData(
accentColor: darkwhichislighter[200], // Accent colour is also known as the secondary colour.
...
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
// TBD
},
label: Text('SHARE'),
icon: Icon(Icons.share),
),
Chips
Chips are compact elements that represent an attribute, text, entity, or action. There are several chip types. they are pretty useful and once you look at them in Flutter you keep noticing them in apps!
ExpansionTile(
leading: Icon(Icons.folder_special),
title: Text('Chips'),
subtitle: Text('Chip, CircleAvatar, InputChip, ChoiceChip, FilterChip, ActionChip'),
children: [
Chip(
avatar: CircleAvatar(
child: Text('C'),
),
label: Text('Chip'),
),
InputChip(
avatar: CircleAvatar(
child: Text('IC'),
),
label: Text('Input Chip'),
onPressed: () {
print('Input Chip pressed.');
}
),
ChoiceChip(
label: const Text('Choice Selected'),
selected: true,
onSelected: (bool selected) { },
),
ChoiceChip(
label: const Text('Choice'),
selected: false,
onSelected: (bool selected) { },
),
ChoiceChip(
label: const Text('Choice Disabled'),
selected: false,
),
FilterChip(
label: const Text('Filter Selected'),
selected: true,
onSelected: (bool selected) { },
),
FilterChip(
label: const Text('Filter'),
selected: false,
onSelected: (bool selected) { },
),
ActionChip(
label: const Text('Action Chip'),
onPressed: () {
print('Action Chip pressed.');
}
),
],
),
Chip, InputChip, ChoiceChip, FilterChip, and ActionChip.
Data table
Data tables display sets of data across rows and columns. The documentation warns that they are expensive (meaning that they require resources for computation) because they need to measure and negotiate dimensions for columns. It is a benefit to have the platform do this rather than try to write such code! Flutter also has PaginatedData Table
to reduce this demand by splitting data into multiple pages.
The data table component does not seem to be easy to style. Here is its code block and light appearance look on Android and iOS.
ExpansionTile(
leading: Icon(Icons.table_chart),
title: Text('Data Tables'),
subtitle: Text('textTheme'),
children: [
DataTable(
columns: const [
DataColumn(
label: Text(
'Shape',
style: TextStyle(color: parchmentLightOnSurface),
),
),
DataColumn(
label: Text(
'Points',
style: TextStyle(color: parchmentLightOnSurface),
),
),
DataColumn(
label: Text(
'Like',
style: TextStyle(color: parchmentLightOnSurface),
),
),
],
rows: const [
DataRow(
cells: [
DataCell(Text('Triangle')),
DataCell(Text('3')),
DataCell(Text('No')),
],
),
DataRow(
cells: [
DataCell(Text('Square')),
DataCell(Text('4')),
DataCell(Text('No')),
],
),
DataRow(
cells: [
DataCell(Text('Octagon')),
DataCell(Text('8')),
DataCell(Text('Yes')),
],
),
],
),
],
),
DataTable, DataColumn, DataRow, DataCell
You can see that the header row is styled, and the DataCell
is not. Sadly neither appear in Dark appearance. This will be revisited.
Dialogs
There are a couple of dialog types available; AlertDialog, for dialogs that have a message and one or more buttons and SimpleDialog, for dialogs that offer one or more options.
ExpansionTile(
leading: Icon(Icons.error),
title: Text('Dialogs'),
subtitle: Text('AlertDialog, SimpleDialog'),
children: [
AlertDialog(
title: new Text("AlertDialog"),
content: new Text("AlertDialog content"),
actions: [
new FlatButton(
child: new Text("Close"),
onPressed: () {
print('Close button pressed.');
},
),
],
),
SimpleDialog(
title: const Text('SimpleDialog'),
children: [
SimpleDialogOption(
onPressed: () {
print('Item 1 pressed.');
},
child: const Text('Item 1'),
),
SimpleDialogOption(
onPressed: () {
print('Item 2 pressed.');
},
child: const Text('Item 2'),
)
],
)
]),
AlertDialog, SimpleDialog
List View
Adding a ListView to our ExpansionTile required settingshrinkWrap and AxisDirection. Othereise this is just the ListView example with dividers from the Flutter documentation
// Values for the ListView
final List entries = ['A', 'B', 'C'];
final List colorCodes = [600, 500, 100];
// ListView Expansion Tile
ExpansionTile(
leading: Icon(Icons.list),
title: Text('ListView'),
subtitle: Text('Listview with dividers'),
children: [
ListView.separated(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(),
),
],
),
Popup Menu
It is easy to add a popup menu to the App Bar actions Widget attached to a Vertical More Icon.
PopupMenuButton(
icon: Icon(Icons.more_vert),
itemBuilder: (context) => [
PopupMenuItem(
child: Text("Settings"),
),
PopupMenuItem(
child: Text("Help and Contact Us"),
),
PopupMenuItem(
child: Text("About nameofyourproject"),
),
],
),
Adding a Navigation Drawer
Navigation drawers are commonplace now, and the Menu icon is well understood as a visual cue that there is a menu lurking behind it. Adding a Menu icon is automatic when you use the navigation drawer in Flutter, but since we had a leading icon it has to go. That is a shame but there is probably a better place for the branding. Comment out the leading: Image.asset('assets/ezone-24-white.png'),
line and add the drawer and sav and magically the drawer Menu appears. Once again, Flutter saves days and days of work and testing.
Our Navigation Drawer is pretty simple. It contains a ListView
which contains a DrawerHeader
which contains the text for the app title. The text style inherits the theme and therefore works in dark and light appearance, as does the DrawerHeader through decoration
.
Each ListTile
contains a leading icon, (which isnt yet themed), some text, and optionally a trailing Icon or text. The Icon(Icons.keyboard_arrow_right)
is often used to denote a that there is a further page to be found. The number migth represent unread items for example.
// Navigation Drawer
drawer: Drawer(
child: ListView(
children: [
DrawerHeader(
child:
Text("Your Project", style: Theme.of(context).textTheme.headline3),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
),
),
ListTile(
leading: Icon(Icons.star_border),
title: Text('Outline Star', style: Theme.of(context).textTheme.headline6),
trailing: Icon(Icons.keyboard_arrow_right),
),
ListTile(
leading: Icon(Icons.star_half),
title: Text('Half Star', style: Theme.of(context).textTheme.headline6),
trailing: Text('39', style: Theme.of(context).textTheme.subtitle2),
),
ListTile(
leading: Icon(Icons.star),
title: Text('Star', style: Theme.of(context).textTheme.headline6),
trailing: Icon(Icons.keyboard_arrow_right),
),
]
)
),
It is still a little bare, we will revisit it to add some branding and make it more interesting.
In the next article lets add the other components. Then we can move on with a view to making our app real rather than a non functional demonstration swiss army knife type app.