Flutter chat interface-TextField input box buildTextSpan implements @ function display highlighting function

Flutter chat interface-TextField input box buildTextSpan implements @ function display highlighting function

When a friend was discussing recently, he mentioned the highlighting of input boxes. A special style tag needs to be inserted in the flutter TextField, such as: "Please @张三answer". This string of characters is entered in the TextField. When @ is entered, a friend list selection pops up, and then "@张三" is highlighted. in TextField.

https://blog.csdn.net/gloryFlow/article/details/132889374

The renderings are as follows

Insert image description here

1. buildTextSpan in TextEditingController

In TextField, we find the buildTextSpan method of TextEditingController.

The function of buildTextSpan is as follows: generate [TextSpan] based on the current editing value, and by default, make the text in the composed range appear underlined. Inheritance can override this method to customize the appearance of text.

I inherit TextEditingController here and override the buildTextSpan method.

List<InlineSpan> textSpans = RichTextHelper.getRichText(value.text);
    if (composingRegionOutOfRange) {
      return TextSpan(style: style, children: textSpans);
    }

The complete code is as follows

import 'package:flutter/material.dart';
import 'package:flutter_lab/rich_text_helper.dart';

class TextFieldController extends TextEditingController {
  /// Builds [TextSpan] from current editing value.
  ///
  /// By default makes text in composing range appear as underlined. Descendants
  /// can override this method to customize appearance of text.
  @override
  TextSpan buildTextSpan(
      {required BuildContext context,
      TextStyle? style,
      required bool withComposing}) {
    assert(!value.composing.isValid ||
        !withComposing ||
        value.isComposingRangeValid);
    // If the composing range is out of range for the current text, ignore it to
    // preserve the tree integrity, otherwise in release mode a RangeError will
    // be thrown and this EditableText will be built with a broken subtree.
    final bool composingRegionOutOfRange =
        !value.isComposingRangeValid || !withComposing;

    print(
        "--- composingRegionOutOfRange:${composingRegionOutOfRange},withComposing:${withComposing},value.isComposingRangeValid:${value.isComposingRangeValid}");
    List<InlineSpan> textSpans = RichTextHelper.getRichText(value.text);
    if (composingRegionOutOfRange) {
      return TextSpan(style: style, children: textSpans);
    }

    print("+++ composingRegionOutOfRange:${composingRegionOutOfRange}");
    final TextStyle composingStyle =
        style?.merge(const TextStyle(decoration: TextDecoration.underline)) ??
            const TextStyle(decoration: TextDecoration.underline);
    return TextSpan(
      style: style,
      children: <TextSpan>[
        TextSpan(text: value.composing.textBefore(value.text)),
        TextSpan(
          style: composingStyle,
          text: value.composing.textInside(value.text),
        ),
        TextSpan(text: value.composing.textAfter(value.text)),
      ],
    );
  }
}

2. Set @Highlight

To highlight "@张三" in the TextField, a regular expression is required to match the @ function regular expression: String regexStr =
r"@[ @]*[ @ ]+[^@]* ";

Using RegExp

String regexStr =
        r"@[^@]*[^@ ]+[^@]* ";
    RegExp exp = RegExp('$regexStr');

The specific code is as follows

import 'package:flutter/material.dart';

class RichTextHelper {
  //图文混排
  static getRichText(String text) {
    List<InlineSpan> textSpans = [];

    String regexStr =
        r"@[^@]*[^@ ]+[^@]* ";
    RegExp exp = RegExp('$regexStr');

    //正则表达式是否在字符串[input]中有匹配。
    if (exp.hasMatch(text)) {
      Iterable<RegExpMatch> matches = exp.allMatches(text);

      int index = 0;
      int count = 0;
      for (var matche in matches) {
        count++;
        String c = text.substring(matche.start, matche.end);
        //匹配到的东西,如表情在首位
        if (index == matche.start) {
          index = matche.end;
        }
        //匹配到的东西,如表情不在首位
        else if (index < matche.start) {
          String leftStr = text.substring(index, matche.start);
          index = matche.end;
          textSpans.add(
            TextSpan(
              text: spaceWord(leftStr),
              style: getDefaultTextStyle(),
            ),
          );
        }

        //匹配到的网址
        if (RegExp(regexStr).hasMatch(c)) {
          textSpans.add(
            TextSpan(
              text: spaceWord(c),
              style:
              TextStyle(color: Colors.blueAccent, fontSize: 16),
            ),
          );
        }

        //是否是最后一个表情,并且后面是否有字符串
        if (matches.length == count && text.length > index) {
          String rightStr = text.substring(index, text.length);
          textSpans.add(
            TextSpan(
              text: spaceWord(rightStr),
              style: getDefaultTextStyle(),
            ),
          );
        }
      }
    } else {
      textSpans.add(
        TextSpan(
          text: spaceWord(text),
          style: getDefaultTextStyle(),
        ),
      );
    }

    return textSpans;
  }

  static TextStyle getDefaultTextStyle() {
    return TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.w400,
      fontStyle: FontStyle.normal,
      color: Colors.black87,
      decoration: TextDecoration.none,
    );
  }

  static String spaceWord(String text) {
    if (text.isEmpty) return text;
    String spaceWord = '';
    for (var element in text.runes) {
      spaceWord += String.fromCharCode(element);
      spaceWord += '\u200B';
    }
    return spaceWord;
  }
}

3. Create TextField

Create the TextField that needs to be displayed, and set the onTap, onChanged, focusNode, TextEditingController, etc. of the input box

code show as below

// 输入框
class InputTextField extends StatefulWidget {
  const InputTextField({
    Key? key,
    this.inputOnTap,
    this.inputOnChanged,
    this.inputOnSubmitted,
    this.inputOnEditingCompleted,
    this.autofocus = false,
    required this.textEditingController,
  }) : super(key: key);

  final inputOnTap;
  final inputOnChanged;
  final inputOnSubmitted;
  final inputOnEditingCompleted;
  final bool autofocus;
  final TextEditingController textEditingController;

  @override
  State<InputTextField> createState() => _InputTextFieldState();
}

class _InputTextFieldState extends State<InputTextField> {
  FocusNode editFocusNode = FocusNode();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  //获取焦点
  void getFocusFunction(BuildContext context) {
    FocusScope.of(context).requestFocus(editFocusNode);
  }

  //失去焦点
  void unFocusFunction() {
    editFocusNode.unfocus();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    editFocusNode.unfocus();
    editFocusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(
        left: 10.0,
        top: 5.0,
        bottom: 5.0,
      ),
      constraints: BoxConstraints(
        minHeight: 40.0,
        maxHeight: 120.0,
      ),
      child: TextField(
        onTap: () {
          widget.inputOnTap();
        },
        onChanged: (string) {
          widget.inputOnChanged(string);
        },
        onEditingComplete: () {
          widget.inputOnEditingCompleted();
        },
        onSubmitted: (string) {
          widget.inputOnSubmitted(string);
        },
        minLines: 1,
        maxLines: null,
        keyboardType: TextInputType.multiline,
        textAlignVertical: TextAlignVertical.center,
        autofocus: widget.autofocus,
        focusNode: editFocusNode,
        controller: widget.textEditingController,
        textInputAction: TextInputAction.send,
        decoration: InputDecoration(
          contentPadding: EdgeInsets.symmetric(vertical: 10, horizontal: 8.0),
          filled: true,
          isCollapsed: true,
          floatingLabelBehavior: FloatingLabelBehavior.never,
          hintText: "说点什么吧~",
          hintStyle: TextStyle(
            fontSize: 14,
            fontWeight: FontWeight.w400,
            fontStyle: FontStyle.normal,
            color: ColorUtil.hexColor(0xACACAC),
            decoration: TextDecoration.none,
          ),
          enabledBorder: OutlineInputBorder(
            /*边角*/
            borderRadius: const BorderRadius.all(
              Radius.circular(5.0), //边角为30
            ),
            borderSide: BorderSide(
              color: ColorUtil.hexColor(0xf7f7f7), //边框颜色为绿色
              width: 1, //边线宽度为1
            ),
          ),
          focusedBorder: OutlineInputBorder(
            borderRadius: const BorderRadius.all(
              Radius.circular(5.0), //边角为30
            ),
            borderSide: BorderSide(
              color: ColorUtil.hexColor(0xECECEC), //边框颜色为绿色
              width: 1, //宽度为1
            ),
          ),
        ),
      ),
    );
  }
}

4. TextField assignment text demonstration

Finally we can set the text in the input box TextField

TextFieldController textEditingController = TextFieldController();

textEditingController.text = "你好@张三 欢迎,哈哈,haha";

The complete code is as follows

class TextFieldRich extends StatefulWidget {
  const TextFieldRich({super.key});

  @override
  State<TextFieldRich> createState() => _TextFieldRichState();
}

class _TextFieldRichState extends State<TextFieldRich> {
  TextFieldController textEditingController = TextFieldController();

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    textEditingController.text = "你好@张三 欢迎,哈哈,haha";
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Size scrSize = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text('TextField测试页面'),
      ),
      body: Container(
        width: scrSize.width,
        height: scrSize.height,
        color: Colors.greenAccent,
        alignment: Alignment.center,
        padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 20.0),
        child: InputTextField(textEditingController: textEditingController),
      ),
    );
  }
}

At this point you can see that @张三 is highlighted in the rendering.

5. Summary

Flutter chat interface-TextField input box buildTextSpan implements @ function display highlighting function. In this example, there is a problem with the cursor, which has not been modified for the time being. I will make time to modify it later.

https://blog.csdn.net/gloryFlow/article/details/132889374

Study and record, keep improving every day.

Guess you like

Origin blog.csdn.net/gloryFlow/article/details/132889374