扩展文本

扩展的官方文本,用于快速构建类似行内图片或@某人的特殊文本,它还支持自定义背景、自定义溢出和自定义选择工具栏及手柄。

特殊文本

extended_text

创建特殊文本

扩展文本可帮助您快速将文本转换为特殊文本Span。

例如,以下代码演示了如何创建@xxxx特殊文本Span。

class AtText extends SpecialText {
  AtText(TextStyle textStyle, SpecialTextGestureTapCallback onTap,
      {this.showAtBackground = false, this.start})
      : super(flag, ' ', textStyle, onTap: onTap);
  static const String flag = '@';
  final int start;

  /// whether show background for @somebody
  final bool showAtBackground;

  @override
  InlineSpan finishText() {
    final TextStyle textStyle =
        this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0);

    final String atText = toString();

    return showAtBackground
        ? BackgroundTextSpan(
            background: Paint()..color = Colors.blue.withOpacity(0.15),
            text: atText,
            actualText: atText,
            start: start,

            ///caret can move into special text
            deleteAll: true,
            style: textStyle,
            recognizer: (TapGestureRecognizer()
              ..onTap = () {
                if (onTap != null) {
                  onTap(atText);
                }
              }))
        : SpecialTextSpan(
            text: atText,
            actualText: atText,
            start: start,
            style: textStyle,
            recognizer: (TapGestureRecognizer()
              ..onTap = () {
                if (onTap != null) {
                  onTap(atText);
                }
              }));
  }
}

SpecialTextSpanBuilder

创建您的SpecialTextSpanBuilder

class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder {
  MySpecialTextSpanBuilder({this.showAtBackground = false});

  /// whether show background for @somebody
  final bool showAtBackground;
  @override
  TextSpan build(String data,
      {TextStyle textStyle, SpecialTextGestureTapCallback onTap}) {
    if (kIsWeb) {
      return TextSpan(text: data, style: textStyle);
    }

    return super.build(data, textStyle: textStyle, onTap: onTap);
  }

  @override
  SpecialText createSpecialText(String flag,
      {TextStyle textStyle, SpecialTextGestureTapCallback onTap, int index}) {
    if (flag == null || flag == '') {
      return null;
    }

    ///index is end index of start flag, so text start index should be index-(flag.length-1)
    if (isStart(flag, AtText.flag)) {
      return AtText(
        textStyle,
        onTap,
        start: index - (AtText.flag.length - 1),
        showAtBackground: showAtBackground,
      );
    } else if (isStart(flag, EmojiText.flag)) {
      return EmojiText(textStyle, start: index - (EmojiText.flag.length - 1));
    } else if (isStart(flag, DollarText.flag)) {
      return DollarText(textStyle, onTap,
          start: index - (DollarText.flag.length - 1));
    }
    return null;
  }
}

Image

custom_image

ImageSpan

使用ImageSpan显示行内图片。

class ImageSpan extends ExtendedWidgetSpan {
  ImageSpan(
    ImageProvider image, {
    Key key,
    @required double imageWidth,
    @required double imageHeight,
    EdgeInsets margin,
    int start = 0,
    ui.PlaceholderAlignment alignment = ui.PlaceholderAlignment.bottom,
    String actualText,
    TextBaseline baseline,
    BoxFit fit= BoxFit.scaleDown,
    ImageLoadingBuilder loadingBuilder,
    ImageFrameBuilder frameBuilder,
    String semanticLabel,
    bool excludeFromSemantics = false,
    Color color,
    BlendMode colorBlendMode,
    AlignmentGeometry imageAlignment = Alignment.center,
    ImageRepeat repeat = ImageRepeat.noRepeat,
    Rect centerSlice,
    bool matchTextDirection = false,
    bool gaplessPlayback = false,
    FilterQuality filterQuality = FilterQuality.low,
    GestureTapCallback onTap,
    HitTestBehavior behavior = HitTestBehavior.deferToChild,
  })
参数 描述 默认值
image 要显示的图片(ImageProvider)。 -
imageWidth 图片的宽度(不包括边距) 必需
imageHeight 图片的高度(不包括边距) 必需
margin 图片的边距 -
actualText 实际文本,在启用选择时请注意,类似于“[爱]” '\uFFFC'
start 文本的起始索引,在启用选择时请注意。 0

选择

selection

参数 描述 默认值
selectionEnabled 是否启用选择
selectionColor 选择的颜色 Theme.of(context).textSelectionColor
dragStartBehavior 文本选择的DragStartBehavior DragStartBehavior.start
textSelectionControls 一个用于构建选择UI的接口,由工具栏控件或手柄控件的实现者提供 extendedMaterialTextSelectionControls/extendedCupertinoTextSelectionControls

TextSelectionControls

textSelectionControls的默认值是MaterialExtendedTextSelectionControls/CupertinoExtendedTextSelectionControls

重写buildToolbar或buildHandle来自定义您的工具栏控件或手柄控件

class MyExtendedMaterialTextSelectionControls
    extends ExtendedMaterialTextSelectionControls {
  MyExtendedMaterialTextSelectionControls();
  @override
  Widget buildToolbar(
    BuildContext context,
    Rect globalEditableRegion,
    double textLineHeight,
    Offset selectionMidpoint,
    List<TextSelectionPoint> endpoints,
    TextSelectionDelegate delegate,
  ) {}

  @override
  Widget buildHandle(
      BuildContext context, TextSelectionHandleType type, double textHeight) {
  }
}

控制工具栏手柄

将您的页面放入ExtendedTextSelectionPointerHandler中,以便您可以控制工具栏和手柄。

默认行为

将您的页面设置为ExtendedTextSelectionPointerHandler的子项

 return ExtendedTextSelectionPointerHandler(
      //default behavior
       child: result,
    );
  • 点击扩展文本外部区域,隐藏工具栏和手柄
  • 滚动,隐藏工具栏和手柄

自定义行为

通过builder回调获取selectionStates(ExtendedTextSelectionState),并自行处理。

 return ExtendedTextSelectionPointerHandler(
      //default behavior
      // child: result,
      //custom your behavior
      builder: (states) {
        return Listener(
          child: result,
          behavior: HitTestBehavior.translucent,
          onPointerDown: (value) {
            for (var state in states) {
              if (!state.containsPosition(value.position)) {
                //clear other selection
                state.clearSelection();
              }
            }
          },
          onPointerMove: (value) {
            //clear other selection
            for (var state in states) {
              state.clearSelection();
            }
          },
        );
      },
    );

自定义背景

background

参考issues 24335/24337关于背景

  BackgroundTextSpan(
      text:
          "This text has nice background with borderradius,no mattter how many line,it likes nice",
      background: Paint()..color = Colors.indigo,
      clipBorderRadius: BorderRadius.all(Radius.circular(3.0))),
参数 描述 默认值
background 背景绘制器 -
clipBorderRadius 裁剪边框半径 -
paintBackground 绘制背景回调,您可以自行绘制背景 -

自定义溢出

overflow

参考issue 26748

参数 描述 默认值
child TextOverflow小部件。 @required
maxHeight [TextOverflowWidget]的最大高度,默认为preferredLineHeight。 preferredLineHeight
align [TextOverflowWidget]的对齐方式,左/右。 right
position TextOverflowWidget应显示的位置。 TextOverflowPosition.end
  ExtendedText(
   overflowWidget: TextOverflowWidget(
     position: TextOverflowPosition.end,
     align: TextOverflowAlign.center,
     // just for debug
     debugOverflowRectColor: Colors.red.withOpacity(0.1),
     child: Container(
       child: Row(
         mainAxisSize: MainAxisSize.min,
         children: <Widget>[
           const Text('\u2026 '),
           InkWell(
             child: const Text(
               'more',
             ),
             onTap: () {
               launch(
                   'https://github.com/fluttercandies/extended_text');
             },
           )
         ],
       ),
     ),
   ),
  )

连接零宽度空格

JoinZeroWidthSpace

参考issue 18761

如果[ExtendedText.joinZeroWidthSpace]为true,它将连接'\u{200B}'到文本中,使换行和溢出样式更好。

  ExtendedText(
      joinZeroWidthSpace: true,
    )

或者您可以按照以下方法转换

  1. 字符串
  String input='abc'.joinChar();
  1. InlineSpan
     InlineSpan innerTextSpan;
     innerTextSpan = joinChar(
        innerTextSpan,
        Accumulator(),
        zeroWidthSpace,
    );

注意以下几点

  1. 这个词不是一个单词,当您想双击选择一个单词时,它将不起作用。

  2. 文本已更改,如果[ExtendedText.selectionEnabled]为true,您应该重写TextSelectionControls并删除zeroWidthSpace。


class MyTextSelectionControls extends TextSelectionControls {

  @override
  void handleCopy(TextSelectionDelegate delegate,
      ClipboardStatusNotifier? clipboardStatus) {
    final TextEditingValue value = delegate.textEditingValue;

    String data = value.selection.textInside(value.text);
    // remove zeroWidthSpace
    data = data.replaceAll(zeroWidthSpace, '');

    Clipboard.setData(ClipboardData(
      text: value.selection.textInside(value.text),
    ));
    clipboardStatus?.update();
    delegate.textEditingValue = TextEditingValue(
      text: value.text,
      selection: TextSelection.collapsed(offset: value.selection.end),
    );
    delegate.bringIntoView(delegate.textEditingValue.selection.extent);
    delegate.hideToolbar();
  }
}

GitHub

https://github.com/fluttercandies/extended_text