skip to Main Content

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.

2

Answers


  1. You can use Text.rich() for inline widget/text case. With rich text we can use inline widget with text without any linebreak.

    I provide simple code to show how Text.rich() works:

    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 [WidgetSpan] with the parameter [text] as [String].
        // 
        // Usage Example: 
        // ```
        // crumb('First Breadcrumb');
        // ```
        WidgetSpan crumb(String text) {
          return WidgetSpan(
            // This alignment handle vertical position of the [WidgedSpan]
            alignment: PlaceholderAlignment.middle,
            // [GestureDetector] can be changed to [TextButton], [ElevatedButton],
            // [OutlinedButton], or anything to handle the link.
            child: GestureDetector(
              // Handle your link function here
              onTap: () {
                // If the crumb clicked, the log will show
                // 'clicked: This is the .... crumb'.
                print('clicked: $text');
              },
              child: Text(text),
            ),
          );
        }
    
        return Scaffold(
          appBar: AppBar(title: const Text('Inline Link Page')),
          body: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Text.rich(
                TextSpan(
                  children: [
                    crumb('This is the 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 text and widget demonstration

    For your information: you can use RichText too for this case.

    If you try my demonstration code, if you click the text, it’ll be show a log ‘clicked: $crumbText’

    Reference:

    Login or Signup to reply.
  2. 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:

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