Flutter Analysis and Practice: RichText Best Practices

2.4.1 How RichText Works

This article describes how RichText works.

Figure 2–14
  • The ComponentElement method calls the CreateRenderObject method of the RichText instance to generate the RenderParagraph instance.
  • The RenderParagraph instance creates a TextPainter, which is a proxy class responsible for width and height calculation and text-to-canvas rendering. In addition, TextPainter also has a TextSpan text structure.
Figure 2–15

2.4.2 Design

Based on the text rendering principle of RichText, we can see that TextSpan records the text information of each text segment. TextPaint calls a native API and calculates the width and height based on the recorded information, and renders the text to the canvas. In the traditional solutions, complex hybrid layouts are rendered using HTML WebView rich text. The performance of WebView is worse than the native implementation. To solve the problem, we tried to design a native solution for hybrid image-text layouts. The initial design was to use special spans, such as ImageSpan and EmojiSpan to record information and use that to calculate layouts in TextPaint Layout. Then, special widgets were rendered separately in the Paint process. However, this solution does considerable damage to class encapsulations and RichText and RenderParagraph source code must be replicated and modified. The final solution is to use special characters (such as an empty string) as placeholders first and move the special spans to the positions, as shown in Figure 2-16.

Figure 2–16
/// The amount of space (in logical pixels) to add between each letter
/// A negative value can be used to bring the letters closer.
final double letterSpacing;
/// Returns the offset at which to paint the caret.
///
/// Valid only after [layout] has been called.
Offset getOffsetForCaret(TextPosition position, Rect caretPrototype)

2.4.3 Key Code Implementation

  • Unified placeholder SpaceSpans.
SpaceSpan({
this.contentWidth,
this.contentHeight,
this.widgetChild,
GestureRecognizer recognizer,
}) : super(
style: TextStyle(
color: Colors.transparent,
letterSpacing: contentWidth,
height: 1.0,
fontSize:
contentHeight),
text: '\u200B',
recognizer: recognizer);
for (TextSpan textSpan in widget.text.children) {
if (textSpan is SpaceSpan) {
final SpaceSpan targetSpan = textSpan;
Offset offsetForCaret = painter.getOffsetForCaret(
TextPosition(offset: textIndex),
Rect.fromLTRB(
0.0, targetSpan.contentHeight, targetSpan.contentWidth, 0.0),
);
........
}
textIndex += textSpan.toPlainText().length;
}
Stack(
children: <Widget>[
RichText(),
Positioned(left: position.dx, top: position.dy, child: child),
],
);
}

2.4.4 Results

As shown in Figure 2–17, the advantage of this solution is that any widget can be integrated with RichText through SpaceSpans, including images, custom tags, and buttons, without affecting the encapsulation of RichText too much.

Figure 2–17

Original Source:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store