How to implement Flutter rich text at low cost, just read this article!

 

Manuscript source: Alibaba Cloud Developer Community

 

background

Xianyu is the first team to use Flutter in China. As an e-commerce App product detail page, it is a very important scene. The main technical ability is text mixing.
https://gw.alicdn.com/tfs/TB1Z5Rxb4D1gK0jSZFKXXcJrVXa-1066-682.jpg

The requirements we face for text classes are complex and changeable. However , in several versions of Flutter history, Text can only display simple style text. It only contains some attributes that control the display of text styles, and RichText realized through TextSpan connection can only Display a variety of text styles (for example: a basic text fragment and a link fragment), these are far from the ability of the design needs. What other platforms can do because of products and design. Why can't Flutter do it? No matter, it must be supported.
http://ww3.sinaimg.cn/bmiddle/a75ef6bely1fim0yiig3hj20gy0ipacb.jpg

Therefore, the need to develop a more capable text mixing component becomes urgent.

The principle of rich text

Before discussing the design and implementation of the text mixed batch component, let's talk about the rich text principle of the system RichText .

  • Creation process
    https://gw.alicdn.com/tfs/TB1dCfIbxn1gK0jSZKPXXXvUXXa-1117-551.png

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

    1. First create an instance of LeafRenderObjectElement .
    2. In the ComponentElement method, the CreateRenderObject method of the RichText instance is called to generate a RenderParagraph instance.
    3. RenderParagraph will create TextPainter responsible for calculating the width and height and drawing the text to the Canvas proxy class, and TextPainter holds the TextSpan text structure.

The RenderParagraph instance will finally register itself to the Dirty Nodes of the rendering module, and the rendering module will traverse the Dirty Nodes and enter the RenderParagraph rendering link.

  • Rendering process

https://gw.alicdn.com/tfs/TB1Dz4ma1bviK0jSZFNXXaApXXa-875-548.png

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

    1. PerfromLayout process by calling TextPaint of Layout , in the course of the by TextSpan tree, followed by AddText add text at all stages, and finally by Paragraph of Layout calculate text height.
    2. In the paint process, clipRect is drawn first , then called by TextPaint 's Paint function, Paragraph 's Paint draws the text, and finally drawRect is drawn .

Design ideas

Through the text drawing principle of RichText , it is not difficult to find that TextSpan records each piece of text information. TextPaint uses the recorded information to call the Native interface to calculate the width and height, and draw the text onto the canvas . The traditional solution achieves complex mixing, and will use HTML to make a WebView rich text. The performance of using WebView is naturally not as good as the native implementation. For performance considerations, we envisage to realize the image and text mixing through the native method. The initial plan was to design several special Spans ( for example: ImageSpan , EmojiSpan, etc. ) , through the information recorded by Span , recalculate the layout according to various types in the Layout of TextPaint , and draw special Widgets in the Paint process . However, This kind of scheme damages the encapsulation of the classes mentioned above, and it needs to be RichText ,RenderParagraph source code Copy came out and revised. The final assumption is that you can first occupy the position with a special text (for example: an empty string), and then move the special Span to the position of this text independently.
https://gw.alicdn.com/tfs/TB1gbnsbQL0gK0jSZFxXXXWHVXa-766-530.png

However, the above scheme will bring two difficulties:

  • Difficulty 1: How to occupy a place in the text first, and be able to set any desired width and height.

By Google found u200B characters represent ZERO WIDTH SPACE (broadband 0 blank), binding TextPainter testing, we found layout out Width is always 0 , fontSize only determines the height, combined with TextStyle inside letterSpacing

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

In this way, we can arbitrarily control the width and height of this special text.

  • Difficulty 2: How to move the special Span to the position.

Through the above test, it is not difficult to find that the special Span is actually independent Widget and RichText does not integrate. So we need to know the relative position of the current widget relative to the RichText space, and combine it with Stack . Combined 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) 

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

Implementation plan

 

The key part of the code is 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);

SpaceSpan relative position acquisition

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

RichtText and SpaceSpan integration

  
Stack
(
        
children
: <Widget>[
        RichText(),
        Positioned(
left
: position.dx, 
top
: position.dy, 
child
: child),
       ],
     );
   }

effect

First look at the effect

https://gw.alicdn.com/tfs/TB1KanxbKH2gK0jSZFEXXcqMpXa-440-94.jpg

The advantage of this solution is that any Widget can be combined through SpaceSpan and RichText , whether it is a picture, a custom label, or even a button, can be integrated, and the encapsulation of RichText itself is less damaged.

future

The above is only the part of rich text display, there are still many limitations, and there are many points that need to be optimized. At present, through the SpaceSpan control, you must specify the width and height. In addition, text selection and custom text background are not supported. The support for the rich text editor allows you to edit the text, let the picture, currency formatting and other controls input.

 

Manuscript source: Alibaba Cloud Developer Community

 

 

Guess you like

Origin blog.csdn.net/weixin_40050195/article/details/98039350
Recommended