flutter输入框运用的是TextField,能够经过设置它的特点给输入框和内部文字设置不同的款式,之前看了一个日记APP,是这样的:
能够给输入的文字别离设置款式。
于是就想用flutter去完成这样的作用。查了一下资料发现,TextField中有controller特点,需要传入TextEditingController,它能够去监听text的一些变化以及设置TextField中光标的方位等,最重要的是,能够自己写子类承继TextEditingController重写buildTextSpan办法,该办法返回TextSpan组件,flutter中的富文本组件RichText就是运用TextSpan来完成的。TextSpan中有children,咱们只需要在buildTextSpan办法中拼接children,给children中的元素装备不同的TextStyle就能够完成这样的作用。
如这儿写了一个子类重写了buildTextSpan办法,在这个办法中,经过每次text的变化,更新文本中的款式段数组,经过款式段数组组成TextSpan来制作输入框文本。
class RichTextEditingController extends TextEditingController {
TextStyle _curStyle = TextStyle();
void setCurStyle(TextStyle style) {
_curStyle = style;
}
TextSegments configs = TextSegments();
@override
TextSpan buildTextSpan(
{required BuildContext context,
TextStyle? style,
required bool withComposing}) {
assert(!value.composing.isValid ||
!withComposing ||
value.isComposingRangeValid);
//给TextSegments装备数据更新replacements
configs.config(
selectRange: value.selection,
editingRange: value.composing,
text: value.text,
style: _curStyle);
List<InlineSpan> children = [];
//拼接
for (TextSegment element in configs.replacements) {
TextSpan span = TextSpan(text: element.text, style: element.style);
children.add(span);
}
print(
"selectRange: {${value.selection.start},${value.selection.end}}, text: ${text},{${configs.replacements}}");
return TextSpan(children: children);
}
}
TextSegments中的replacements是TextSegment数组,TextSegment中记录了每个文本款式段的文本信息和文本坐标规模:
class TextSegment {
TextRange range;
TextStyle style;
String text;
TextSegment({required this.range, required this.style, required this.text});
//自己在不在挑选的区域中
bool inRange(TextRange selectRange) {
if (text.length <= 1) return false;
return rangeContain(range, selectRange.start) ||
rangeContain(range, selectRange.end);
}
省掉的代码.....
}
TextSegments经过text的改动对内部的款式段数组进行增删改操作:
class TextSegments {
List<TextSegment> replacements = [];
String oldText = "";
//上一次光标选中的区域
TextRange selectRange = TextRange(start: -1, end: -1);
TextRange editingRange = TextRange(start: -1, end: -1);
void config(
{required TextRange selectRange,
required TextRange editingRange,
required String text,
required TextStyle style}) {
if (oldText == text) {
return;
}
//删去
if (text.length < oldText.length) {
delete(selectRange, editingRange, text);
}
//直接append到最后边
else if (selectRange.start > oldText.length || replacements.length == 0) {
append(selectRange, editingRange, text, style);
}
//从中心刺进
else {
insert(selectRange, editingRange, text, style);
}
oldText = text;
}
.....下面省掉代码.....
}
对replacements进行删去和刺进的时分,一定要重新更新刺进和删去之后的元素的方位信息TextRange。如删去的时分:
void _deleteWithStart(int start) {
int idx = -1;
for (var i = 0; i < replacements.length; i++) {
TextSegment element = replacements[i];
if (element.removeWithStart(start)) {
idx = i;
} else if (idx != -1) {
//更新后边元素的方位信息
element.range = TextRange(
start: element.range.start - 1, end: element.range.end - 1);
}
}
if (idx != -1 && replacements[idx].isEmpty()) {
replacements.removeAt(idx);
}
}
刺进的时分:
void insert(TextRange selectRange, TextRange editingRange, String text,
TextStyle style) {
int insertIndex = -1;
....省掉代码,找到需要刺进的方位insertIndex....
//找到需要将增加的文字刺进到replacements的某个元素中
for(){
... insertIndex = ...
}
//找到需要将增加的文字刺进到replacements的某个元素后边
if(insertIndex = -1) {
for(){
... insertIndex = ...
}
}
//都没找见,直接append到最后边
if (insertIndex == -1) {
append(selectRange, editingRange, text, style);
return;
}
//将增加的文字参加进来
TextRange insertR = TextRange(
start: selectRange.start,
end: selectRange.start + (text.length - oldText.length));
TextSegment newT = TextSegment(
range: insertR,
style: style,
text: text.substring(insertR.start, insertR.end));
list.insert(insertIndex + 1, newT);
//刺进的range后边的元素,range后移
for (var i = insertIndex + 2; i < replacements.length; i++) {
TextSegment element = replacements[i];
element.range = TextRange(
start: element.range.start + length, end: element.range.end + length);
}
replacements = list;
_connect(); //将相同style切相邻的元素进行兼并
}
最后简单的完成作用: