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

创建特殊文本
扩展文本可帮助您快速将文本转换为特殊文本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

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 |
选择

| 参数 | 描述 | 默认值 |
|---|---|---|
| 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();
}
},
);
},
);
自定义背景

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 | 绘制背景回调,您可以自行绘制背景 | - |
自定义溢出

参考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');
},
)
],
),
),
),
)
连接零宽度空格

参考issue 18761
如果[ExtendedText.joinZeroWidthSpace]为true,它将连接'\u{200B}'到文本中,使换行和溢出样式更好。
ExtendedText(
joinZeroWidthSpace: true,
)
或者您可以按照以下方法转换
- 字符串
String input='abc'.joinChar();
- InlineSpan
InlineSpan innerTextSpan;
innerTextSpan = joinChar(
innerTextSpan,
Accumulator(),
zeroWidthSpace,
);
注意以下几点
-
这个词不是一个单词,当您想双击选择一个单词时,它将不起作用。
-
文本已更改,如果[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();
}
}