Flutter Text Rendering
Learn about how Flutter renders Text widgets and see how to make your own custom text widget. By Jonathan Sande.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Flutter Text Rendering
30 mins
- Getting Started
- A Journey Through the Framework
- Stepping in: The Text Widget
- Stepping Down: Text Rendering Objects
- Way Down: Flutter’s Text Engine
- Stepping Up Your Game: Building a Custom Text Widget
- Custom Render Object
- Calculating and Measuring Text Runs
- Laying Out Runs in Lines
- Setting the size
- Painting Text to the Canvas
- Where to Go From Here?
Have you ever ridden a camel over the silent Gobi or sipped tea in a nomad’s tent? Just click the link below to enjoy this once in a lifetime opportunity. This is a limited time offer so act now while tickets last!
Just kidding. :]
This isn’t a travel site, but I will be taking you on an exciting journey to an exotic corner of the Flutter framework: text rendering. It seems so simple at first glance. Just ABC, right? Yet untold complexities lie beneath.
By the end of this tutorial you’ll:
- See the relationship between widgets, elements, and render objects.
- Explore what lies behind the
Text
andRichText
widgets. - Make your own custom text widget.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the page. This time I’m not kidding about clicking the link. Do you think you learn more by watching a travel show on TV or by actually getting on a plane and going there? Your best bet is to download the starter project and follow along.
A Journey Through the Framework
As a Flutter developer, you’re already quite familiar with stateless and stateful widgets, but they’re not the only ones. Today you’re going to learn a little about a third kind, RenderObjectWidget
, and the low level classes of the Flutter framework that support it.
The following diagram shows the Widget
subclasses, where the ones in blue are the ones I want to focus on most in this lesson.
RenderObjectWidget
is a blueprint. It holds the configuration information for RenderObject
, which does all the hard work of hit testing and painting the UI.
The following diagram shows some of the subclasses of RenderObject
. The most common one is RenderBox
, which defines a rectangular area of the screen to paint on. One of its many subclasses, RenderParagraph
, is what Flutter uses to paint text.
Guess what? In just a bit, you’re going to make your very own render paragraph from scratch. I know, I can’t wait either!
As you know, layouts in Flutter are made by composing widgets into trees. Behind the scenes there is a corresponding render object tree. But widgets and render objects don’t know how to interact with each other. Widgets can’t make a render object tree, and render objects don’t know when the widget tree changes.
That’s where elements come in. There is a corresponding element tree that has an Element
for every Widget
in the widget tree. The elements hold a reference to both the widgets and the corresponding render objects. They are the managers or intermediaries between the widgets and the render objects. Elements know when to create render objects, where to put them in a tree, how to update them when there are changes, and when to inflate (create) new elements from child widgets.
The following diagram shows the main Element
subclasses. Every widget has a corresponding element, so you’ll notice the similarity in names to the Widget
subclasses.
BuildContext
? That’s really just a nickname for Element
. Or to put it more technically, BuildContext
is an abstract class that Element
implements.The explanation above was my tour bus talk on the way to our destination. Let’s get out of the bus now and have a walk down to see a real-life example.
Stepping in: The Text Widget
You’re going to step into the Flutter source code now to see how widgets, elements, and render objects are actually used. You’ll follow a Text
widget all the way to the creation of its render object, that is, RenderParagraph
.
Don’t worry, I’ll stay with you all along the way.
Open your starter project in Android Studio 3.4 or later (with the Flutter plugin installed) and run it. You’ll need to first run flutter pub get
to get your project dependencies. In Android Studio, you can do so using the Get dependencies popup you see after opening the project.
After running the app, you should see the welcome page of a travel app called Steppe Up with the text TRAVEL MONGOLIA at the bottom.
Stop the app now.
In lib/main.dart, scroll to the bottom and find the TODO: Start your journey here line:
child: Align(
alignment: Alignment.bottomCenter,
child: Text( // TODO: Start your journey here
Strings.travelMongolia,
The widget tree here has an Align
widget with a Text
widget child. As you step through the source code, you can refer to the diagram below:
Perform the following steps:
- Command-click (or Control-click on a PC) Text to go to the widget’s source code definition. Note that
Text
is a stateless widget. - Scroll down to the
build
method. What does the method return? Surprise! It’s aRichText
widget. It turns out thatText
is justRichText
in disguise. - Command-click RichText to go to its source code definition. Note that
RichText
is aMultiChildRenderObjectWidget
. Why multi-child? In previous versions of Flutter before 1.7, it actually used to be aLeafRenderObjectWidget
, which has no children, but nowRichText
supports inline widgets with widget spans. - Scroll down to the
createRenderObject
method. There it is. This is where it createsRenderParagraph
. - Add a breakpoint to the return RenderParagraph line.
- Run the app again in debug mode.
In Android Studio if you have the Debug and Variables tabs selected, you should see something similar to the following:
You should also have the following stack trace with these lines at the top. I added the widget or element type in parentheses. The numbers on the far right refer to the comments below.
RichText.createRenderObject (RichText) // 8
RenderObjectElement.mount (RichText) // 7
MultiChildRenderObjectElement.mount (RichText)
Element.inflateWidget (Text) // 6
Element.updateChild (Text)
ComponentElement.performRebuild (Text) // 5
Element.rebuild (Text)
ComponentElement._firstBuild (Text)
ComponentElement.mount (Text) // 4
Element.inflateWidget (Align) // 3
Element.updateChild (Align) // 2
SingleChildRenderObjectElement.mount (Align) // 1
Let’s follow how RenderParagraph
was created. You won’t click every line, but starting at the 12th line from the top:
- Click SingleChildRenderObjectElement.mount. You are in the element for the
Align
widget. In your layout the child ofAlign
is aText
widget. So thewidget.child
that is getting passed intoupdateChild
is theText
widget. - Click Element.updateChild. At the end of a long method your
Text
widget, callednewWidget
, is being passed intoinflateWidget
. - Click Element.inflateWidget. Inflating a widget means creating an element from it, as you can see happens with Element newChild = newWidget.createElement(). At this point you are still in the
Align
element, but you are about to step into themount
method of theText
element that was just inflated. - Click ComponentElement.mount. You are now in the
Text
element. Component elements (likeStatelessElement
) don’t create render objects directly, but they create other elements, which will eventually create render objects. - The next exciting thing is a few methods up the stack trace. Click ComponentElement.performRebuild. Find the built = build() line. That right there, folks, is where the
build
method of theText
widget gets called.StatelessElement
uses a setter to add a reference to itself as theBuildContext
argument. Thebuilt
variable is yourRichText
widget. - Click Element.inflateWidget. This time
newWidget
isRichText
, and it’s used to create aMultiChildRenderObjectElement
. You’re still in theText
element, but you’re about to step into themount
method of theRichText
element. - Click RenderObjectElement.mount. Will you look at that? What a beautiful sight: widget.createRenderObject(this). Finally, this is where
RenderParagraph
gets created. The argumentthis
is theMultiChildRenderObjectElement
that you are in. - Click RichText.createRenderObject. And here you are out the other side. Notice that the
MultiChildRenderObjectElement
was rebranded asBuildContext
.
Is anyone tired? Since you are at a breakpoint, why don’t you take a break and drink some water. There are still a lot of great things to see and do.