This is the 24th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Xiao CAI is learningFlutterIn the process, there is a special requirement that a fixed number of lines should be displayed for the content with too long text, and the user is prompted to click expand and fold in the lower right corner of the text. Side dishes try a custom collapsible shrinkACEFoldTextView;

ACEFoldTextView

First of all, I briefly combed the design process, as shown in the picture below.

  • If the number of lines occupied by the text content is less than or equal to the maximum number of lines, the entire text content is displayed by default, and no expansion/collapse is displayed.
  • When the number of lines occupied by the text content is greater than the maximum number of lines, the maximum number of lines is displayed by default, and the “expand” prompt is displayed in the lower right corner.
  • Click the “Expand” area, when the content width of the last line of text content and the “expand” area is less than the maximum width, the default display of “fold up”;
  • Click the “Expand” area, when the last line of text content and the “expand” area occupy the content width is only greater than or equal to the maximum width, the “fold up” area line break display;

1. Transparent Gradient

As a whole, the clickable [Expand/fold] text area is displayed in the lower right corner through Stack hierarchy nesting. In order to improve the display effect and prevent completely blocking the content text, the small dish tries two ways to achieve color transparency gradient.

1.1 ShaderMask shader

ShaderMask shaders can be used to color child widgets, including mask layer effects. A LinearGradient is set, but the ShaderMask is applied to the entire sub-widget mask layer, which may affect the Text display effect and requires the Stack layer to use.

_transparentWid02() => ShaderMask( shaderCallback: (bounds) => LinearGradient( colors: [_bgcolor.withopacity (0.0), _bgColor],).createshader (bounds), child: Container(alignment: Alignment.centerRight, color: Colors.white, width: _kMoreWidth, child: Text((_temLines > _maxLines) ? TextStyle(color: Theme. Of (context). AccentColor, fontSize: Widget.textstyle? .fontSize ?? 14.0))));Copy the code

1.2 the Container BoxDecoration

The second is commonly used Container with BoxDecoration to set linear gradient; This method is more convenient to use;

_transparentWid01() => Container( alignment: Alignment.centerRight, decoration: BoxDecoration( gradient: LinearGradient(colors: [_bgColor. WithOpacity (0.0), _bgColor], end: FractionalOffset(0.5, 0.5))), width: _kMoreWidth, child: Text((_temLines > _maxLines) ? TextStyle(color: Theme. Of (context). AccentColor, fontSize: Widget.textstyle? .fontSize ?? 14.0)));Copy the code

2. Text Collapses the Text content

If you want to achieve Text folding, first of all, you need to know the number of lines occupied by the Text Text in the range in advance, which is usually obtained by such means as TextPainter. Xiao CAI tried two ways to judge;

2.1 TextPainter.didExceedMaxLines

TextPainter and TextSpan are mainly used for text drawing. When maxLines is set, you can use didExceedMaxLines to determine whether the text content has exceeded the line. I’m going to take a closer look at TextPainter later on;

_checkOverMaxLines01(maxLines, maxWidth) {
  final textSpan = TextSpan(text: _textStr, style: widget.textStyle);
  final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr, maxLines: maxLines);
  textPainter.layout(maxWidth: widget.maxWidth ?? MediaQuery.of(context).size.width);
  return textPainter.didExceedMaxLines;
}
Copy the code

2.2 LineMetrics

DidExceedMaxLines can directly obtain whether the text content exceeds the line, but cannot obtain the text information of each line, etc. Therefore, Xiao CAI tried computeLineMetrics() to obtain LineMetrics baseline metrics. You can get the width of each line;

Of course, LineMetrics can’t capture every line of text, and there are some caveats when two text alignment methods are used together. More on that later.

Tips: When computeLineMetrics() is used to obtain LineMetrics information, it is necessary to pay attention to the TextPainter must set the textDirection text alignment, and can be obtained after the Layout.

_checkOverMaxLines02(maxWidth) {
  final textSpan = TextSpan(text: _textStr, style: widget.textStyle);
  final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr);
  textPainter.layout(maxWidth: widget.maxWidth ?? MediaQuery.of(context).size.width);
  _lines = textPainter.computeLineMetrics();
  return _lines;
}
Copy the code

3. ACEFoldTextView

With the foundation of the previous two steps, Xiao CAI combines them to generate a custom ACEFoldTextView. Constraint subtext delay loading by LinearBuilder; Use LineMetrics to get the length of the last line of text, calculate the sum with the default [expand] Widget, and determine whether the width exceeds the maximum limit. When the maximum width is exceeded, the side dish adds a \n to the text to force a newline;

return LayoutBuilder(builder: (context, size) {
  _isOverFlow = _checkOverMaxLines01(_maxLines, widget.maxWidth);
  _temLines = _checkOverMaxLines02(widget.maxWidth)?.length;
  return (_temLines <= _maxLines)
      ? _itemText() : Stack(children: <Widget>[_itemText(), _moreText()]);
});

_moreText() => Positioned(
      bottom: 0, right: 0,
      child: GestureDetector(
          child: _transparentWid02(),
          onTap: () => setState(() {
                if (_temLines > _maxLines) {
                  if (_lines.last.width + _kMoreWidth >= widget.maxWidth) {
                    _maxLines = _temLines + 1;
                    _textStr = '${widget.text}\n';
                  } else {
                    _maxLines = _temLines;
                  }
                } else if (_temLines == _maxLines) {
                  _maxLines = widget.maxLines;
                }
              })));
Copy the code


ACEFoldTextView case source code


The side dish drew the ACEFoldTextView to this position, which involves the simple content of TextPainter. After the side dish, I will further study it. If there are mistakes, please give more guidance!

Source: Little Monk A Ce