1. 场景阐明
最近遇到一个小问题,这儿把问题模型简化,记载一下处理方式,也算是一个小留念。先说一下场景,如下所示:
已知字符串 src
匹配段列表:matches
这样,在 Flutter 中能够经过对 matches
的遍历,形成富文本段,进行展现,效果如下:
TextSpan formSpan() {
List<List<int>> matches = [
[1, 2], [5, 8], [14, 15]
];
String src = "toly 1994,hello!";
List<InlineSpan> span = [];
int cursor = 0;
for (int i = 0; i < matches.length; i++) {
List<int> match = matches[i];
// 非匹配段
String noMatchStr = src.substring(cursor, match[0]);
span.add(TextSpan(text:noMatchStr , style: style0));
// 匹配段
String matchStr = src.substring(match[0], match[1]+1);
span.add(TextSpan(text: matchStr, style: style1,));
cursor = match[1]+1;
}
if (cursor != src.length - 1) {
span.add(TextSpan(text: src.substring(cursor), style: style0));
}
return TextSpan(children: span);
}
2. 要处理的需求
现在有个需求,给定槽点列表 slots
,在 坚持原有匹配效果 的前提下,在每个槽点对应的索引处,刺进该槽点的索引值,如下所示:
如下,是刺进后的效果,其间本来的高亮款式坚持不变,且在指定方位处额定刺进了文字。这时候 有用怪 难抑心中疑问,发出灵魂呼喊:这有什么用呢?
如下所示,如果在定点刺进的东西不是文字,而是其他组件,比方 FlutterLogo
。就完成了在不影响原有高亮匹配情况下,在指定槽位刺进其他组件的能力:
说一个最直接的使用场景,如下代码高亮行号的刺进,便是使用这种手段。不影响原有富文本,在定点刺进指定组件。
代码高亮 + 行号 | 代码高亮 + 行号 |
---|---|
3. 完成思路
这个问题的实质是根据 slots
点,对已字符段进行切割。就像一个拼接手术:首要找到方位,然后剪开,把刺进段放在两片之间,再黏在一同:
因为槽点能够在恣意方位,所以关于每段来说,操作都是一致的。这样关于每段字符,能够封装一个通用办法来处理。如下,界说 insertSlotWithBoundary
办法,传入每段的起止索引。第一步,应该校验当时段中是否存在槽点。如下左图所示,该段无槽点,就不需求进行什么处理:
这儿界说 slotCursor
记载槽点数组的游标,它会跟着每次槽点被处理,而自加。所以某段在处理时,经过 slots[slotCursor]
能够得到当时待入槽点方位。如下所示,当 slotCursor
长度大于大于总槽位时,阐明已经刺进结束,不需求关注槽点了;或者 待入槽点方位
要比 end
还大,阐明当时段没有槽点:
int slotCursor = 0;
insertSlotWithBoundary(int start, int end, TextStyle style) {
if (slotCursor >= slots.length || slots[slotCursor] > end) {
// 阐明当时段没有槽点,无需处理
span.add(TextSpan(
text: src.substring(start, end),
style: style,
));
return;
}
// TODO 槽点处理
}
在某段中,或许存在 n
个槽点,把段切割为 n+1
段。结合 slotCursor
游标和 end
值,能够经过 while
循环进行遍历处理:
在进入循环时,将 slotCursor++
,需求留意截取的结尾需求额定处理一下。若干槽位已经结束,或下一槽位大于 end
,阐明 下一槽点不再当时段
。 将截取的结尾设为 end
:
insertSlotWithBoundary(int start, int end, TextStyle style) {
// 同上,略...
// 有槽点,切割插槽
String matchStr = src.substring(start, slots[slotCursor]);
span.add(TextSpan(text: matchStr, style: style));
while (slots[slotCursor] < end) {
int slotPosition = slots[slotCursor];
slotCursor++;
int currentEndPosition = 0;
if (slotCursor == slots.length || slots[slotCursor] > end) {
// 阐明插槽结束
// 阐明下一槽点不再当时段
currentEndPosition = end;
} else {
currentEndPosition = slots[slotCursor];
}
// 刺进槽点组件:
span.add(const WidgetSpan(child: FlutterLogo()));
String matchStr = src.substring(slotPosition, currentEndPosition);
span.add(TextSpan(
text: matchStr,
style: style,
));
if (slotCursor >= slots.length) break;
}
}
到这儿,处理就完成了,尽管代码量比较少,可是其间需求考虑的点挺多的。包括校验条件、循环流程、游标处理等。在完成期间也走了不少弯路,试错花了不少时间,在调试中逐步处理问题。本以为我完成不了代码高亮的行号显示的,但在耐心和剖析中还是写出来了,过程可谓是痛快的。
现在总算能够在 Flutter 中代码展现或者文本展现时加上行号了,仅以此文留念这份自主处理问题的的愉悦感。下面是完整的 formSpan
办法,感兴趣的能够自己试一下:
TextSpan formSpan() {
List<List<int>> matches = [[1, 2], [5, 8], [14, 15]];
List<int> slots = [0, 2, 6, 8, 11, 13];
String src = "toly 1994,hello!";
List<InlineSpan> span = [];
int cursor = 0;
int slotCursor = 0;
insertSlotWithBoundary(int start, int end, TextStyle style) {
if (slotCursor>=slots.length||slots[slotCursor] > end) {
// 阐明当时段没有槽点,无需处理
span.add(TextSpan(
text: src.substring(start, end),
style: style,
));
return;
}
// 有槽点,切割插槽
String matchStr = src.substring(start, slots[slotCursor]);
span.add(TextSpan(text: matchStr, style: style));
while (slots[slotCursor] < end) {
int slotPosition = slots[slotCursor];
slotCursor++;
int currentEndPosition = 0;
if (slotCursor == slots.length || slots[slotCursor] > end) {
// 阐明插槽结束
// 阐明下一槽点不再当时段
currentEndPosition = end;
} else {
currentEndPosition = slots[slotCursor];
}
span.add(const WidgetSpan(child: FlutterLogo()));
String matchStr2 = src.substring(slotPosition, currentEndPosition);
span.add(TextSpan(
text: matchStr2,
style: style,
));
if (slotCursor >= slots.length) break;
}
}
for (int i = 0; i < matches.length; i++) {
List<int> match = matches[i];
insertSlotWithBoundary(cursor, match[0], style0);
insertSlotWithBoundary(match[0], match[1] + 1, style1);
cursor = match[1] + 1;
}
if (cursor != src.length - 1) {
insertSlotWithBoundary(cursor,src.length, style0);
}
return TextSpan(children: span);
}