×
Community Blog Flutter Analysis and Practice: RichText Best Practices

Flutter Analysis and Practice: RichText Best Practices

This article describes how RichText work in the context of Xianyu's application.

With the booming development of mobile smart devices, a mobile multi-terminal development framework has become a general trend. Download the Flutter Analysis and Practice: Evolution and Innovation of Xianyu Technologies eBook for step-by-step analyses based on real-life problems, as well as clear explanations of the important concepts of Flutter.

The detail pages are an essential part of an e-commerce app. On the page, the major technological challenge is the hybrid text layout. Xianyu has complex and varying text layout requirements. However, in several earlier versions of Flutter, there were just simple text styles with a few configurable attributes. Even rich text implemented through TextSpan links can only display text in multiple styles, for example, a basic text segment and a link segment. These could not meet the design requirements. Therefore, development of a hybrid text layout component with more powerful features was urgently needed.

2.4.1 How RichText Works

This article describes how RichText works.

1) Creation

Figure 2-14 shows the objects that are created with RichText.

1
Figure 2-14

  • Create a LeafRenderObjectElement instance.
  • 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.

Finally, the RenderParagraph instance registers itself with Dirty Nodes of the rendering module. The rendering module traverses Dirty Nodes to start RenderParagraph rendering.

2) Rendering

As shown in Figure 2-15, the RenderParagraph method encapsulates the logic of rendering text to the canvas by using the TextPainter module. The calling process of RenderParagraph is the same as RenderObject.

2
Figure 2-15

During the PerfromLayout process, TextPaint Layout is called and AddText is used to add text for each stage using the TextSpan structure tree. Finally, Paragraph Layout is called to calculate the text height.

During the Paint process, clipRect is rendered first. Then, the Paint function of TextPaint is called to render the text through Paint of Paragraph. Finally, the drawRect is rendered.

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.

3
Figure 2-16

This solution has two challenges:

1) How can we fill the desired positions in the text and customize for the width and height we want.

u200B represents a zero width space. According to the test for TextPainter, the width of the layout is always 0, and fontSize only determines the height. Therefore, we use the font size with letterSpacing in TextStyle to control the width and height of the special characters.

/// 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;

2) How can we move special spans to the desired positions

From the above test, it is not hard to discern that special spans are independent of Widget and RichText. Therefore, we need to know the relative positions of the current widgets to the RichText spans and use Stack to integrate the widgets and RichText. We can use the getOffsetForCaret method of TextPaint to obtain the relative position of the current placeholder.

/// 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);
  • Acquisition of SpaceSpan relative positions.
  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;
    }
  • Integration of RichText and SpaceSpans.
     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.

4
Figure 2-17

This article only discussed rich text display, which still has limitations and places that need optimization. For example, the width and height must be specified through SpaceSpans. In addition, neither text selection nor custom text backgrounds are supported. The rich text editor needs to support image and currency formatting during text editing.

0 0 0
Share on

XianYu Tech

56 posts | 4 followers

You may also like

Comments