background

Xianyu is the first and largest team to use Flutter in China. As an e-commerce App, the product details page is a very important scene, in which the most important technical ability is text mixing.

Our requirements for Text classes are complex and variable. However, several versions of Flutter history, Text can only display simple Text styles, which contain only some properties that control the display of Text styles, and RichText via TextSpan connections can only display multiple Text styles, which are far from the capabilities needed for design. Why other platforms can do this, why not Flutter, no matter what, must be supported!

Therefore, the need to develop a stronger ability of text mixing components become imminent!

Rich text principles

Before we talk about the design and implementation of the text mixing component, we will talk about the principle of RichText of the system RichText.

The creation process

When a RichText node is created, the following objects are actually created:

  1. Create an instance of LeafRenderObjectElement.

  2. The ComponentElement method calls the CreateRenderObject method of the RichText instance to generate the RenderParagraph instance.

  3. RenderParagraph creates the TextPainter proxy class that calculates the width and height and draws the text to the Canvas, and the TextPainter holds the TextSpan text structure.

The RenderParagraph instance will eventually register itself with the Dirty Nodes of the rendering module, which will traverse the Dirty Nodes to enter the RenderParagraph rendering session.

Rendering process

The RenderParagraph method encapsulates the logic of drawing text to canvas, mainly using a module called TextPainter, whose call process follows RenderObject call.

  1. The PerfromLayout procedure calls the Layout of TextPaint, adds the text of each stage through the TextSpan structure tree, adds the text of each stage through AddText, and calculates the text height through the Layout of Paragraph.

  2. In the Paint process, the clipRect is drawn first, followed by the Paint function call from TextPaint, the text from Paragraph Paint, and finally the drawRect.

Design ideas

According to RichText’s text rendering principle, it is not difficult to find that TextSpan records various pieces of text information, and TextPaint calls the Native interface to calculate the width and height through the recorded information, and draws the text on the canvas. The traditional scheme to achieve complex mixing, through HTML to make a WebView rich text, the use of WebView in the performance of nature is not as good as the native implementation, for performance consideration, we imagine through the native way to achieve text and text mixing. The initial proposal was to design several special spans (e.g. ImageSpan, EmojiSpan, etc.), the Layout of TextPaint is re-calculated according to various types of information recorded by Span, and special widgets are drawn during the Paint process. However, this scheme is particularly damaging to the packaging of the above mentioned classes. You need to Copy the RichText and RenderParagraph source codes and modify them. The final idea is that you can use a special text to preempt space (for example, an empty string), and then move the special Span on top of that text independently.

Two difficulties accompany this scheme:

Difficulty 1: how to occupy the space in the text first, and can make any desired width and height.

Through Google, we found that \u200B character stands for ZERO WIDTH SPACE. Combined with TextPainter test, we found that layout WIDTH is always 0, fontSize only determines the height. Combine letterSpacing in TextStyle


     

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

Copy the code

This allows us to arbitrarily control the width and height of the particular text.

Difficulty 2: how to move a particular Span into position.

As you can see from the above test, the special Span is actually a separate Widget and RichText that don’t mix. So we need to know the current position of the widget relative to the RichText space and merge it with the Stack. Combine that with the getOffsetForCaret method in TextPaint.

                                        
     

    /// Returns the offset at which to paint the caret.

    ///

    /// Valid only after [layout] has been called.

    Offset getOffsetForCaret( TextPosition position, Rect caretPrototype)

Copy the code

The relative position of the current placeholder can be retrieved naturally.

Implementation scheme

Key parts of the code are as follows:

Unified placeholder SpaceSpan


     

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

Copy the code

SpaceSpan relative location gets


     

    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;

    }

Copy the code

RichtText and SpaceSpan merge


     

    Stack(

    children: <Widget>[

    RichText(),

    Positioned(left: position.dx, top: position.dy, child: child),

    ].

    );

    }

Copy the code

The effect

Let’s see what it looks like

The advantage of this approach is that any Widget can be combined with RichText via SpaceSpan, whether it’s images, custom tags, or even buttons, with less damage to RichText itself.

In the future

The above is only the rich text display part, there are still many limitations, there are more points to optimize, currently through SpaceSpan control, must specify the width and height, in addition to the text selection, custom text background these are not supported, secondly support for rich text editor, can make it edit text, Let pictures, currency formatting controls input, etc.

Is now, client/server Java/architecture/front-end/quality engineer, xiaoxiyu all looking forward to your joining, Base Hangzhou Alibaba Xixi Park. Welcome you to join us, do creative space community products, do the depth of the top open source projects, expand the technological boundary together to achieve the ultimate!

* Send resumes to small idle fish → [email protected]


More series of articles, open source projects, key insights, in-depth interpretation

Please look for the idle fish technology