skip to Main Content

I’d like to create a text field in Flutter where, when I select a portion of the text and press a button, only the selected part changes its color, for example, to red. Moreover, if another section of the text is selected and its color changes, the previous text’s style should be preserved.

My goal is to modify the colors of different portions of text within a text field in Flutter. What steps should I follow to achieve this?

2

Answers


  1. Chosen as BEST ANSWER

    How can I create this with the pub.dev/packages/extended_text_field ?


  2. I believe the best way to go about doing this is by using SelectableText.rich(). This doesn’t let you edit the text the way a TextField would; luckily you can make it editable using the extended_text_field package.

    Here’s a working Flutter app that implements the highlighting capability you’ve described:

    import 'dart:math';
    import 'package:flutter/material.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      @override
      Widget build(BuildContext context) => const MaterialApp(home: MyHomePage());
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      static const _text = 'Lorem ipsum dolor sit amet, '
          'consectetur adipiscing elit, '
          'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. '
          'Ut enim ad minim veniam, '
          'quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. '
          'Duis aute irure dolor in reprehenderit '
          'in voluptate velit esse cillum dolore eu fugiat nulla pariatur. '
          'Excepteur sint occaecat cupidatat non proident, '
          'sunt in culpa qui officia deserunt mollit anim id est laborum.';
      final List<TextStyle> _style = List.filled(_text.length, const TextStyle());
      (int startIndex, int endIndex) _selection = (0, 0);
    
      Widget get highlightedText {
        final List<TextSpan> children = [];
        String currentText = '';
        late TextStyle currentStyle;
    
        for (int i = 0; i < _text.length; i++) {
          if (currentText.isEmpty) {
            currentText = _text[i];
            currentStyle = _style[i];
          } else {
            currentText += _text[i];
            if (i + 1 == _text.length || _style[i + 1] != currentStyle) {
              children.add(TextSpan(text: currentText, style: currentStyle));
              currentText = '';
            }
          }
        }
    
        return SelectableText.rich(
          TextSpan(children: children),
          onSelectionChanged: (selection, cause) {
            final (base, extent) = (selection.baseOffset, selection.extentOffset);
            _selection = (min(base, extent), max(base, extent));
          },
        );
      }
    
      void hilight(Color color) {
        final (start, end) = _selection;
        if (start == end) return;
    
        final style = TextStyle(
            color: color.computeLuminance() > 0.25 ? Colors.black : Colors.white,
            backgroundColor: color);
        for (int i = start; i < end; i++) {
          setState(() => _style[i] = style);
        }
      }
    
      late final Widget highlightButtons = Padding(
        padding: const EdgeInsets.only(top: 50, bottom: 25),
        child: Row(children: [
          for (final color in [
            Colors.red,
            Colors.orange,
            Colors.amber,
            Colors.yellow,
            Colors.lime,
            Colors.green,
            Colors.cyan,
            Colors.blue,
            Colors.indigo,
            Colors.purple,
          ])
            _HighlightButton(color: color, onPressed: () => hilight(color))
        ]),
      );
    
      late final Widget resetButton = OutlinedButton(
        style: OutlinedButton.styleFrom(foregroundColor: Colors.grey),
        onPressed: () {
          _selection = (0, _text.length);
          hilight(const Color(0x00FFFFFF));
        },
        child: const Text('reset'),
      );
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Container(
            margin: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.width / 16),
            alignment: Alignment.center,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                highlightedText,
                highlightButtons,
                resetButton,
              ],
            ),
          ),
        );
      }
    }
    
    class _HighlightButton extends StatelessWidget {
      const _HighlightButton({required this.color, required this.onPressed});
      final Color color;
      final void Function() onPressed;
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: Padding(
            padding: const EdgeInsets.all(4),
            child: ElevatedButton(
              style: OutlinedButton.styleFrom(backgroundColor: color),
              onPressed: onPressed,
              child: const SizedBox.shrink(),
            ),
          ),
        );
      }
    }
    

    Here it is in action:

    screen recording

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search