Flutter三方库(FlutterTextSpanField)适配鸿蒙教学 + 文本删除与清空功能
本文介绍了FlutterTextSpanField库中的删除与清空功能实现。重点解析了块删除机制:当删除操作涉及@块内容时,会自动删除整个@块而非部分文字,确保用户体验一致性。文章详细说明了两种删除方式(用户手动删除和代码控制删除)以及清空功能的使用方法,并提供了实现演示页面的代码示例。同时分享了块删除的内部实现原理、光标位置限制等细节处理,以及开发过程中遇到的常见问题和解决方案。该功能在鸿蒙系统
三方库开源地址:https://atomgit.com/nutpi/flutter_ohos_text_span_field
写在前面
前两篇文章分别介绍了@用户功能和隐藏域值获取,今天来聊聊删除和清空功能。
说实话,删除这个操作看起来简单,但在@功能的场景下还挺有讲究的。普通文本一个字一个字删没问题,但@块呢?删一半算什么?所以就有了"块删除"这个概念。
什么是块删除
先解释一下什么是块删除。
假设输入框里的内容是:今天@张三 天气不错
如果用户把光标放在"张"和"三"之间,按删除键,会发生什么?
普通的 TextField 会删掉"张"这一个字,变成 今天@三 天气不错。但这显然不是我们想要的效果,@三 是什么鬼?
FlutterTextSpanField 的处理方式是:只要删除操作涉及到@块的任何一部分,就把整个@块都删掉。所以结果会变成 今天 天气不错。
这个行为和微信、QQ的@功能是一样的,用户体验上比较符合直觉。
两种删除方式
这个库提供了两种删除方式:
第一种是用户手动删除。 就是用户在输入框里按删除键,这个库会自动处理块删除的逻辑,不需要我们写代码。
第二种是代码控制删除。 通过调用 delete(start, end) 方法,可以删除指定范围的内容。
先看代码控制删除怎么用:
_textSpanBuilder.delete(0, 5);
这行代码会删除下标0到5之间的内容。注意是左闭右开区间,也就是删除下标0、1、2、3、4这5个字符。
如果删除范围涉及到@块,同样会触发块删除:
// 假设内容是:今天@张三 天气不错
// @张三 的范围是 2-5
_textSpanBuilder.delete(3, 4); // 只删除"张"
// 实际效果:整个@张三都被删除
// 结果:今天 天气不错
即使你只指定删除"张"这一个字,库也会自动把整个@块删掉。这个逻辑是在库内部处理的。
清空功能
清空就简单多了,一行代码搞定:
_textSpanBuilder.clear();
调用之后,输入框里的所有内容都会被清空,包括普通文本和@块。
有一点要注意,clear() 方法不只是清空文本,还会清空内部维护的@块列表。所以清空之后再调用 getWidgets() 会返回空列表。
_textSpanBuilder.clear();
List<TextSpanWidget> widgets = _textSpanBuilder.getWidgets();
print(widgets.length); // 输出: 0
实现一个删除功能的演示页面
来写一个完整的演示页面,方便理解这些功能:
class _DeleteDemoState extends State<DeleteDemo> {
TextSpanBuilder _textSpanBuilder = TextSpanBuilder();
int _startIndex = 0;
int _endIndex = 1;
}
先定义好需要的变量。_startIndex 和 _endIndex 用来控制删除范围。
添加一些测试数据的方法:
_addTestData() {
_textSpanBuilder.appendTextToEnd("今天天气真好,");
_textSpanBuilder.appendToEnd(AtTextSpan(
id: "10001",
text: "@张三",
style: TextStyle(color: Color(0xFF5BA2FF)),
));
_textSpanBuilder.appendTextToEnd(" 一起出去玩吧!");
}
这里用了两个方法:appendTextToEnd 添加普通文本,appendToEnd 添加@块。
执行完之后,输入框里的内容是:今天天气真好,@张三 一起出去玩吧!
删除按钮的点击事件:
_onDeletePressed() {
_textSpanBuilder.delete(_startIndex, _endIndex);
}
很简单,就是调用 delete 方法,传入起始和结束下标。
清空按钮的点击事件:
_onClearPressed() {
_textSpanBuilder.clear();
}
更简单,直接调用 clear 就行。
获取当前文本长度
在做删除操作之前,最好先知道当前文本有多长,避免下标越界:
_getTextLength() {
int length = _textSpanBuilder.controller?.text.length ?? 0;
print("当前文本长度: $length");
}
通过 controller.text.length 可以拿到文本长度。
有了长度信息,就可以做一些边界检查:
_onDeletePressed() {
int maxLength = _textSpanBuilder.controller?.text.length ?? 0;
if (_startIndex >= maxLength || _endIndex > maxLength) {
print("下标越界了!");
return;
}
if (_startIndex >= _endIndex) {
print("起始下标必须小于结束下标!");
return;
}
_textSpanBuilder.delete(_startIndex, _endIndex);
}
加上这些检查,可以避免一些奇怪的问题。
块删除的内部原理
如果你对原理感兴趣,可以看看库是怎么实现块删除的。
核心逻辑在 _deleteLimit 方法里。大概流程是这样的:
- 遍历所有的@块
- 检查删除范围是否和@块有交集
- 如果有交集,把删除范围扩展到整个@块
- 执行删除操作
伪代码大概是这样:
void _deleteLimit(...) {
for (var item in _customWidgets) {
// 检查删除范围是否涉及到这个@块
bool shouldDeleteBlock = false;
for (var i = deleteRange.start; i <= deleteRange.end; i++) {
if (i > item.range.start && i < item.range.end) {
shouldDeleteBlock = true;
break;
}
}
// 如果涉及到,扩展删除范围
if (shouldDeleteBlock) {
deleteRange = TextRange(
start: min(deleteRange.start, item.range.start),
end: max(deleteRange.end, item.range.end),
);
}
}
// 执行删除
// ...
}
理解了这个原理,就能明白为什么删除@块中间的一个字,会导致整个@块被删除。
光标位置限制
除了块删除,这个库还有一个细节处理:光标位置限制。
用户没办法把光标定位到@块的中间。如果用户点击@张三的"张"和"三"之间,光标会自动跳到@块的前面或后面。
这个行为也是自动的,不需要我们写代码。
判断逻辑大概是这样:如果点击位置在@块范围内,就计算一下离前面近还是离后面近,然后把光标移到近的那一端。
int _calculationCursorPosition(TextRange range, int position, int max) {
if (position >= range.start && position <= range.end) {
int median = range.start + (range.end - range.start) ~/ 2;
if (position < median) {
return range.start; // 离前面近,跳到前面
}
return range.end; // 离后面近,跳到后面
}
return position;
}
这个细节处理得挺好的,用户体验上很自然。
鸿蒙适配说明
删除和清空功能都是纯 Dart 实现的,在鸿蒙上没有任何兼容性问题。
我测试的时候特意试了一下鸿蒙输入法的删除键,块删除效果和安卓、iOS上完全一致。
踩过的坑
坑1:删除后光标位置
调用 delete 方法之后,光标会自动移到删除位置的起点。如果你想让光标在其他位置,需要手动设置:
_textSpanBuilder.delete(5, 10);
// 手动设置光标位置
_textSpanBuilder.controller?.selection = TextSelection.collapsed(offset: 5);
坑2:连续删除
如果你需要连续删除多个范围,要注意下标会变化。比如删除了0-5之后,原来下标为10的字符现在变成下标5了。
建议从后往前删,这样前面的下标不会受影响:
// 要删除 0-5 和 10-15
// 先删后面的
_textSpanBuilder.delete(10, 15);
// 再删前面的
_textSpanBuilder.delete(0, 5);
坑3:清空后立即添加
clear() 之后如果立即调用 appendToEnd,有时候会有一点延迟问题。建议加一个小延迟:
_textSpanBuilder.clear();
Future.delayed(Duration(milliseconds: 50), () {
_textSpanBuilder.appendTextToEnd("新内容");
});
写在最后
删除功能看起来简单,但块删除这个细节处理得好不好,直接影响用户体验。
FlutterTextSpanField 在这方面做得还不错,块删除、光标限制这些都帮我们处理好了,省了不少事。
这个系列的三篇文章到这里就结束了,分别介绍了@用户功能、隐藏域值获取、删除与清空功能。希望对你有帮助,有问题欢迎留言讨论~
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)