How to highlight a word in string in flutter programmatically

Viewed 21536

Is there any way to change color on particular word in a string ?

Text("some long string")

now i want to give color to only long word.

can someone tell me how can i do this programatically ?

eg:-

I am long a really long and long string in some variable, a long one

now here i want to seperate long word. I can seperate simple string to highlight one word but not sure how to find and highlight each of these words.

8 Answers

Wrap the word in a TextSpan and assign style properties to change the text appearance and use RichText instead of Text

RichText(
  text: TextSpan(
    text: 'Hello ',
    style: DefaultTextStyle.of(context).style,
    children: <TextSpan>[
      TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
      TextSpan(text: ' world!'),
    ],
  ),
)

or use the Text.rich constructor https://docs.flutter.io/flutter/widgets/Text-class.html

const Text.rich(
  TextSpan(
    text: 'Hello', // default text style
    children: <TextSpan>[
      TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)),
      TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
    ],
  ),
)

Here is my code.

import 'package:flutter/material.dart';

class HighlightText extends StatelessWidget {
  final String text;
  final String highlight;
  final TextStyle style;
  final TextStyle highlightStyle;
  final Color highlightColor;
  final bool ignoreCase;

  HighlightText({
    Key key,
    this.text,
    this.highlight,
    this.style,
    this.highlightColor,
    TextStyle highlightStyle,
    this.ignoreCase: false,
  })  : assert(
          highlightColor == null || highlightStyle == null,
          'highlightColor and highlightStyle cannot be provided at same time.',
        ),
        highlightStyle = highlightStyle ?? style?.copyWith(color: highlightColor) ?? TextStyle(color: highlightColor),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    final text = this.text ?? '';
    if ((highlight?.isEmpty ?? true) || text.isEmpty) {
      return Text(text, style: style);
    }

    var sourceText = ignoreCase ? text.toLowerCase() : text;
    var targetHighlight = ignoreCase ? highlight.toLowerCase() : highlight;

    List<TextSpan> spans = [];
    int start = 0;
    int indexOfHighlight;
    do {
      indexOfHighlight = sourceText.indexOf(targetHighlight, start);
      if (indexOfHighlight < 0) {
        // no highlight
        spans.add(_normalSpan(text.substring(start)));
        break;
      }
      if (indexOfHighlight > start) {
        // normal text before highlight
        spans.add(_normalSpan(text.substring(start, indexOfHighlight)));
      }
      start = indexOfHighlight + highlight.length;
      spans.add(_highlightSpan(text.substring(indexOfHighlight, start)));
    } while (true);

    return Text.rich(TextSpan(children: spans));
  }

  TextSpan _highlightSpan(String content) {
    return TextSpan(text: content, style: highlightStyle);
  }

  TextSpan _normalSpan(String content) {
    return TextSpan(text: content, style: style);
  }
}


Extending on zhpoo's awesome answer, here's a widget snippet that would allow you to style/highlight anything in a string programmatically using regular expressions (dart's RegExp).

Link to dartpad demo: https://dartpad.dev/d7a0826ed1201f7247fafd9e65979953

class RegexTextHighlight extends StatelessWidget {
  final String text;
  final RegExp highlightRegex;
  final TextStyle highlightStyle;
  final TextStyle nonHighlightStyle;

  const RegexTextHighlight({
    @required this.text,
    @required this.highlightRegex,
    @required this.highlightStyle,
    this.nonHighlightStyle,
  });

  @override
  Widget build(BuildContext context) {
    if (text == null || text.isEmpty) {
      return Text("",
          style: nonHighlightStyle ?? DefaultTextStyle.of(context).style);
    }

    List<TextSpan> spans = [];
    int start = 0;
    while (true) {
      final String highlight =
          highlightRegex.stringMatch(text.substring(start));
      if (highlight == null) {
        // no highlight
        spans.add(_normalSpan(text.substring(start)));
        break;
      }

      final int indexOfHighlight = text.indexOf(highlight, start);

      if (indexOfHighlight == start) {
        // starts with highlight
        spans.add(_highlightSpan(highlight));
        start += highlight.length;
      } else {
        // normal + highlight
        spans.add(_normalSpan(text.substring(start, indexOfHighlight)));
        spans.add(_highlightSpan(highlight));
        start = indexOfHighlight + highlight.length;
      }
    }

    return RichText(
      text: TextSpan(
        style: nonHighlightStyle ?? DefaultTextStyle.of(context).style,
        children: spans,
      ),
    );
  }

  TextSpan _highlightSpan(String content) {
    return TextSpan(text: content, style: highlightStyle);
  }

  TextSpan _normalSpan(String content) {
    return TextSpan(text: content);
  }
}

I recently developed a package called Dynamic Text Highlighting. It let's you to highlight programatically some given words inside a given text.

Take a look at https://pub.dev/packages/dynamic_text_highlighting

Example

Widget buildDTH(String text, List<String> highlights) {
  return DynamicTextHighlighting(
    text: text,
    highlights: highlights,
    color: Colors.yellow,
    style: TextStyle(
      fontSize: 18.0,
      fontStyle: FontStyle.italic,
    ),
    caseSensitive: false,
  );
}

It is a stateless widget, so for any changes just call setState(() {...}).

void applyChanges(List<String> newHighlights) {
  setState(() {
    highlights = newHighlights;
  });
}

To achieve this, there are several possibilities :

1- Using the Text.rich constructor instead of the Text widget and then inside the constructor, you will use the TextSpan widgets that will allow you to add style through the style property. The first TextSpan directly in Text.rich and then the other TextSpan in the first TextSpan through its children property.

Text.rich( 
  TextSpan(
    text: 'Some ', 
    children: <TextSpan>[
      TextSpan(
        text: ' Long ',
        style: TextStyle(fontWeight: FontWeight.bold , background: Paint()..color = Colors.red)),
      TextSpan(
        text: ' world',
        style: TextStyle(backgroundColor: Colors.yellow)),
              ],
            ),
          )

Output :

enter image description here

2- The use of RichText widget . Same as Text.rich but this time the first TextSpan will be put on the text property of the RichText widget.

RichText( 
  text:TextSpan(
     text: 'Some ', 
     style: TextStyle(color: Colors.blue), 
     children: <TextSpan>[
        TextSpan(
           text: ' Long ',
           style: TextStyle(color:Colors.black ,fontWeight: FontWeight.bold , background: Paint()..color = Colors.red)),
        TextSpan(
           text: ' world',
           style: TextStyle(backgroundColor: Colors.yellow)),
              ],
            ),
          )

Output :

enter image description here

3- The use of external packages . I propose you highlight_text or substring_highlight

Use this code it would even highlight the query letters, check once

  List<TextSpan> highlight(
      String main, String query) {
    List<TextSpan> children = [];
    List<String> abc = query.toLowerCase().split("");
    for (int i = 0; i < main.length; i++) {
      if (abc.contains(main[i])) {
        children.add(TextSpan(
            text: main[i],
            style: TextStyle(
                backgroundColor: Colors.yellow[300],
                color: Colors.black,
                decoration: TextDecoration.none,
                fontFamily: fontName,
                fontWeight: FontWeight.bold,
                fontSize: 16)));
      } else {
        children.add(TextSpan(
            text: main[i],
            style: TextStyle(
                color: Colors.black,
                decoration: TextDecoration.none,
                fontFamily: fontName,
                fontWeight: FontWeight.w300,
                fontSize: 16)));
      }
    }
    return children;
  }

that's Good Answer @Gauter Zochbauer. if You want to change dynamically then Follow following answer.

import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(
      title: 'Forms in Flutter',
      home: new LoginPage(),
    ));

class LoginPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {

  Color color =Colors.yellowAccent;

  @override
  Widget build(BuildContext context) {
    final Size screenSize = MediaQuery.of(context).size;

    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Login'),
      ),
      body: new Container(
          padding: new EdgeInsets.all(20.0),
          child: Column(
            children: <Widget>[
              Text.rich(
                TextSpan(
                  text: 'Hello', // default text style
                  children: <TextSpan>[
                    TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic,color: color)),
                    TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)),
                  ],
                ),
              ),
              new RaisedButton(
                onPressed: (){
                  setState(() {
                    color == Colors.yellowAccent ? color = Colors.red : color = Colors.yellowAccent;
                  });
                },
              child: Text("Click Me!!!")
        ),
            ],
          )
    )); 
  }
}
Related