Custom widgets #
Introduction #
Now that you know about some of the built-in widgets in Flutter, it’s time to see how you can create your own reusable widgets.
All widgets are subclasses of Widget class.
You normally won’t subclass Widget
directly.
Instead, you will, depending on the situation, use one of the following as your
base class:
For now, we will just focus on StatelessWidget. You will learn about the others later on.
Introduction to StatelessWidget made by Google some years ago.
App widget #
Instead of passing the entire widget tree to the runApp()
function, most apps
wrap either MaterialApp
or CupertinoApp
in a custom widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text("Custom widget")),
body: const Center(
child: Text(
"Title",
style: TextStyle(fontSize: 48),
),
),
),
);
}
}
StatelessWidget
is an abstract class.
It has one abstract method we need to override.
That is the build method.
It returns a subtree of widgets.
Meaning, a custom widget can be composed of other widgets.
Maintainability #
Composing your app from custom widgets can have two huge benefits. It helps structure your code by breaking your big app up into many smaller chunks. The other is to avoid having to repeat the same code over and over again.
To help illustrate better, here is a slightly more complicated example.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final gradient = const LinearGradient(
colors: [
Color(0xffda44bb),
Color(0xff01579c),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text("Custom widget")),
body: Center(
child: Text(
'Fancy Title',
style: TextStyle(
fontSize: 36.0,
fontWeight: FontWeight.bold,
foreground: Paint()
..shader = gradient.createShader(
Rect.fromLTWH(1.0, 0.0, 200, 70.0),
),
),
),
),
),
);
}
}
A gradient is used to shade the text. There are two problems with the example that we can solve by introducing another widget.
- The
gradient
field is used to shade the “Fancy Title” text, but it is not next to it. - What if we need to display several titles? Then we will have repeated code.
To fix the first problem, we will extract the title as its own widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text("Custom widget")),
body: FancyTitle(),
),
);
}
}
class FancyTitle extends StatelessWidget {
final gradient = const LinearGradient(
colors: [
Color(0xffda44bb),
Color(0xff01579c),
],
);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
'Fancy Title',
style: TextStyle(
fontSize: 36.0,
fontWeight: FontWeight.bold,
foreground: Paint()
..shader = gradient.createShader(
Rect.fromLTWH(1.0, 0.0, 200, 70.0),
),
),
),
);
}
}
Tip for Android Studio
Extracting widgets is a pretty common operation. So Android Studio got a option in context menu to do it for your.
Note: You might need to do some manual adjustments of the code afterward.
To fix the second problem, we can turn the title text into a parameter for the
new FancyTitle
widget.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text("Custom widget")),
body: Column(
children: [
FancyTitle("Title1"),
FancyTitle("Title2"),
],
),
),
);
}
}
class FancyTitle extends StatelessWidget {
final gradient = const LinearGradient(
colors: [
Color(0xffda44bb),
Color(0xff01579c),
],
);
const FancyTitle(this.text, {super.key});
final String text;
@override
Widget build(BuildContext context) {
return Center(
child: Text(
text,
style: TextStyle(
fontSize: 36.0,
fontWeight: FontWeight.bold,
foreground: Paint()
..shader = gradient.createShader(
Rect.fromLTWH(1.0, 0.0, 200, 70.0),
),
),
),
);
}
}
Helper methods #
Helpers methods can also be used to break down a large build method into smaller chunks. It can be seen as an alternative to introducing new custom widgets. Here is a variation where the fancy title is defined with a helper method.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text("Custom widget")),
body: Column(
children: [
_buildFancyTitle("Title1"),
_buildFancyTitle("Title2"),
],
),
),
);
}
final gradient = const LinearGradient(
colors: [
Color(0xffda44bb),
Color(0xff01579c),
],
);
Widget _buildFancyTitle(String text) {
return Center(
child: Text(
text,
style: TextStyle(
fontSize: 36.0,
fontWeight: FontWeight.bold,
foreground: Paint()
..shader = gradient.createShader(
Rect.fromLTWH(1.0, 0.0, 200, 70.0),
),
),
),
);
}
}
Using widgets over helper methods can have some advantages. It allows the widget to be reused. And in some situations it will actually perform better as well.