Search code examples
flutterdartflutter-layout

How to make this widget in flutter?


Here is the image i am trying to build. Tried Stack with Alignment.topRight but i need to build a TextField after the IconButton that's the bottleneck. Little details: The grey box is the input field, which i will append some static text at start, as you can see in the pic below. Static Text = Happy Birthday, then the button with cross icon and after that a TextField as shown in the design. I tried different approaches but not coming-up with accurate results. enter image description here

  Row(
  children: [
    Container(
      margin: const EdgeInsets.only(top: 5, left: 5),
      padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
      decoration:
          BoxDecoration(color: Constants.colorWhite.withOpacity(0.90)),
      child: Text(Constants.happyBirthday),
    ),
    Container(
        margin: const EdgeInsets.only(top: 5, right: 0),
        padding:
            const EdgeInsets.only(left: 5, top: 10, right: 0, bottom: 10),
        decoration:
            BoxDecoration(color: Constants.colorWhite.withOpacity(0.50)),
        child: Stack(
          children: [
            Text(widget.userName.toString()),
            const Positioned(top: 0, right: 0, child: Icon(Icons.close))
          ],
        )),
    Container(
      margin: const EdgeInsets.only(top: 5, left: 0),
      padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
      decoration:
          BoxDecoration(color: Constants.colorWhite.withOpacity(0.90)),
      child: Text('${Constants.happyBirthday} '
          '${Constants.happyBirthday}'
          '${Constants.happyBirthday}'),
    ),
  ],
)

Solution

  • You can create your own TextEditingController and override buildTextSpan.

    enter image description here

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    
    /// @techbusiness
    ///
    /// NOTE: Needs to be improved. Feel free to play with the styling 
    ///       to get your desired result. This is just to show you the
    ///       possibility of your design.
    class TaggerTextEditingController extends TextEditingController {
      TaggerTextEditingController({
        this.onPressTag,
        this.onDeleteTag,
        this.tagger = '@',
        this.tagStyle,
        this.tagBackgroundColor,
        this.deleteIconSize = 15,
        super.text,
      });
    
      final ValueChanged<String>? onPressTag;
      final ValueChanged<String>? onDeleteTag;
      final String tagger;
      final TextStyle? tagStyle;
      final Color? tagBackgroundColor;
      final double deleteIconSize;
    
      void _onDeleteTag(String tag) {
        text = text.replaceFirst(tag, '');
        onDeleteTag?.call(tag.replaceFirst(tagger, ''));
      }
    
      WidgetSpan _buildTag(String tag, TextStyle? style) => WidgetSpan(
            alignment: PlaceholderAlignment.middle,
            child: Stack(
              clipBehavior: Clip.none,
              children: <Widget>[
                InkWell(
                  onTap: () => onPressTag?.call(tag.replaceFirst(tagger, '')),
                  borderRadius: const BorderRadius.all(Radius.circular(15)),
                  child: Container(
                    padding: const EdgeInsets.all(10),
                    decoration: BoxDecoration(
                      borderRadius: const BorderRadius.all(Radius.circular(15)),
                      color: tagBackgroundColor ?? Colors.grey.shade200,
                    ),
                    child: Text(
                      tag.replaceFirst(tagger, ''),
                      style: style,
                    ),
                  ),
                ),
                Positioned(
                  top: -deleteIconSize / 4,
                  right: -deleteIconSize / 4,
                  child: GestureDetector(
                    onTap: () => _onDeleteTag(tag),
                    child: Icon(
                      CupertinoIcons.xmark_circle_fill,
                      size: deleteIconSize,
                      color: Colors.red,
                    ),
                  ),
                ),
              ],
            ),
          );
    
      @override
      TextSpan buildTextSpan({
        required BuildContext context,
        TextStyle? style,
        required bool withComposing,
      }) {
        final List<TaggerText> texts = TaggerText.getTags(text, tagger);
    
        return TextSpan(
          children: texts
              .map(
                (TaggerText value) => value.isTag
                    ? _buildTag(value.text, tagStyle ?? style)
                    : TextSpan(
                        text: value.text,
                        style: style,
                      ),
              )
              .toList(),
        );
      }
    }
    
    //------------------------------------------------------------------------------
    class TaggerText {
      const TaggerText(this.text, this.isTag);
    
      final String text;
      final bool isTag;
    
      static List<TaggerText> getTags(String text, String tagger) {
        final List<TaggerText> tags = <TaggerText>[];
    
        StringBuffer textPortion = StringBuffer();
        String prevChar = '';
    
        bool isTextPortionATag() => textPortion.toString().startsWith(tagger);
    
        void addTaggerText() => tags.add(
              TaggerText(textPortion.toString(), isTextPortionATag()),
            );
    
        for (final String char in text.characters) {
          if (char == tagger && prevChar == ' ') {
            addTaggerText();
            textPortion.clear();
          } else if (char == ' ' && isTextPortionATag()) {
            addTaggerText();
            textPortion.clear();
          }
    
          textPortion.write(char);
          prevChar = char;
        }
    
        if (textPortion.isNotEmpty) {
          addTaggerText();
        }
    
        return tags;
      }
    }
    

    How to use.

    TextField(
      controller: TaggerTextEditingController(),
      maxLines: 5,
    ),