Flutter Widgets: Text, Icon, & Image (Assets & Network) | Ch. 8
Welcome to Chapter 8! In our last chapter, we became layout
architects. We mastered the "Big Four" of layout:
Container, Column, Row, and
Stack. We learned how to build the "skeleton" of any
UI.
But a skeleton is just bones. It's time to add the "flesh." This chapter is all about the "content" widgets—the three widgets you will use more than any others. These are the widgets that actually show your users information.
We're going to master the "Essential Three":
Text: For displaying text.Icon: For displaying vector icons.-
Image: For displaying, well... images!
The Image widget section is the most critical part
of this chapter. We will do a deep dive into the two ways to load
an image: from the network (like a URL) and, most
importantly, from your app's local assets. This
will be our first time editing the pubspec.yaml file
to add our own files, which is a fundamental skill.
Let's get started.
📖 Chapter 8: Table of Contents
1. `Text`: More Than Just Words
The Text widget is simple. You give it a string, and
it displays it.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Hello, Flutter!'),
),
);
}
Boring, right? The real power of the Text widget is
in its style property.
The style Property & TextStyle
The style property takes a
TextStyle widget. This is where you
define everything about how the text looks. We used this in our
Digital Business Card project, but let's see a full example.
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black, // Dark background
body: Center(
child: Text(
'I am the Walrus.',
style: TextStyle(
color: Colors.white,
fontSize: 32.0,
fontWeight: FontWeight.bold, // Makes the text bold
fontStyle: FontStyle.italic, // Makes the text italic
letterSpacing: 2.0, // Space between letters
wordSpacing: 10.0, // Space between words
fontFamily: 'Pacifico', // The custom font we used in Ch 5
decoration: TextDecoration.underline,
decorationColor: Colors.red,
decorationStyle: TextDecorationStyle.wavy,
),
),
),
);
}
With TextStyle, you have complete, granular control
over how your text is rendered.
color: Sets the text color.fontSize: Sets the size in logical pixels.-
fontWeight: Controls the "thickness" (e.g.,FontWeight.bold,FontWeight.w100). fontStyle:FontStyle.italic.-
decoration:TextDecoration.underline,TextDecoration.overline, orTextDecoration.lineThrough.
Text Alignment & Overflow
What if your text is very long? You have three properties to
control this: textAlign, maxLines, and
overflow.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 300, // Give the Text a limited width
color: Colors.grey[200],
padding: EdgeInsets.all(10.0),
child: Text(
'This is a very, very, very long piece of text that will almost certainly not fit on a single line. We must control the overflow!',
// 1. Alignment: How to align text within its box
textAlign: TextAlign.center, // or .left, .right, .justify
// 2. Max Lines: How many lines to show
maxLines: 2,
// 3. Overflow: What to do with the extra text
overflow: TextOverflow.ellipsis, // This is the '...'
),
),
),
);
}
The overflow property is fantastic.
-
TextOverflow.ellipsis: Shows the "..." at the end. This is the most common. -
TextOverflow.fade: Fades the text out at the end. -
TextOverflow.clip: Just cuts the text off.
RichText & TextSpan
What if you want one part of your text to be a different style? Like this: "Hello, World!"
You can't do that with a single Text widget. For
this, you need the RichText widget.
RichText works with TextSpans, which
are "spans" of text that can be nested, each with its own style.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RichText(
text: TextSpan(
// This is the "default" style for all children
style: TextStyle(
fontSize: 30.0,
color: Colors.black,
),
children: <TextSpan>[
TextSpan(text: 'Hello, '),
TextSpan(
text: 'World',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
TextSpan(text: '! Welcome to '),
TextSpan(
text: 'Flutter',
style: TextStyle(
fontStyle: FontStyle.italic,
color: Colors.green,
fontSize: 34.0,
),
),
],
),
),
),
);
}
2. `Icon`: A Thousand Pictures in One Font
The Icon widget is one of the easiest to use.
Flutter comes bundled with the entire
Material Design Icon
font.
This means you have thousands of high-quality icons ready to use, for free, with no setup.
To use one, you just call Icon(Icons.THE_ICON_NAME).
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
// We can put them in a Row (from Ch 7!)
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
Icons.phone,
color: Colors.green,
size: 50.0,
),
Icon(
Icons.email,
color: Colors.red,
size: 50.0,
),
Icon(
Icons.favorite,
color: Colors.pink,
size: 50.0,
),
Icon(
Icons.home,
color: Colors.blue,
size: 50.0,
),
],
),
),
);
}
The two properties you'll use 99% of the time are
color and
size. That's all there is to it.
It's just a piece of "text" from a special font, so it scales
perfectly and can be any color.
3. `Image`: Bringing Your App to Life
This is the big one. Your app will feel static until you add images. In Flutter, there are two primary ways to display an image:
-
Image.network: Load an image from a URL on the internet. -
Image.asset: Load an image that is bundled *inside* your app.
Image.network (The Easy Way)
This is the fastest way to get an image on the screen. Find a URL, and plug it in.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Image.network(
'https://picsum.photos/400', // A random 400x400 image
),
),
);
}
Run this, and you'll see a random image! But... what happens while it's loading? You just see a blank space. What happens if the internet is down, or the URL is broken? You get an ugly error.
This is bad UI. A *professional* app handles these cases.
Image.network gives us builders for this.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Image.network(
'https://picsum.photos/400', // A random 400x400 image
// This is what to show WHILE the image is loading
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child; // The image is done, show it
}
// The image is still loading, show a progress indicator
return Center(
child: CircularProgressIndicator(),
);
},
// This is what to show IF the image fails to load
errorBuilder: (BuildContext context, Object exception,
StackTrace? stackTrace) {
return Center(
child: Icon(
Icons.error,
color: Colors.red,
size: 40.0,
),
);
},
),
),
);
}
This is much more robust. Your user now sees a
CircularProgressIndicator (a spinning circle) while
it loads, and a helpful Icon if it fails.
Image.asset (The "Right" Way)
What about images that are *part* of your app? Your logo? A welcome banner? You can't rely on the internet for those. You need to "bundle" them with your app.
This is called an "asset," and it's a 5-step process.
This is the single most common place new Flutter developers get stuck. Pay close attention to these steps, especially the indentation in Step 3!
If you just try Image.asset('my_image.png'), your
app will crash. You must tell Flutter about your
asset first.
The 5-Step Guide to Adding Assets
Let's add a local image (e.g., logo.png) to our
project.
Step 1: Create an `assets` folder
In the root of your Flutter project (at the same level as your
lib and pubspec.yaml folders), create
a new folder. You can call it `assets` or
`images`. assets is the standard.
Step 2: Add your image to the folder
Copy your logo.png file from your computer and paste
it inside this new assets folder.
Step 3: Edit your `pubspec.yaml` file
This is the critical step. Open your pubspec.yaml
file. This is your app's "ID card" and "ingredient list" (from
Chapter 4).
Scroll down until you find the flutter: section. It
will look like this (commented out):
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
We need to "uncomment" this and tell it about our new
assets folder.
INDENTATION IS CRITICAL. YAML (the
language pubspec.yaml is written in) uses
two spaces for indentation. Not tabs.
Make your flutter: section look
exactly like this:
flutter:
uses-material-design: true
# This is the part you add
assets:
- assets/
What did we do?
-
assets:is "uncommented" (no#) and is indented with two spaces (so it lines up withuses-material-design). -
- assets/is *also* "uncommented" and is indented with four spaces (or two spaces relative toassets:). -
By adding the slash (
/) at the end, we are telling Flutter, "I want to include everything inside theassetsfolder." This is easier than adding each file one by one.
Step 4: Stop and Restart your app
When you save pubspec.yaml, your IDE will run
flutter pub get. But this is not always enough.
A simple Hot Reload is not enough to load new
assets. You must STOP your app completely and
RESTART it (Ctrl+F5 or "Run
Without Debugging").
Step 5: Use Image.asset in your code
Now, finally, you can use the widget. Flutter knows about your
assets/ folder, so you can just reference the file
path.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Image.asset(
'assets/logo.png', // The path from the root of your project
),
),
);
}
If you followed all 5 steps, your logo.png will
appear on the screen. If you get a "Failed to load asset" error,
go back and check your `pubspec.yaml`
indentation!
BoxFit: Fitting Your Image
One last thing. What if your Image is inside a
Container with a specific size, but the image is a
different shape?
Use the fit property with
BoxFit to control this.
child: Container(
width: 300,
height: 150,
color: Colors.grey,
child: Image.asset(
'assets/logo.png', // Let's say this logo is square
// This tells the image how to fit in the non-square box
fit: BoxFit.contain,
),
),
-
BoxFit.contain: (Default) The image will shrink to fit inside the box. It will not be cropped. This may leave empty space. -
BoxFit.cover: This is the most common. The image will scale up to fill the entire box. Parts of the image may be cropped to avoid empty space. -
BoxFit.fill: The image will stretch and warp to fill the box. This usually looks bad.
Conclusion: Your UI Has Content!
This chapter was a big one. You've moved from "empty boxes" to apps that can show real, dynamic content.
You've learned:
-
How to style
TextwithTextStyle, control itsoverflow, and even style individual words withRichText. -
How to use the thousands of built-in
Icons. -
How to load an
Image.networkand showloading/errorstates. -
The critical 5-step process for adding an
Image.assetto your app, including the all-importantpubspec.yamledit. -
How to control image scaling with
BoxFit.
You now have 90% of the "display" widgets you will ever need. You have the skeleton (layout) and the flesh (content). What's missing?
Interaction. How does the user *do* anything?
In the next chapter, we'll learn about user
interaction. We'll master Buttons,
TextFields (for user input), and the
GestureDetector for making any widget tappable.
Go try to build a social media "post" UI. You can do it now.
You'll need a Column, then a Row (for
the avatar and name), then an Image.network (for the
post image), and finally another Row (for the Like,
Comment, Share Icons). You have all the pieces!
Comments
Post a Comment