flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能

news2024/12/25 9:32:05

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能

最近有位朋友讨论的时候,提到了输入框的高亮展示。在flutter TextField中需要插入特殊样式的标签,比如:“请 @张三 回答一下”,这一串字符在TextField中输入,当输入@时 弹出好友列表选择,然后将 “@张三”高亮显示在TextField中。

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

效果图如下

在这里插入图片描述

一、TextEditingController中的buildTextSpan

在TextField中,我们找到TextEditingController的buildTextSpan方法。

buildTextSpan功能如下:根据当前编辑值生成[TextSpan],默认情况下,使组成范围内的文本显示为带下划线。继承可以重写此方法以自定义文本的外观。

我这里继承TextEditingController,重写buildTextSpan方法,

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

完整代码如下

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)),
      ],
    );
  }
}

二、设置@高亮

实现将 “@张三”高亮显示在TextField中,需要正则表达是,匹配到@功能正则表达式:String regexStr =
r"@[@]*[@ ]+[^@]* ";

使用RegExp

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

具体代码如下

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;
  }
}

三、创建TextField

创建需要显示的TextField,设置输入框的onTap、onChanged、focusNode、TextEditingController等

代码如下

// 输入框
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
            ),
          ),
        ),
      ),
    );
  }
}

四、TextField赋text演示

最后我们可以在输入框TextField设置文本

TextFieldController textEditingController = TextFieldController();

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

完整代码如下

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),
      ),
    );
  }
}

至此可以看到效果图中@张三 高亮显示了。

五、小结

flutter聊天界面-TextField输入框buildTextSpan实现@功能展示高亮功能。该示例中,光标会有问题,暂时没做修改,后续抽空修改。

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

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1018014.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day23集合02

1.泛型 1.1泛型概述 泛型的介绍 ​ 泛型是JDK5中引入的特性&#xff0c;它提供了编译时类型安全检测机制 泛型的好处 把运行时期的问题提前到了编译期间避免了强制类型转换 泛型的定义格式 <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:…

多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出

多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出 目录 多输入多输出 | MATLAB实现PSO-BP粒子群优化BP神经网络多输入多输出预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 Matlab实现PSO-BP粒子群优化BP神经网络多输入多输出预测 1.data为数据…

小程序引入vant-Weapp保姆级教程及安装过程的问题解决

小知识&#xff0c;大挑战&#xff01;本文正在参与“程序员必备小知识”创作活动。 本文同时参与 「掘力星计划」&#xff0c;赢取创作大礼包&#xff0c;挑战创作激励金 当你想在小程序里引入vant时&#xff0c;第一步&#xff1a;打开官方文档&#xff0c;第二步&#xff…

Linux C/C++实现SSL的应用层VPN (MiniVPN)

SSL协议和VPN&#xff08;虚拟私人网络&#xff09;原理是网络安全领域中的两个重要概念。 SSL协议&#xff0c;全称安全套接层&#xff08;Secure Sockets Layer&#xff09;&#xff0c;是一种广泛应用于互联网的安全协议&#xff0c;主要在两个通信端点之间建立安全连接&am…

深度解剖数据在栈中的应用

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大一&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 望小伙伴们点赞&#x1f44d;收藏✨加关注哟&#x1f495;&#x1…

Vue 2 组件间的通信方式总结

引言 组件间的关系有父子关系、兄弟关系、祖孙关系和远亲关系。 不同的关系间&#xff0c;组件的通信有不同的方式。 一、prop 和 $emit prop向下传递&#xff0c;emit向上传递。 父组件使用 prop 向子组件传递信息。 ParentComponent.vue <template><div><…

红心向阳 百鸟朝凤

背景 最近在玩 folium 模块&#xff0c;基于使用过程中的一些个人体验&#xff0c;对 folium 进行了二次封装&#xff0c;开源在 GpsAndMap.在使用的过程中&#xff0c;发现在地图上打图标是可以进行旋转的。遇到就发现了一些有意思的玩法。 隔海的相望 下面的代码在地图 厦…

GIS跟踪监管系统信息查询

GIS跟踪监管系统信息查询 GIS跟踪监管系统&#xff08;1&#xff09;物资查询与展示。① 几何查询。代码说明&#xff1a;② 物资定位。• 单个物资定位&#xff1a;• 多个物资定位&#xff1a;③ 物资统计。&#xff08;2&#xff09;物资信息更新① 新增物资。 GIS跟踪监管系…

【项目经验】:elementui表格中数字汉字排序问题及字符串方法localeCompare()

一.需求 表格中数字汉字排序&#xff0c;数字按大小排列&#xff0c;汉字按拼音首字母&#xff08;A-Z&#xff09;排序。 二.用到的方法 第一步&#xff1a;把el-table-column上加上sortable"custom" <el-table-column prop"date" label"序号…

第七章 查找 一、查找的基本概念

一、基本概念 查找——在数据集合中寻找满足某种条件的数据元素的过程称为查找。 查找表(查找结构)——用于查找的数据集合称为查找表&#xff0c;它由同一类型的数据元素(或记录)组成。 关键字——数据元素中唯一标识该元素的某个数据项的值&#xff0c;使用基于关键字的查…

2023年的深度学习入门指南(27) - CUDA的汇编语言PTX与SASS

通过前面的学习&#xff0c;我们了解了在深度学习和大模型中&#xff0c;GPU的广泛应用。可以说&#xff0c;不用说没有GPU&#xff0c;就算是没有大显存和足够先进的架构&#xff0c;也没法开发大模型。 有的同学表示GPU很神秘&#xff0c;不知道它是怎么工作的。其实&#x…

Vue2.7 封装 Router@4 的 hook

1、问题 在 Vue2.7 中&#xff0c;尤大大是支持大部分 Vue3 的功能&#xff0c;并且支持使用 CompositionAPI 的写法&#xff0c;也支持 script setup 的便捷语法&#xff0c;但是 Vue2 对应的 Vue-router3 库并没有提供 hook 对应的支持&#xff0c;所以需要我们自行封装 Vue…

代码对比工具,都在这了

Git Diff Git是一个流行的分布式版本控制系统&#xff0c;它内置了代码对比功能。使用git diff命令可以比较两个不同版本的代码文件&#xff0c;也可以使用图形化的Git客户端进行可视化对比。 git diff 命令 | 菜鸟教程www.runoob.com/git/git-diff.html Diff diff是一个Un…

NAND价格第4季度回暖,现在是SSD入手时机吗?

这两天有粉丝后台在咨询购买SSD相关的问题。小编也好奇的搜下当前业内SSD品牌。不搜不知道&#xff0c;一搜吓一跳&#xff0c;将近200多个品牌。 那么&#xff0c;买SSD应该买什么品牌&#xff1f;现在是否可以入手SSD呢&#xff1f; 1.固态硬盘SSD的原理 我们首先了解下固态…

兄弟DCP-7080激光打印机硒鼓清零方法

兄弟DCP-708打印机清零方法?兄弟DCP-7080打印机的硒鼓计数器是用来记录硒鼓使用寿命的&#xff0c;当硒鼓使用寿命达到一定程度时&#xff0c;打印机会提示更换硒鼓。如果用户更换了硒鼓&#xff0c;但打印机仍提示需要更换&#xff0c;这时需要进行清零操作&#xff0c;详细请…

xen-gic初始化流程

xen-gic初始化流程 调试平台使用的是gic-600&#xff0c;建议参考下面的文档来阅读代码&#xff0c;搞清楚相关寄存器的功能。 《corelink_gic600_generic_interrupt_controller_technical_reference_manual_100336_0106_00_en》 《IHI0069H_gic_architecture_specification》…

基于SSM的实验室开放管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

「信号与系统」语音信号的语谱图、尺度变化、带限处理、基音提取

「信号与系统」语音信号的语谱图、尺度变化、带限处理、基音提取 本文将简单介绍几种语音信号的处理方法。 1、语谱图 语谱图是一种描述语音信号频率特征的方法&#xff0c;横轴表示时间&#xff0c;纵轴表示频率&#xff0c;颜色深浅表示能量。基本原理是将语音信号分帧&am…

js中this的原理详解(web前端开发javascript语法基础)

欢迎关注作者微信公众号&#xff1a;愤怒的it男 一、问题的由来 学懂 JavaScript 语言&#xff0c;一个标志就是理解下面两种写法&#xff0c;可能有不一样的结果。 var angry_it_man {name : 欢迎关注微信公众号&#xff1a;angry_it_man,say : function(){console.log(thi…

学习SLAM:SLAM进阶(十)暴力更改ROS中的PCL库

话不多说&#xff0c;上活 1.1 为什么要这么做 项目中有依赖。。。。 1.2 安装VTK7.1.1 PCL1.8.0 略 1.3 移植到ROS 删除ROS依赖的vtk6.2和PCL1.8.0的动态链接库&#xff1a; liugongweiubuntu:~$ sudo mv /usr/lib/x86_64-linux-gnu/libvtk* Desktop/lib/ [sudo] password fo…