Flutter: Text Widget

In this post we’re going to look at the Text widget in Flutter and some of the options you can tweak when displaying text within your app. If you’re after a more detailed discussion of strings, characters and how they’re displayed, you should check out the post, Mastering Styled Text in Flutter. To get into it, we’re going to start off with a new Flutter project, which already displays text to indicate how many times the button has been pressed. The text is broken into two Text widgets to allow for the styling of the actual counter value to be different from the preceding text.

Text Constructor

This code snippet shows us a couple of things: firstly, that the first parameter of the Text constructor is the string to be displayed; secondly, we can override, or set, the style of the text using the style parameter. This, of course, prompts the question as to what parameters are there on the Text widget and what do they mean.

Let’s take a look at the Text constructor – select the Text widget and press F12 in Visual Studio Code to be taken to the actual code for the Text class and its constructor:

class Text extends StatelessWidget {
  const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
    this.textWidthBasis,
  })

As we can see from the constructor definition, there is a single data parameter, followed by a list of optional parameters. Let’s step through these parameters and take a look at what they do.

Text.style

The first optional parameter of the Text widget is “style” which is of type TextStyle. As you’d imagine the TextStyle class can be used to specify the foreground and background color, the font size and weight, letter and word spacing, locale, shadows and much more.

What’s interesting is that the first Text widget shown above, we didn’t specify a TextStyle, and yet the text was still displayed on the screen with a basic style applied. This is because when the style property isn’t set, the Text widget will search the widget tree looking for style information to use. To demonstrate this, try passing a Text widget into the runApp method.

void main() => runApp(Text("test"));

This will actually fail to run stating that RichText widgets require a Directionality widget ancestor. We can fix this easily by wrapping the Text widget with a Directionality widget as follows:

void main() => runApp(Directionality(textDirection: TextDirection.ltr, child: Text("test")));

After doing this, what we’re left with on-screen is a very unstyled piece of text situated in the top left corner of the screen.

Returning now to our application, the question becomes, where does the default styling for Text come from. The answer to this question can be found in the documentation for the style property (press F12 in VS Code to navigate to the property definition where you can find the relevant documentation). The documentation states that the style property will be merged with the style associated with the closest enclosing DefaultTextStyle (assuming the inherit property on the style is set to true). What this means is that the Text widget will traverse up the widget hierarchy looking for a DefaultTextStyle widget on which to base the style of the text on.

So the next question you’re probably going to ask is where is the DefaultTextStyle widget being added to the hierarchy because it doesn’t appear anywhere in the app that was generated when we created the new project. Well, it might surprise you to know that the DefaultTextStyle widget appears at least twice, being added by both the MaterialApp and Scaffold widgets respectively. The following diagram illustrates just part of the widget hierarchy that shows the DefaultTextStyle appearing below the AnimatedDefaultTextStyle node.

Coming back to our Text widget, if we specify a value for the style property, it will be merged with the style of the nearest DefaultTextStyle widget. For example, let’s change the colour of the text to purple.

Text(
  'You have pushed the button this many times:',
  style: TextStyle(color: Colors.purple),
),

Alternatively, as we saw in the second Text widget in the initial example, the style of the Text widget can be set based on the current Theme. The Theme defines a number of different text styles that can be used – check out the Flutter documentation for more information on the individual text styles. In the following code, the display1 style is applied to the Text widget.

Text(
  '$_counter',
  style: Theme.of(context).textTheme.display1,
),

Sometime you’ll want to use one of the theme text styles but you want to adjust one or more attributes. For this, you can use the apply method as in the following example that sets the colour of the text to purple.

Text(
  '$_counter',
  style: Theme.of(context).textTheme.display1.apply(color: Colors.purple),
),

Since we’ve now set the colour of both Text widgets to purple, it would be good to be able to extract this so that it’s only applied once and will affect both Text widgets. This comes back to what I was saying about the DefaultTextStyle widget and how the style property merges with the nearest DefaultTextStyle widget – in order to set an attribute that should apply to all Text widget, we just need to add a DefaultTextStyle widget to the hierarchy, setting the appropriate attribute.

child: DefaultTextStyle(
  style: TextStyle(color: Colors.purple),
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Text(
        'You have pushed the button this many times:',
      ),
      Text('$_counter', style: Theme.of(context).textTheme.display1),
    ],
  ),
),

Note that the DefaultTextStyle widget also merges its style with the style of the nearest parent DefaultTextStyle widget.

Text.strutStyle    

Setting the strutStyle property gives you the ability to fine-tune the separation between rows of text. For example, if you have a number of Text widgets that have differing font style and sizes, you can specify the strutStyle to ensure the same spacing between each row.

Text(
  'This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  style: TextStyle(fontSize: 12),
  strutStyle: StrutStyle(fontSize: 13),
),
Text(
  'This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  style: TextStyle(fontSize: 14),
  strutStyle: StrutStyle(fontSize: 13),
),
Text(
  'This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  style: TextStyle(fontSize: 12),
  strutStyle: StrutStyle(fontSize: 13),
),

As you can see from the following image, despite the text in the middle section being a slightly larger font, the line separation is uniform across all lines of text.

Further information on using the strutStyle property can be found at the StrutStyle documentation

Text.textAlign and Text.textDirection

I’ve grouped the textAlign and textDirection properties together as they are related as the textDirection determines how some textAlign values control the layout of text in the Text widget. In the following example there are five Text widgets, with different combination of textAlign and textDirection property values.

Text(
  'JUSTIFIED - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.justify,
),
Text(
  'LEFT (LTR) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.left,
  textDirection: TextDirection.ltr,
),
Text(
  'LEFT (RTL) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.left,
  textDirection: TextDirection.rtl,
),
Text(
  'START (LTR) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.start,
  textDirection: TextDirection.ltr,
),
Text(
  'START (RTL) - This is a very long piece of text designed to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
  textAlign: TextAlign.start,
  textDirection: TextDirection.rtl,
),

When we run this code we can see that the textAlign property does what you’d expect it to – the text is justified for the TextAlign.justify value and aligned left for TextAlign.left. What’s interesting is that TextAlign also includes values start and end which are important if you’re considering supporting RTL languages within your application. As you can see from the following image with textAlign set to TextAlign.start, if we adjust the textDirection between LTR and RTL we can see that the text changes from left aligned to right aligned.

Text.locale

One of the most common reason for explicitly setting the locale for a Text widget is to adjust the text that is being rendered. Adjusting the locale will change the way certain unicode characters are displayed.

Text.softWrap

Setting the softWrapp property to false will disable wrapping, causing the text to be truncated by the right edge of the parent container.

Text.overflow

The overflow property controls what happens when there is more text than will fit in the space available. For example you can use the predefined value TextOverflow.ellipsis for Flutter to insert … when there isn’t sufficient space. Note that ellipsis will disable wrapping, regardless of how much space there is available (see maxLines discussion below).

Another possible overflow value is TextOverflow.fade

Container(
  height: 30,
  child: Text(
    'This is a very long piece of text designed verylongwordwithnospaces to wrap over multiple lines. This is a very long piece of text designed to wrap over multiple lines.',
    overflow: TextOverflow.fade,
  ),
),

Using TextOverflow.fade in conjunction with a fixed height on the parent Container rejusts in the following effect where the first Test widget fades out before the second Test widget.

Text.textScaleFactor

The textScaleFactor can be set to apply an arbitrary scaling to the text being displayed by the Text widget. Note that this will override any textScaleFactor applied by the current MediaQuery, so may result in incorrect layouts on devices where the textScaleFactor isn’t null or 1.0.

Text.maxLines

Where the text being displayed stretches over multiple lines, the maxLines property defines the maximum number of lines that will be displayed.

When the overflow property is set to TextOverflow.ellipse the maxLines property can be set to increase the number of lines of text that will be displayed before the ellipses are appended.

Text.semanticsLabel    

You can use the semanticsLabel property to improve the way that screen reader and assistive technologies work with your application. Check out this post on Semantics for further information.

Text.textWidthBasis

The textWidthBasis property can control how the width of the Text widget is defined. More information is available via the documentation.

Summary

In this post, I’ve walked through the use of the Text widget. I would highly recommend pressing F12 on a Text widget and go explore the source code. It’s worth to note that other classes, such as RightText and TextSpan, that exist – I’ll leave it to the reader to drill into these and understand how they’re related.

Leave a comment