extended_text 0.7.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 97

extended_text #

pub package GitHub stars GitHub forks GitHub license GitHub issues flutter-candies

Language: English | 中文简体

Extended official text to build special text like inline image or @somebody quickly,it also support custom background,custom over flow and custom selection toolbar and handles.

Table of contents #

Speical Text #

Create Speical Text #

extended text helps to convert your text to speical textSpan quickly.

for example, follwing code show how to create @xxxx speical textSpan.

class AtText extends SpecialText {
  static const String flag = "@";
  final int start;

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

  AtText(TextStyle textStyle, SpecialTextGestureTapCallback onTap,
      {this.showAtBackground: false, this.start})
      : super(
          " ",

  InlineSpan finishText() {
    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 #

create your SpecialTextSpanBuilder

class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder {
  /// whether show background for @somebody
  final bool showAtBackground;
  final BuilderType type;
      {this.showAtBackground: false, this.type: BuilderType.extendedText});

  TextSpan build(String data, {TextStyle textStyle, onTap}) {
    var textSpan = super.build(data, textStyle: textStyle, onTap: onTap);
    return textSpan;

  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,
          type: type);
    } 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), type: type);
    return null;

more detail

Image #

ImageSpan #

show inline image by using 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,
    TextStyle style,
    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,

        imageWidth: size,
        imageHeight: size,
        margin: EdgeInsets.only(left: 2.0, bottom: 0.0, right: 2.0));
imageThe image to display(ImageProvider).-
imageWidthThe width of image(not include margin)required
imageHeightThe height of image(not include margin)required
marginThe margin of image-
actualTextActual text, take care of it when enable selection,something likes "[love]"'\uFFFC'
startStart index of text,take care of it when enable selection.0

Cache Image #

if you want cache the network image, you can use ExtendedNetworkImageProvider and clear them with clearDiskCachedImages

import extended_image_library

  extended_image_library: ^0.1.4
  this.url, {
  this.scale = 1.0,
  this.cache: false,
  this.retries = 3,
  this.timeRetry = const Duration(milliseconds: 100),
  CancellationToken cancelToken,
})  : assert(url != null),
      assert(scale != null),
      cancelToken = cancelToken ?? CancellationToken();
urlThe URL from which the image will be fetched.required
scaleThe scale to place in the [ImageInfo] object of the image.1.0
headersThe HTTP headers that will be used with [HttpClient.get] to fetch image from network.-
cachewhether cache image to localfalse
retriesthe time to retry to request3
timeLimittime limit to request image-
timeRetrythe time duration to retry to requestmilliseconds: 100
cancelTokentoken to cancel network requestCancellationToken()
/// Clear the disk cache directory then return if it succeed.
///  <param name="duration">timespan to compute whether file has expired or not</param>
Future<bool> clearDiskCachedImages({Duration duration}) async

more detail

Selection #

selectionEnabledWhether enable selectionfalse
selectionColorColor of selectionTheme.of(context).textSelectionColor
dragStartBehaviorDragStartBehavior for text selectionDragStartBehavior.start
textSelectionControlsAn interface for building the selection UI, to be provided by the implementor of the toolbar widget or handle widgetextendedMaterialTextSelectionControls/extendedCupertinoTextSelectionControls

TextSelectionControls #

default value of textSelectionControls are MaterialExtendedTextSelectionControls/CupertinoExtendedTextSelectionControls

override buildToolbar or buildHandle to custom your toolbar widget or handle widget

class MyExtendedMaterialTextSelectionControls
    extends MaterialExtendedTextSelectionControls {
  Widget buildToolbar(
    BuildContext context,
    Rect globalEditableRegion,
    double textLineHeight,
    Offset position,
    List<TextSelectionPoint> endpoints,
    TextSelectionDelegate delegate,
  ) {

    // The toolbar should appear below the TextField
    // when there is not enough space above the TextField to show it.
    final TextSelectionPoint startTextSelectionPoint = endpoints[0];
    final TextSelectionPoint endTextSelectionPoint =
        (endpoints.length > 1) ? endpoints[1] : null;
    final double x = (endTextSelectionPoint == null)
        ? startTextSelectionPoint.point.dx
        : (startTextSelectionPoint.point.dx + endTextSelectionPoint.point.dx) /
    final double availableHeight = globalEditableRegion.top -
        MediaQuery.of(context).padding.top -
    final double y = (availableHeight < _kToolbarHeight)
        ? startTextSelectionPoint.point.dy +
            globalEditableRegion.height +
            _kToolbarHeight +
        : startTextSelectionPoint.point.dy - textLineHeight * 2.0;
    final Offset preciseMidpoint = Offset(x, y);

    return ConstrainedBox(
      constraints: BoxConstraints.tight(globalEditableRegion.size),
      child: CustomSingleChildLayout(
        delegate: MaterialExtendedTextSelectionToolbarLayout(
        child: _TextSelectionToolbar(
          handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
          handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null,
          handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
              canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
          handleLike: () {
            //mailto:<email address>?subject=<subject>&body=<body>, e.g.
            //clear selecction
            delegate.textEditingValue = delegate.textEditingValue.copyWith(
                selection: TextSelection.collapsed(
                    offset: delegate.textEditingValue.selection.end));

  Widget buildHandle(
      BuildContext context, TextSelectionHandleType type, double textHeight) {
    final Widget handle = SizedBox(
      width: _kHandleSize,
      height: _kHandleSize,
      child: Image.asset("assets/love.png"),

    // [handle] is a circle, with a rectangle in the top left quadrant of that
    // circle (an onion pointing to 10:30). We rotate [handle] to point
    // straight up or up-right depending on the handle type.
    switch (type) {
      case TextSelectionHandleType.left: // points up-right
        return Transform.rotate(
          angle: math.pi / 4.0,
          child: handle,
      case TextSelectionHandleType.right: // points up-left
        return Transform.rotate(
          angle: -math.pi / 4.0,
          child: handle,
      case TextSelectionHandleType.collapsed: // points up
        return handle;
    assert(type != null);
    return null;

/// Manages a copy/paste text selection toolbar.
class _TextSelectionToolbar extends StatelessWidget {
  const _TextSelectionToolbar({
    Key key,
  }) : super(key: key);

  final VoidCallback handleCut;
  final VoidCallback handleCopy;
  final VoidCallback handlePaste;
  final VoidCallback handleSelectAll;
  final VoidCallback handleLike;

  Widget build(BuildContext context) {
    final List<Widget> items = <Widget>[];
    final MaterialLocalizations localizations =

    if (handleCut != null)
          child: Text(localizations.cutButtonLabel), onPressed: handleCut));
    if (handleCopy != null)
          child: Text(localizations.copyButtonLabel), onPressed: handleCopy));
    if (handlePaste != null)
        child: Text(localizations.pasteButtonLabel),
        onPressed: handlePaste,
    if (handleSelectAll != null)
          child: Text(localizations.selectAllButtonLabel),
          onPressed: handleSelectAll));

    if (handleLike != null)
      items.add(FlatButton(child: Icon(Icons.favorite), onPressed: handleLike));

    // If there is no option available, build an empty widget.
    if (items.isEmpty) {
      return Container(width: 0.0, height: 0.0);

    return Material(
      elevation: 1.0,
      child: Wrap(children: items),
      borderRadius: BorderRadius.all(Radius.circular(10.0)),

Control ToolBar Handle #

contain your page into ExtendedTextSelectionPointerHandler, so you can control toolbar and handle.

Default Behavior #

set your page as child of ExtendedTextSelectionPointerHandler

 return ExtendedTextSelectionPointerHandler(
      //default behavior
       child: result,
  • tap region outside of extended text, hide toolbar and handle
  • scorll, hide toolbar and handle

Custom Behavior #

get selectionStates(ExtendedTextSelectionState) by builder call back, and handle by your self.

 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
          onPointerMove: (value) {
            //clear other selection
            for (var state in states) {

more detail

Custom Background #

refer to issues 24335/24337 about background

          "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))),
backgroundBackground painter-
clipBorderRadiusClip BorderRadius-
paintBackgroundPaint background call back, you can paint background by self-

more detail

Custom Overflow #

refer to issue 26748

      overFlowTextSpan: OverFlowTextSpan(children: <TextSpan>[
              TextSpan(text: '  \u2026  '),
                  text: "more detail",
                  style: TextStyle(
                    color: Colors.blue,
                  recognizer: TapGestureRecognizer()
                    ..onTap = () {

more detail


  • Fix error about TargetPlatform.macOS


  • Fix issue that Index out of range for overflow WidgetSpan


  • Fix issue that TextPainter was not layout


  • extract method for TextSelection


  • codes base on 1.12.13+hotfix.5
  • set limitation of flutter sdk >=1.12.13 <1.12.16


  • Fix kMinInteractiveSize is missing in high version of flutter
  • Fix text overflow about WidgetSpan


  • Improve codes about selection
  • Select all SpecialTextSpan which deleteAll is true when double tap or long tap



  • Fix wrong selection offset
  • Fix wrong text clip due to overflowspan



  • Improve codes base on v1.7.8
  • Support WidgetSpan (ExtendedWidgetSpan)


  • Breaking change: Remove background parameter of [OverFlowTextSpan]


  • Issue: Fix textEditingValue and textSelectionControls are not update when didUpdateWidget


  • Feature: Support text selection
  • Issue: Fix issue about rect of overFlowTextSpan


  • Update extended_text_library


  • Update path_provider 1.1.0


  • Update extended_text_library Remove caretIn parameter(SpecialTextSpan) DeleteAll parameter has the same effect as caretIn parameter(SpecialTextSpan)


  • Fix wrong background rect of OverFlowTextSpan when over flow area has image span


  • Fix wrong background rect of OverFlowTextSpan(issue 6)


  • Disabled informationCollector to keep backwards compatibility for now (ExtendedNetworkImageProvider)


  • Add GestureRecognizer for ImageSpan
  • Add demo to show image in photo view


  • Handle image span load failed


  • Update extended_text_library for cache folder is changed


  • Update extended_text_library for BackgroundTextSpan


  • Override compareTo method in BackgroundTextSpan and OverFlowTextSpan to Fix issue that it was error rendering


  • Import extended_text_library


  • Fix issue that tap exception throw when use OverFlowTextSpan


  • Add clearFailedCache parameter for CachedNetworkImage Add clearLoadFailedImageMemoryCache method Both them are used to clear image load failed memory cache, so that image will be reloaded


  • Update path_provider version from 0.4.1 to 0.5.0+1


  • Change SpecialTextGestureTapCallback input from string to dynamic


  • Change BeforePaintImage function to BeforePaintTextImage Change AfterPaintImage function to AfterPaintTextImage


  • Fix issue that BackgroundTextSpan has error clip.


  • Add TextPainter wholeTextPainter for BackgroundTextSpan's paintBackground call back,so that you can get info for whole text painter.


  • Fix issue that find TextPosition near overflow is not accurate.


  • Use ExtendedTextOverflow to replace TextOverflow(new flutter sdk TextOverflow has new value TextOverflow.visible)


  • Suport inline image, custom background ,custom over flow.


example #

A new Flutter application.

Getting Started #

This project is a starting point for a Flutter application.

A few resources to get you started if this is your first Flutter project:

For help getting started with Flutter, view our online documentation, which offers tutorials, samples, guidance on mobile development, and a full API reference.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:

  extended_text: ^0.7.1

2. Install it

You can install packages from the command line:

with Flutter:

$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:

import 'package:extended_text/extended_text.dart';
Describes how popular the package is relative to other packages. [more]
Code health derived from static analysis. [more]
Reflects how tidy and up-to-date the package is. [more]
Weighted score of the above. [more]
Learn more about scoring.

We analyzed this package on Jan 27, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.4
  • Flutter: 1.12.13+hotfix.5

Health suggestions

Format lib/src/extended_rich_text.dart.

Run flutter format to format lib/src/extended_rich_text.dart.

Format lib/src/extended_text_typedef.dart.

Run flutter format to format lib/src/extended_text_typedef.dart.


Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.3.0 <3.0.0
extended_text_library ^0.5.3 0.5.3
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies