12 Classic Controls How to Use Text, Images and Buttons in Flutter

12 Classic Controls How to Use Text, Images and Buttons in Flutter #

Hello, I’m Chen Hang.

In the previous article, I introduced the State, the actual carrier of Widget’s lifecycle, and explained in detail the key method call sequence involved in the initialization, state update, and widget destruction stages. Understanding the process of loading, building, and destroying views can help you understand how to do the right thing at the right time according to the view’s state.

In the previous few articles, we talked a lot about the basic knowledge and principles of Flutter’s view rendering. However, some students may feel that these basic knowledge and principles are not commonly used in practice, so they choose to ignore them when learning.

But in fact, knowledge such as the view data flow mechanism, underlying rendering scheme, and view update strategy is the foundation of a UI framework. It may seem mundane but often has the longest-lasting vitality. New frameworks emerge every year, but if you peel off the cool “skin”, you will find that it is still built on these most basic knowledge and principles.

Therefore, only by understanding these most basic knowledge and cultivating your skills can you understand things easily, form your own knowledge system, and think about the rationality of building views based on the framework.

Once you have a general understanding of the basic knowledge of views, learning about the UI controls provided by the Flutter view system will be much easier. As a UI framework, Flutter naturally provides many UI controls, similar to Android, iOS, and React. Text, images, and buttons are the three most basic controls used to build views in different UI frameworks. Therefore, in today’s article, I will teach you how to use them in Flutter.

Text Widget #

Text is a common control in the view system used to display a string with a specific style, just like TextView in Android or UILabel in iOS. In Flutter, text display is implemented through the Text widget.

The Text widget supports two types of text display: one is the default display of single-style text using the Text widget, and the other is the display of rich text with multiple mixed styles using the Text.rich widget.

Let’s first see how to use Text widget to display single-style text.

To initialize a Text widget with single-style text, you need to pass in the string you want to display. The specific display effect of the string is controlled by other parameters in the constructor. These parameters can be roughly divided into two categories:

  • Parameters that control the overall text layout, such as text alignment (textAlign), text direction (textDirection), maximum number of lines to display (maxLines), text truncation rules (overflow), etc. These parameters are passed in the constructor.
  • Parameters that control the text display style, such as font name (fontFamily), font size (fontSize), text color (color), text shadow (shadows), etc. These parameters are encapsulated in the style parameter of the constructor.

Next, let’s look at an example of using the Text widget. The following code defines a string with center alignment, 20-point bold font, and red color:

Text(
  'Text is a common control in the view system used to display a string with a specific style, just like TextView in Android or UILabel in iOS.',
  textAlign: TextAlign.center, // Center align
  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.red), // Display with 20-point bold red font
);

The expected result is shown in the following image:

Sample result image

Image 1: Example of Text widget with single style

After understanding how to use the Text widget to display single-style text, let’s see how to support mixed styles in a string.

The key difference between mixed styles and single styles lies in the concept of text spans, i.e., how to divide a string into several segments and set different styles for each segment. In Android, we use SpannableString to achieve this; in iOS, we use NSAttributedString. In Flutter, the equivalent concept is called TextSpan.

TextSpan defines how to control the display style of a string segment. Combining these independently styled strings together enables the display of rich text with mixed styles.

As shown in the following code, we define two styles: black and red, and then divide a string into four segments, specifying different display styles for each segment:

TextStyle blackStyle = TextStyle(fontWeight: FontWeight.normal, fontSize: 20, color: Colors.black); // Black style

TextStyle redStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.red); // Red style

Text.rich(
    TextSpan(
        children: <TextSpan>[
          TextSpan(text:'Text is a common control in the view system used to display a string with a specific style, similar to ', style: redStyle), // Segment 1, red style 
          TextSpan(text:'Android', style: blackStyle), // Segment 2, black style 
          TextSpan(text:'\'s ', style:redStyle), // Segment 3, red style 
          TextSpan(text:'TextView', style: blackStyle) // Segment 4, black style 
        ]
    ),
  textAlign: TextAlign.center,
);

The expected result is shown in the following image:

Sample result image

Image 2: Example of Text.rich widget with mixed styles

Next, let’s take a look at the Image widget in Flutter.

Images #

With the use of Image, we can display images to users. There are many ways to display images, such as using resource images, network images, or file images. Each image may have a different format, so Flutter provides various ways to load images with different forms and formats:

  • Load local resource images, such as Image.asset('images/logo.png');
  • Load local (File) images, such as Image.file(new File('/storage/xxx/xxx/test.jpg'));
  • Load network images, such as Image.network('http://xxx/xxx/test.gif').

Besides setting different image sources based on the display mode of the image, the constructor of the Image class also provides attributes like padding mode (fit), stretching mode (centerSlice), and repeat mode (repeat). These attributes allow you to define the layout mode based on the aspect ratio difference between the image and the target area.

This is similar to the properties of ImageView in Android and UIImageView in iOS. Therefore, I won’t elaborate further here. You can refer to the Image constructor section in the official documentation to learn more about the specific usage of the Image widget.

Regarding image display, I would also like to introduce the FadeInImage widget in Flutter. When loading network images, we often include elements like placeholders and loading animations to improve user waiting experience. However, the default constructor of Image.network does not support these advanced features. This is where the FadeInImage widget comes into play.

The FadeInImage widget provides the functionality of displaying a placeholder for an image and supports the fading effect when the image finishes loading. Additionally, since Image supports GIF format, we can even use some fancy loading animations as placeholders.

The following code demonstrates this scenario. When loading a large image, we display a loading GIF as a placeholder to the user:

FadeInImage.assetNetwork(
  placeholder: 'assets/loading.gif', // placeholder GIF
  image: 'https://xxx/xxx/xxx.jpg',
  fit: BoxFit.cover, // image stretching mode
  width: 200,
  height: 200,
)

FadeInImage Figure 3: FadeInImage placeholder

The Image widget needs to determine its display effect based on the asynchronous loading of the image resource. Therefore, it is a StatefulWidget. The image loading process is triggered by an ImageProvider, which represents an asynchronous operation to fetch image data from resources, files, or the internet.

First, the ImageProvider generates the corresponding image cache key based on the image configuration passed in by the _ImageState. Then, it checks the ImageCache to see if there is a corresponding image cache. If there is, it notifies the _ImageState to update the UI. If not, it starts the ImageStream to begin asynchronous loading. After the loading is completed, it updates the cache and notifies the _ImageState to update the UI.

The process of image display can be represented by the following flowchart:

Image loading process Figure 4: Image loading process

It is worth noting that the ImageCache uses the Least Recently Used (LRU) algorithm for cache update strategy and by default, it caches up to 1000 images with a maximum limit of 100MB. When the allotted space is full, the least recently accessed images are cleared. The image cache only takes effect during runtime, which means it only caches in memory. If you want to support caching to the file system, you can use a third-party widget like CachedNetworkImage.

CachedNetworkImage has similar usage to the Image class. In addition to supporting image caching, it also provides more powerful features such as placeholder during the loading process and error placeholder when the image fails to load. It also allows more flexible custom widget placeholders compared to using images as placeholders.

In the code below, when loading an image, besides displaying a rotating loading spinner as a placeholder, we also provide a fallback error icon in case the image fails to load:

CachedNetworkImage(
  imageUrl: "http://xxx/xxx/jpg",
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

Finally, let’s take a look at the button widget in Flutter.

Buttons #

Buttons are used to respond to user interaction events. Flutter provides three basic button widgets: FloatingActionButton, FlatButton, and RaisedButton.

  • FloatingActionButton: A circular button that usually appears in front of the screen content, used to handle the most frequently used and basic user actions in the interface. In the previous article “Starting from the standard template, experiencing how Flutter code runs on the native system” [], the “+” floating button in the counter example is a FloatingActionButton.
  • RaisedButton: A raised button with a default grey background that darkens when clicked.
  • FlatButton: A flat button with a default transparent background that displays a grey background when clicked.

These three button widgets have similar usage, and the only difference is their default styles.

In the code below, I have defined FloatingActionButton, FlatButton, and RaisedButton, which have the same functionality of printing a text when clicked:

FloatingActionButton(onPressed: () => print('FloatingActionButton pressed'),child: Text('Btn'),);
FlatButton(onPressed: () => print('FlatButton pressed'),child: Text('Btn'),);
RaisedButton(onPressed: () => print('RaisedButton pressed'),child: Text('Btn'),);

Figure 5 Button widgets

Since it is a button, in addition to controlling the basic style, we also need to respond to user click events. This corresponds to the two most important parameters in the button widget:

  • The onPressed parameter is used to set the click callback, telling Flutter to notify us when the button is clicked. If the onPressed parameter is null, the button will be disabled and will not respond to user clicks.
  • The child parameter is used to set the content of the button, telling Flutter what the widget should look like, which controls the basic style of the button widget. The child can receive any widget, such as Text, and we can also pass in Image and other widgets in addition to Text in the example above.

Although we can control the basic style of the button widget through the child parameter, the default style is still too monotonous. Therefore, in most cases, we still need to customize the style of the widget.

Similar to the Text widget, the button widget also provides rich style customization features, such as background color (color), button shape (shape), theme color (colorBrightness), and so on.

Next, I will use FlatButton as an example to introduce button style customization to you:

FlatButton(
    color: Colors.yellow, // Set the background color to yellow
    shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(20.0)), // Set the shape to a beveled rectangle border
    colorBrightness: Brightness.light, // Ensure that the text is dark for the button
    onPressed: () => print('FlatButton pressed'), 
    child: Row(children: <Widget>[Icon(Icons.add), Text("Add")],)
);

As you can see, we combine an add Icon with text to define the basic appearance of the button; then we specify its shape as a beveled rectangle border through shape and set the background color of the button to yellow.

Because the background color of the button is light, to avoid the button text being unclear, we set the theme colorBrightness to Brightness.light to ensure that the button text color is dark.

The display effect is as follows:

Figure 6 Customized appearance of the button widget

Summary #

UI controls are the basic elements for building a view, and text, images, and buttons are among the most classic controls.

Next, let’s briefly review today’s content to deepen our understanding and memory.

First, we learned about the Text control, which supports two types of text display: single style and mixed style. With TextStyle, we can control the display style of the string, while other parameters control text layout, allowing us to achieve single-style text display. Using TextSpan, we can split the string into several fragments, set styles individually for each fragment, and assemble them to achieve rich text display with mixed styles.

Then, I taught you about the Image control, which supports multiple ways of loading image sources. Inside the Image control, the ImageProvider triggers the asynchronous loading process based on the cache state and notifies “_ImageState” to refresh the UI. However, since image caching is in-memory, it only takes effect during runtime. If you want to support cache to the file system, you can use a third-party library like CachedNetworkImage.

Finally, we learned about button controls. Flutter provides various button controls, and their usage is similar. The “child” parameter is used to set the appearance of the button during initialization, while the “onPressed” parameter is used to set the click callback. Similar to Text, button controls also have rich UI customization interfaces to meet developers’ needs.

Through today’s learning, we can see that in expressing basic UI information, Flutter’s classic controls have no essential differences compared to the native controls provided by Android and iOS systems. However, in customizing control styles, Flutter’s classic controls provide powerful and concise extension capabilities, allowing us to quickly develop pages with complex functionality and rich styles.

Thought-provoking question #

Finally, I leave you with a thought-provoking question.

Please open your IDE and read the source code of Text, Image, FadeInImage, as well as the button widgets FloatingActionButton, FlatButton, and RaisedButton in the Flutter SDK. Please find the widgets that actually carry their visual functions within the build function. Please share with me what you have discovered during this process.

Feel free to leave a comment in the comment section to share your thoughts. I will be waiting for you in the next article! Thank you for listening, and please feel free to share this article with more friends to read together.