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?

My code is this but it can’t be editable :

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(),
        ),
      ),
    );
  }
}

enter image description here

2

Answers


  1. This is one interesting case study, and I actually have never done this in Flutter, but I have a few tips on how this can be created.

    First of all, remember that you can always break a problem into smaller ones.
    The second tip is there is always more than one solution to a problem.

    Option 1 – HTML / Markdown

    Create your own widget, to display an HTML or Markdown formated String, because any of them support CSS styles and HTML tags.

    Then create your own method that grabs the selected color, locates the position of the selected String, and places the HTML tags between the beginning and the end

    Example:

    my_string = "This is an original string";
    selected_text = "original";
    selected_color = Colors.blue;  
    //you'll need to convert the color to Hexadecimal
    ...
    /* 1- locate the selected string in the text
       and find the start and end location
    
       2- replace the selected text with 
          <span style:"backgroundcolor:$selected_color">$selected_text</span>
    
       3- return the Styled string 
    */
    ...
    // output should be 
     """This is an <span style:"backgroundcolor:$selected_color">$selected_text</span> string."""
    

    Option 2 – using TextStyle

    Create a Widget that outputs multiple Text elements with different styles, depending on the background of each element.
    If you have text selected, and click on the color, your widget should replace the whole selected section with a Text element with that background color.

    Bonus tip

    There is some programming code highlighting tools for Flutter, look those up and check how they are doing the color styling.

    You can also look up these references.
    code_highlight – pub.dev package for code highlighting
    How to Make a Multicolored TextField in Flutter – article
    Flutter Gallery – demo with code viewer

    Login or Signup to reply.
  2. You might want to use a WYSIWYG editor such as flutter_quill or html_editor_enhanced. These packages have options that allow you to disable certain functionalities to limit your editor, such as removing options that is not related to text highlighting.

    About customizing available formatting options on flutter_quill:

    The QuillToolbar class lets you customize which formatting options are available. Sample Page provides sample code for advanced usage and configuration.

    About modifying enabled toolbar buttons in html_editor_enhanced:

    This API allows you to customize the toolbar in a nice, readable format.
    By default, the toolbar will have all buttons enabled except the "other file"
    button, because the plugin cannot handle those files out of the box.

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