Flutter三方库(FlutterTextSpanField)适配鸿蒙教学 + 隐藏域值获取功能
本文介绍了如何在Flutter应用中实现@功能时获取隐藏的用户ID值。通过自定义AtTextSpan存储用户ID,并利用getWidgets()方法从输入框中筛选出自定义的@块,可准确获取被@用户的ID列表。文章详细说明了如何封装获取方法、组装发送数据,并提供了调试组件示例。该方案不涉及平台代码,在鸿蒙系统上同样适用,需要注意数据获取时机和空值处理等问题。
三方库开源地址:https://atomgit.com/nutpi/flutter_ohos_text_span_field
写在前面
上一篇文章介绍了怎么用 FlutterTextSpanField 实现@用户功能,但光能@还不够,我们还得把@的用户ID拿出来发给后端。不然后端怎么知道要给谁发通知呢?
这就涉及到一个概念——隐藏域。简单说就是用户看到的是昵称,但我们实际存储和传输的是ID。
说实话,这个功能我一开始也没太重视,觉得不就是拿个ID嘛,能有多难?结果真正做的时候发现,要从一堆 TextSpan 里筛选出自定义的@块,还要保证顺序不乱,还挺麻烦的。
好在这个库提供了一个 getWidgets() 方法,用起来还挺方便。今天就来聊聊怎么获取这些隐藏的值。
为什么需要隐藏域
举个例子你就明白了。
假设用户发了一条动态:今天和@张三 @李四 一起吃饭
只存文字的问题
如果我们只存这段文字,后端怎么知道张三是哪个张三?
系统里可能有几百个叫张三的用户:
- 张三(ID: 10001)- 北京的
- 张三(ID: 10002)- 上海的
- 张三(ID: 10003)- 广州的
后端根本没法判断用户@的是哪个。
正确的做法
所以正确的做法是,除了存文字内容,还要存一个@用户的ID列表。
发给后端的数据应该长这样:
{
"content": "今天和@张三 @李四 一起吃饭",
"at_users": ["10001", "10002"]
}
这样后端就能精确地知道@的是谁,也能给对应的用户发通知。
实际应用场景
这个功能在很多场景下都用得到:
场景一:发送通知
用户发了一条@别人的动态,后端需要给被@的人发推送通知。如果没有ID,根本没法发。
场景二:权限控制
有些 App 的@功能有权限限制,比如只能@好友,或者只能@同事。后端需要根据ID来判断是否有权限@这个人。
场景三:数据统计
产品经理可能想知道哪些用户被@的次数最多,这就需要统计ID,而不是昵称。因为昵称可能重复,也可能被修改。
回顾一下 AtTextSpan
在上一篇文章里,我们创建了一个自定义的 AtTextSpan:
class AtTextSpan extends TextSpan {
final String id;
const AtTextSpan({
required this.id,
String? text,
TextStyle? style,
}) : super(text: text, style: style);
}
这个 id 字段就是我们的隐藏域。用户看不到它,但它一直跟着 TextSpan 存在。
获取隐藏域的核心方法
TextSpanBuilder 提供了一个 getWidgets() 方法,可以拿到输入框里所有的自定义组件。
调用 getWidgets 方法
List<TextSpanWidget> widgets = _textSpanBuilder.getWidgets();
返回的是一个 TextSpanWidget 列表。
TextSpanWidget 是什么?
它是一个数据类,包含了我们之前插入的 TextSpan 对象,以及这个 TextSpan 在文本中的位置信息。
简单来说,每个 TextSpanWidget 代表输入框里的一个"块",可能是普通文本,也可能是@块。
筛选出 AtTextSpan
拿到列表之后,我们需要遍历它,把 AtTextSpan 类型的筛选出来:
widgets.forEach((element) {
if (element.span is AtTextSpan) {
AtTextSpan at = element.span as AtTextSpan;
print("昵称: ${at.text}, ID: ${at.id}");
}
});
为什么要做类型判断?
因为列表里可能还有普通的 TextSpan(用户手动输入的文字),我们只需要 AtTextSpan。
用 is 关键字判断类型,然后用 as 转换成 AtTextSpan,就能访问到我们自定义的 id 字段了。
封装成方法
实际项目中,我们一般会封装成一个方法,直接返回ID列表:
List<String> getAtUserIds() {
List<String> ids = [];
_textSpanBuilder.getWidgets().forEach((element) {
if (element.span is AtTextSpan) {
AtTextSpan at = element.span as AtTextSpan;
ids.add(at.id);
}
});
return ids;
}
这样在发送消息的时候,直接调用这个方法就能拿到所有@用户的ID了。
为什么要封装?
一是代码更简洁,二是方便复用。可能有多个地方需要获取@用户ID,封装成方法之后,哪里需要就在哪里调用。
完整的发送流程
来看一个比较完整的发送消息流程:
_sendMessage() {
// 1. 获取文本内容
String content = _textSpanBuilder.controller?.text ?? "";
// 2. 获取@用户ID列表
List<String> atUserIds = getAtUserIds();
// 3. 组装数据
Map<String, dynamic> data = {
"content": content,
"at_users": atUserIds,
};
// 4. 发送给后端
// api.post("/message/send", data);
print("发送的数据: $data");
}
第一步拿文本内容,这个直接从 controller 里取就行。
第二步拿@用户ID列表,用我们刚才封装的方法。
第三步组装成后端需要的格式,一般是一个 Map。
第四步调接口发送,这里就不展开了。
打印出来的数据大概长这样:
发送的数据: {
content: "今天和@张三 @李四 一起吃饭",
at_users: ["10001", "10002"]
}
后端拿到这个数据,就能知道要给ID为10001和10002的用户发@通知了。
获取更多信息
有时候我们不只需要ID,可能还需要知道@用户在文本中的位置。TextSpanWidget 里有一个 range 属性可以用:
widgets.forEach((element) {
if (element.span is AtTextSpan) {
AtTextSpan at = element.span as AtTextSpan;
print("昵称: ${at.text}");
print("ID: ${at.id}");
print("起始位置: ${element.range.start}");
print("结束位置: ${element.range.end}");
}
});
range.start 是这个@块在文本中的起始下标,range.end 是结束下标。
这个信息在某些场景下挺有用的,比如你想高亮显示@的用户,或者做一些文本分析。
一个实用的展示组件
写了一个简单的组件,用来展示获取到的隐藏域值,方便调试:
Widget _buildValueDisplay() {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"获取到的@用户:",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(_valueContent.isEmpty ? "暂无数据" : _valueContent),
],
),
);
}
样式比较简单,就是一个灰色背景的容器,里面显示获取到的内容。
点击按钮的时候更新显示内容:
_getValue() {
List<TextSpanWidget> widgets = _textSpanBuilder.getWidgets();
_valueContent = "";
int count = 0;
widgets.forEach((element) {
if (element.span is AtTextSpan) {
count++;
AtTextSpan at = element.span as AtTextSpan;
_valueContent += "$count. ${at.text} (ID: ${at.id})\n";
}
});
if (count == 0) {
_valueContent = "没有@任何用户";
}
setState(() {});
}
这样就能直观地看到输入框里@了哪些用户,以及他们的ID是什么。
鸿蒙适配说明
隐藏域值获取这块完全是 Dart 层面的逻辑,不涉及任何平台相关的代码,所以在鸿蒙上跑起来没有任何问题。
我在鸿蒙模拟器和真机上都测试过,getWidgets() 方法返回的数据和安卓、iOS上完全一致。
注意事项
1. 时机问题
调用 getWidgets() 的时机要注意。如果用户还在输入(比如正在用拼音输入法打字),这时候拿到的数据可能不准确。建议在用户明确点击"发送"按钮之后再获取。
2. 空值处理
getWidgets() 返回的列表可能为空(用户没有@任何人),也可能包含非 AtTextSpan 类型的元素。所以遍历的时候一定要做类型判断。
3. 顺序问题
返回的列表顺序是按照@块在文本中的位置排列的,先出现的在前面。如果你需要按其他顺序排列,可以自己再排一下序。
写在最后
隐藏域值获取是@功能的重要组成部分,没有它,@功能就只是个"花架子",好看但没用。
FlutterTextSpanField 把这块封装得还不错,一个 getWidgets() 方法就能搞定,省了不少事。
下一篇文章会介绍文本的删除和清空功能,包括那个很有意思的"块删除"效果,感兴趣的话继续关注~
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)