'How to dynamically create onTap gestures as a user inputs text?

I want to create a custom input field which matches some known strings and then changes the style and adds an onTap gesture when those text values are pressed (ideally using a modifier key to trigger the onTap - I'm using windows for this project - though I think I can figure this out on my own if I can get the rest of the system nailed down) dynamically as the user inputs text.

I've tried to research this, but most of the options I've seen seem to only want to change the style of the text, or don't seem to function as a dynamic text replacement.

One option was to extend TextEditingController and override the buildTextSpan function, but it's not clear to me that this provides a way to create onTap gestures for each individual block (or even blocks) of text.

Unfortunately, I can't provide any examples of what I've done to try this, because I'm not sure there's a predefined system that can accomplish this.

-- EDIT --

A bit more clarity on what I'm looking for.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed doe eiusmod 
tempor incididunt ut labore et dolore magna aliqua. Amet commodo nulla 
facilisi nullam vehicula. [onclick](https://stackoverflow.com/posts/71918728)
faucibus 
interdum posuere lorem. 
[onclick2](https://stackoverflow.com/posts/71918728)
Potenti 
nullam ac tortor vitae purus 
faucibus. Tellus cras adipiscing enim eu 
turpis egestas pretium aenean pharetra. Mauris pellentesque pulvinar 
pellentesque habitant [onclickN](https://stackoverflow.com/posts/71918728)
morbi tristique.
 Odio pellentesque diam volutpat 
commodo sed egestas egestas fringilla. Nec sagittis aliquam malesuada 
bibendum arcu vitae elementum curabitur. Scelerisque purus semper eget duis 
at tellus. Convallis tellus id interdum velit laoreet id donec ultrices. 

The idea is that the user can input text, and some known strings would automatically be tagged and built as an onClick detector with some optional styling. The text and links above show more or less how I think it would look for an end user (roughly, of course) however it should be dynamically created as the user inputs text.



Solution 1:[1]

The most customizable example of how to solve this problem is by extending TextEditingController. Once TextEditingController is extended, overriding buildTextSpan() allows you to selectively style TextSpans. This also allows you to add a recognizer to the TextSpan, which is what we're really after. This article provides a write up on the idea. https://www.flutterclutter.dev/flutter/tutorials/styling-parts-of-a-textfield/2021/101326/.

class CustomTextController extends TextEditingController {
  CustomTextController();

  @override
  TextSpan buildTextSpan(
      {required BuildContext context,
      TextStyle? style,
      required bool withComposing}) {
    final List<InlineSpan> textSpanChildren = [];
    text.splitMapJoin('test', onMatch: (match) {
      final String? matchVal = match.group(0);
      _addTextSpan(
          textSpanChildren,
          matchVal,
          style,
          TapGestureRecognizer()
            ..onTap = () {
              print('tapped');
            });
      return '';
    }, onNonMatch: (String text) {
      _addTextSpan(textSpanChildren, text, style, null);
      return '';
    });

    return TextSpan(
      children: textSpanChildren,
    );
  }

  void _addTextSpan(
    List<InlineSpan> textSpanChildren,
    String? textToBeStyled,
    TextStyle? style,
    GestureRecognizer? gestureRecognizer,
  ) {
    textSpanChildren.add(
      TextSpan(
        text: textToBeStyled,
        style: style,
        recognizer: gestureRecognizer,
      ),
    );
  }
}

When overriding buildTextSpan(), first we set up a list to hold the TextSpans we want the user to actually see.

Next, split the text using splitMapJoin to a pattern - either a regex or a simple string - and on each match to our pattern, we add a TextSpan to the list of children we want to use for our final TextSpan which returns from buildTextSpan().

This is where we can add the inline onTap functionality. When we add a TextSpan to the textSpanChildren list, using _addTextSpan, we can add a recognizer to the TextSpan. For example, we can add a TapGestureRecognizer and can set the onTap callback using TapGestureRecognizer()..onTap = () {}.

The matches don't need to return anything special because we aren't using the results of splitMapJoin, just the iteration over the parts of the string, so returning '' is fine. onNonMatch works similarly, but since we don't have any interest in this value, we can simply say there's no need for a recognizer.

In the code example above, you can create a TextField using the extended controller, and whatever matches the pattern in splitMapJoin will have a tap gesture on it.

Obviously, the example above has only a single, hard-coded pattern to match, but you can refer to the link above if you'd like a more extensible approach that builds a regex pattern depending on an arbitrary amount of input patterns.

Solution 2:[2]

you can try extended_text_field, with this, you can create your on reg text, such as @ or # with tap gesture:

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(
          flag,
          " ",
          textStyle,
        );

  @override
  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);
              }));
  }
}

Solution 3:[3]

here is the custom Form field extends of StatelessWidget... it's one of the reusable components ways to create a dynamic Widget to use anywhere in your App...

if you looking to add more arguments (click on any properties then click Ctrl+Q) to know the DataType of the property so you can define it then pass to the contractor

for Example, onSaved is a void Function() DataType and so on...

`class CustomFormField extends StatelessWidget {
  const CustomFormField(
      { 
      this.onSaved,
      required this.textInputType,
      required this.suffixIcon,
      this.obscureText,
      required this.validator,
        this.controller,Key? key}) : super (key: key);

  final void Function(String?)? onSaved;
  final TextInputType? textInputType;
  final Widget? suffixIcon;
  final bool? obscureText;
  final FormFieldValidator? validator;
  final TextEditingController? controller;


  @override
  Widget build(BuildContext context) {
    return TextFormField(
      validator: validator,
      keyboardType: textInputType,
      controller: controller,
      decoration: InputDecoration(
          suffixIcon: suffixIcon,
          floatingLabelBehavior: FloatingLabelBehavior.always,
          enabledBorder: _outlineInputBorder(),
      onSaved: onSaved,
      obscureText: obscureText ?? false,
    );
  }

  OutlineInputBorder _outlineInputBorder() {
    return OutlineInputBorder(
      borderRadius: BorderRadius.circular(20),
      borderSide: const BorderSide(color: Colors.black),
      gapPadding:  10,
    );
  }
}`

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Cody Moore
Solution 2 Jim
Solution 3 AhmedElsherif