0

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}'),
    ),
  ],
)
12
  • Are you refering to the text ? Commented Oct 29, 2022 at 10:06
  • @YeasinSheikh The grey box is the input field, which i will append some static text at start, as you can see in the pic above. Static Text = Happy Birthday, then the button with cross icon and after that a TextField as shown in the design. Commented Oct 29, 2022 at 10:11
  • Grey box is a button (with cross icon), not input text field Commented Oct 29, 2022 at 10:13
  • 1
    How about RichText with WidgetSpan with fixed width TextFiled Commented Oct 29, 2022 at 10:32
  • 1
    Change your design: for example add text field to some dialog and when done with editing render it as a normal text Commented Oct 29, 2022 at 10:33

1 Answer 1

1

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,
),
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you rick for this awesome answer. But i need to add a missing functionality to the tagged text area. Exp: I have another username textField above the Screenshot area and whenever a user enter a username that name should be reflected in the Tagged area. I will update my question with the new screenshot so you have an idea.
@techbusiness You can play around the values of TaggerTextEditingController::text. This property can be set in your name TextField::onChanged.
How to get text from TaggerTextEditingController after writing something in that field?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.