flutterword-wrap

Is there a way to make hot text (links) wrap (within the link when needed)?


I am implementing something like a breadcrumbs trail of hot (tap-able) text separated by separator characters and I want the whole thing to wrap as needed in the middle of the hot text if the need to wrap falls there. Because the crumbs are links, they have to be a collection of widgets, but displaying them in a Wrap object causes them to wrap on a per-widget basis and therefore never in the middle of the crumb links.

screen shot of desired vs. actual behavior

I tried to do this using Row, rows of Rows with the last one expanded, trying properties like softWrap on Text objects, "rich text," and more. I looked around for this use case, and posted to another forum. Seems like this should be possible in a mature app development framework.


Solution

  • After a quick research, it turns out that TextSpan has a property that provides gesture recognizer in recognizer. This recognizer can be filled with many gesture recognizer such as TapGestureRecognizer that has similar ability with onTap in GestureDetector.

    I provide example code of how this works:

    import 'package:flutter/gestures.dart';
    import 'package:flutter/material.dart';
    
    class InlineLinkPage extends StatelessWidget {
      const InlineLinkPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        // Crumb separator.
        const TextSpan separator = TextSpan(text: ' | ');
    
        // Build crumb with [TextSpan] with the parameter [text] as [String].
        //
        // Usage Example:
        // ```
        // crumb('First Breadcrumb');
        // ```
        TextSpan crumb(String text) {
          // A function that triggered when the text is tapped.
          void handleTap() {
            // Write your logical [crumb] behavior when user tap the [crumb] here...
            print('Clicked: $text');
          }
    
          // Initialize [GestureRecognizer] for TextSpan. This is for detect tap
          // from user. 
          // 
          // In this case [recognizer] use [TapGestureRecognizer], The way it work 
          // is the same as [onTap] in [GestureDetector].
          // 
          // There's other option for [recognizer] like [LongPressGestureRecognizer()..onLongPress],
          // for detect long press on the widget.
          final recognizer = TapGestureRecognizer()..onTap = handleTap;
    
          return TextSpan(
            text: text,
            recognizer: recognizer,
          );
        }
    
        return Scaffold(
          appBar: AppBar(title: const Text('Inline Link Page')),
          body: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: RichText(
                text: TextSpan(
                  style: const TextStyle(color: Colors.black87),
                  children: [
                    crumb('This is first crumb'),
                    separator,
                    crumb('This is the second crumb'),
                    separator,
                    crumb('This is the third crumb'),
                    separator,
                    crumb('This is the fourth crumb'),
                    separator,
                    crumb('etc'),
                    separator,
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    

    The Output

    Inline functional text

    Possible issue

    I don't see that TapGestureRecognizer has tapTargetSize to manage the tap area of the link, so the user needs to tap precisely inside TextSpan.

    Recommendation

    Adding line height to TextSpan will minimalize miss click case. Check this question for the insight.

    Reference: