Learn to create custom widgets and use the most common SDK widgets like Container, Center, and Text.
Before you start
This app relies on a bit of game logic that isn't UI-related, and thus is outside the scope of this tutorial. Before you move on, you need to add this logic to your app.
-
Download the following Dart file and save it as
lib/game.dartin your project directory. -
To enable access to the types defined in the
game.dartlibrary, add an import to it from yourlib/main.dartfile:import 'package:material_ui/material_ui.dart'; import 'game.dart';
The legalGuesses and legalWords lists in lib/game.dart only contain a
small sample of five-letter words (such as aback, abase, and abate) for
brevity. When testing your app later in this tutorial, only words defined in
lib/game.dart are accepted as valid guesses.
A Widget is a Dart class that extends one of the Flutter widget classes,
in this case StatelessWidget.
Open your main.dart file and add this code below the MainApp class,
which defines a new widget called Tile.
class Tile extends StatelessWidget {
const Tile(this.letter, this.hitType, {super.key});
final String letter;
final HitType hitType;
@override
Widget build(BuildContext context) {
return Container();
}
}Constructor
The Tile class has a constructor that defines
what data needs to be passed into the widget to render the widget.
In this case, the constructor accepts two parameters:
- A
Stringrepresenting the guessed letter of the tile. - A
HitTypeenum value represent the guess result and used to determine the color of the tile. For example,HitType.hitresults in a green tile.
Passing data into widget constructors is at the core of making widgets reusable.
Build method
Finally, there's the all important build method,
which must be defined on every widget,
and will always return another widget.
class Tile extends StatelessWidget {
const Tile(this.letter, this.hitType, {super.key});
final String letter;
final HitType hitType;
@override
Widget build(BuildContext context) {
// TODO: Replace Container with widgets.
return Container();
}
}Use the custom widget
When the app is finished,
there will be 25 instances of this widget on the screen.
For now, display just a single tile in the center of the screen
so you can see styling updates as you make them.
In the MainApp.build method,
replace the Text widget with your Tile widget:
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Tile('A', HitType.hit), // NEW
),
),
);
}
}At the moment, your app appears blank
because the Tile widget returns an empty Container,
which doesn't render anything by default.
The Container widget
The Tile widget consists of three of the most common core widgets:
Container, Center, and Text.
Container is a convenience widget that wraps several core styling widgets,
such as Padding, ColoredBox, SizedBox, and DecoratedBox.
Because the finished UI contains 25 Tile widgets in neat columns and rows,
it should have an explicit size.
Set the width and height properties on the Container.
(You could also do this with a SizedBox widget, but you'll use
more properties of the Container next.)
class Tile extends StatelessWidget {
const Tile(this.letter, this.hitType, {super.key});
final String letter;
final HitType hitType;
@override
Widget build(BuildContext context) {
// NEW
return Container(
width: 60,
height: 60,
// TODO: Add needed widgets
);
}
}BoxDecoration
Next, add a Border to the box with the following code:
class Tile extends StatelessWidget {
const Tile(this.letter, this.hitType, {super.key});
final String letter;
final HitType hitType;
@override
Widget build(BuildContext context) {
// NEW
return Container(
width: 60,
height: 60,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
// TODO: add background color
),
);
}
}BoxDecoration is an object that knows how to
add any number of decorations to a widget, from
background color to borders to box shadows and more.
In this case, you added a border.
When you hot reload,
a lightly colored border appears around the tile.
When this game is complete, the color of the tile will depend on the user's guess. The tile will be green when the user has guessed correctly, yellow when the letter is correct but the position is incorrect, and gray if the guess is wrong in both respects.
The following figure shows all three possibilities.
To achieve this in UI, use a switch expression to
set the color of the BoxDecoration.
class Tile extends StatelessWidget {
const Tile(this.letter, this.hitType, {super.key});
final String letter;
final HitType hitType;
@override
Widget build(BuildContext context) {
return Container(
width: 60,
height: 60,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
color: switch (hitType) {
HitType.hit => Colors.green,
HitType.partial => Colors.yellow,
HitType.miss => Colors.grey,
_ => Colors.white,
},
// TODO: add children
),
);
}
}Child widgets
Finally, add the Center and Text widgets to the Container.child property.
Most widgets in the Flutter SDK have a child or children property that's
meant to be passed a widget or a list of widgets, respectively.
It's the best practice to use the same naming convention in
your own custom widgets.
class Tile extends StatelessWidget {
const Tile(this.letter, this.hitType, {super.key});
final String letter;
final HitType hitType;
@override
Widget build(BuildContext context) {
return Container(
width: 60,
height: 60,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
color: switch (hitType) {
HitType.hit => Colors.green,
HitType.partial => Colors.yellow,
HitType.miss => Colors.grey,
_ => Colors.white,
},
),
child: Center(
child: Text(
letter.toUpperCase(),
style: Theme.of(context).textTheme.titleLarge,
),
),
);
}
}Hot reload, and a single green tile containing the letter A appears in the center of your app. To test different colors, try replacing the Tile widget in MainApp with one of the following examples, then hot reload:
// main.dart line ~16
// green
Tile('A', HitType.hit);
// grey
Tile('A', HitType.miss);
// yellow
Tile('A', HitType.partial);Soon, this small box will be one of many widgets on the screen. In the next lesson, you'll build the full 5x5 grid of tiles for the game board.