文章目录
- TextEditingValue
- 一、fromJSON
- 二、text、selection、composing、empty
- 三、isComposingRangeValid
- 四、replaced
TextEditingValue
/// The current text, selection, and composing state for editing a run of text.
class TextEditingValue {
const TextEditingValue({
this.text = '',
this.selection = const TextSelection.collapsed(offset: -1),
this.composing = TextRange.empty,
});
创建用于编辑文本段落的信息。
选择范围和正在编辑的范围必须位于文本之内。这一点在构建时不会被检查,
必须由调用者确保。
[selection] 的默认值是 TextSelection.collapsed(offset: -1)。
这表示没有任何选择。
一、fromJSON
class TextEditingValue {
// ... 省略大量代码
factory TextEditingValue.fromJSON(Map<String, dynamic> encoded) {
final String text = encoded['text'] as String;
final TextSelection selection = TextSelection(
baseOffset: encoded['selectionBase'] as int? ?? -1,
extentOffset: encoded['selectionExtent'] as int? ?? -1,
affinity: _toTextAffinity(encoded['selectionAffinity'] as String?) ?? TextAffinity.downstream,
isDirectional: encoded['selectionIsDirectional'] as bool? ?? false,
);
final TextRange composing = TextRange(
start: encoded['composingBase'] as int? ?? -1,
end: encoded['composingExtent'] as int? ?? -1,
);
assert(_textRangeIsValid(selection, text));
assert(_textRangeIsValid(composing, text));
return TextEditingValue(
text: text,
selection: selection,
composing: composing,
);
}
从 JSON 对象创建此类的实例。
final String text = encoded[‘text’] as String; : JSON 中提取文本内容
TextSelection
:从 JSON 中提取选择范围信息并创建 TextSelection 对象
baseOffset: encoded['selectionBase'] as int? ?? -1,
// 选择起始位置,默认为 -1
extentOffset: encoded['selectionExtent'] as int? ?? -1,
// 选择结束位置,默认为 -1
affinity: _toTextAffinity(encoded['selectionAffinity'] as String?) ?? TextAffinity.downstream,
// 选择的光标方向,默认为下游
isDirectional: encoded['selectionIsDirectional'] as bool? ?? false,
// 选择是否为方向性选择,默认为 false
TextRange
: 从 JSON 中提取正在编辑的范围信息并创建 TextRange 对象
final TextRange composing = TextRange(
start: encoded['composingBase'] as int? ?? -1,
// 编辑范围起始位置,默认为 -1
end: encoded['composingExtent'] as int? ?? -1,
// 编辑范围结束位置,默认为 -1
);
断言检查选择范围和编辑范围是否在文本范围内,如果_textRangeIsValid(selection, text)为true,assert(true)就抛出错误,不再往下执行。
assert(_textRangeIsValid(selection, text));
assert(_textRangeIsValid(composing, text));
如果检查没问题,就返回赋值后的TextEditingValue。
return TextEditingValue(text: text, selection: selection, composing: composing);
二、text、selection、composing、empty
class TextEditingValue {
// ... 省略大量代码
/// text 是当前正在编辑的文本。
final String text;
/// selection是当前选中的文本范围。
/// 当 [selection] 是一个 [TextSelection],并且其 baseOffset 和 extentOffset 相同且为非负数时,
/// [selection] 属性表示光标的位置。
/// 如果当前 [selection] 的 baseOffset 或 extentOffset 为负数,
/// 则表示当前文本没有选中内容或光标位置,大多数依赖于当前选择的文本编辑操作
/// (例如,在光标位置插入字符)将不会执行任何操作。
final TextSelection selection;
/// 正在被组合输入的文本范围。
/// 组合输入区域由输入法(IME)创建,用于指示某个范围内的文本是临时的。
/// 例如,Android Gboard 应用的英文键盘会将光标下的当前单词放入组合输入区域,
/// 以指示该单词可能会受到自动更正或预测更改的影响。
/// 组合输入区域还可用于执行多阶段输入,这通常由为拼音键盘设计的输入法使用,
/// 用于输入表意符号。例如,许多中日韩(CJK)键盘要求用户输入拉丁字母序列,
/// 然后将其转换为 CJK 字符。在 iOS 上,默认的软件键盘没有专门的视图来显示未完成的拉丁字母序列,
/// 因此它直接显示在文本字段中,位于组合输入区域内。
/// 组合输入区域通常只应由输入法或用户通过与输入法的交互来更改。
/// 如果此属性表示的文本范围是 [TextRange.empty],则表示当前没有文本正在被组合输入。
final TextRange composing;
/// 一个表示空字符串、没有选择且没有组合输入范围的值。
static const TextEditingValue empty = TextEditingValue();
}
text
: 当前编辑的文字
selection
: 当前选中文字的范围
composing
:正在被组合输入的文本范围。例如拼音输入法,在输入字母出现候选字还未确认选择的阶段,composing就为true,当确认选中后,composing就为false。下图就是composing: true的示例。
empty
: 一个表示空字符串、没有选择且没有组合输入范围的值
使用:_textEditController.value = TextEditingVlaue.empty;
给一个输入赋值一个空值。
class TextEditingValue {
// ... 省略大量代码
TextEditingValue copyWith({
String? text,
TextSelection? selection,
TextRange? composing,
}) {
return TextEditingValue(
text: text ?? this.text,
selection: selection ?? this.selection,
composing: composing ?? this.composing,
);
}
}
创建一个当前 TextEditingValue 的副本,并允许部分字段被替换为新值。
在需要修改 TextEditingValue
的部分属性
时,避免直接修改原对象,而是返回一个新的对象。这是不可变对象设计的常见模式。
参数:text、selection 和 composing 都是可选参数。如果调用者提供了新值,则使用新值;否则,保留当前对象的值。
空值合并运算符 (??
):用于判断参数是否为 null。如果为 null,则使用当前对象的对应值。
返回新对象:通过 TextEditingValue 构造函数创建一个新对象,确保原对象不会被修改。
text: text ?? this.text,
如果提供了新的文本,则使用新文本,否则保留原文本
selection: selection ?? this.selection,
如果提供了新的选择范围,则使用新范围,否则保留原范围
composing: composing ?? this.composing,
如果提供了新的组合输入范围,则使用新范围,否则保留原范围
三、isComposingRangeValid
class TextEditingValue {
// ... 省略大量代码
bool get isComposingRangeValid => composing.isValid && composing.isNormalized && composing.end <= text.length;
}
isComposingRangeValid
:通过get
关键字,给 isComposingRangeValid 提供了一个只读
的访问方式,使外部能直接读取isComposingRangeValid 值,而无法直接修改它。
用于判断 composing 范围是否text的有效范围。
当且仅当 composing 范围是规范化的、其起始位置大于或等于 0,并且其结束位置小于或等于 [text] 的长度
时,返回 true
。
如果此属性为 false,而 [composing] 范围的 isValid 为 true,通常表示当前 [composing] 范围由于编程错误而无效。
composing.isValid
:检查 composing 范围是否有效(即 start 和 end 都是非负数)。
composing.isNormalized
:检查 composing 范围是否规范化(即 start <= end)。
composing.end <= text.length
:检查 composing 的结束位置是否不超过文本的长度。
返回值:只有当所有条件都满足时,返回 true,否则返回 false。
composing 是 TextRange
,在TextRange中有以下定义:
class TextRange {
// ... 省略大量代码
/// 范围的起始位置
/// 如果 [start] 和 [end] 都为 -1,则表示一个空的文本范围
final int start;
/// 范围结束位置的下一个索引
/// 如果 [start] 和 [end] 都为 -1,则表示一个空的文本范围
final int end;
/// 判断该范围是否表示文本中的有效位置
bool get isValid => start >= 0 && end >= 0;
/// 判断该范围是否为空(但仍可能位于文本中),未选中一段范围的文本。
bool get isCollapsed => start == end;
/// 判断该范围的起始位置是否在结束位置之前。
bool get isNormalized => end >= start;
}
isCollapsed: true时,光标未选中范围,例如在“本”
后“。”
前的光标,如下图:
四、replaced
class TextEditingValue {
// ... 省略大量代码
TextEditingValue replaced(TextRange replacementRange, String replacementString) {
if (!replacementRange.isValid) {
return this;
}
final String newText = text.replaceRange(replacementRange.start, replacementRange.end, replacementString);
if (replacementRange.end - replacementRange.start == replacementString.length) {
return copyWith(text: newText);
}
int adjustIndex(int originalIndex) {
// The length added by adding the replacementString.
final int replacedLength = originalIndex <= replacementRange.start && originalIndex < replacementRange.end ? 0 : replacementString.length;
// The length removed by removing the replacementRange.
final int removedLength = originalIndex.clamp(replacementRange.start, replacementRange.end) - replacementRange.start;
return originalIndex + replacedLength - removedLength;
}
final TextSelection adjustedSelection = TextSelection(
baseOffset: adjustIndex(selection.baseOffset),
extentOffset: adjustIndex(selection.extentOffset),
);
final TextRange adjustedComposing = TextRange(
start: adjustIndex(composing.start),
end: adjustIndex(composing.end),
);
assert(_textRangeIsValid(adjustedSelection, newText));
assert(_textRangeIsValid(adjustedComposing, newText));
return TextEditingValue(
text: newText,
selection: adjustedSelection,
composing: adjustedComposing,
);
}
}
-
if (!replacementRange.isValid) { return this; }
:
检查 replacementRange 是否有效:
如果 replacementRange 无效(如 start 或 end 为负数),则直接返回当前对象,不做任何操作。 -
final String newText = text.replaceRange(replacementRange.start, replacementRange.end, replacementString);
:
替换文本:
使用 text.replaceRange 方法将 replacementRange 指定的文本替换为 replacementString,生成新的文本 newText。 -
处理特殊情况:
if (replacementRange.end - replacementRange.start == replacementString.length) { return copyWith(text: newText); }
如果替换前后的文本长度相同(即 replacementRange.end - replacementRange.start == replacementString.length),则直接调用 copyWith 方法返回新的 TextEditingValue,因为选择范围和组合输入范围不需要调整。 -
int adjustIndex(int originalIndex) {}
:
调整索引:
定义 adjustIndex 方法,用于计算替换后 selection 和 composing 范围的新位置。
final int replacedLength = originalIndex <= replacementRange.start && originalIndex < replacementRange.end ? 0 : replacementString.length;
replacedLength:如果原始索引在替换范围内,则不增加长度;否则,增加 replacementString 的长度。
final int removedLength = originalIndex.clamp(replacementRange.start, replacementRange.end) - replacementRange.start;
:
removedLength:计算被替换文本的长度。
调整公式:originalIndex + replacedLength - removedLength。当前索引 + 替换文本的长度 - 被替换删除的文本的长度 = 最终光标的索引下标
clamp
是 Dart 中用于限制一个数值在指定范围内的工具方法。它的定义如下:
int clamp(int lowerLimit, int upperLimit)
;
功能:将当前值限制在 lowerLimit 和 upperLimit 之间。
如果当前值小于 lowerLimit
,则返回 lowerLimit
。
如果当前值大于 upperLimit
,则返回 upperLimit
。
当前值处于lowerLimit~upperLimit之间
,返回当前值
。
上述的originalIndex.clamp
(replacementRange.start, replacementRange.end),表示返回替换文案在长度范围内的合法索引。
-
final TextSelection adjustedSelection = TextSelection( baseOffset: adjustIndex(selection.baseOffset), extentOffset: adjustIndex(selection.extentOffset), );
通过adjustIndex
计算 selection 和 composing 的最终索引范围,并通过TextSelection重新赋值。 -
断言检查:_textRangeIsValid(adjustedSelection, newText) 是否为true
。
使用 assert 检查调整后的 selection 和 composing 范围是否有效。 -
返回新对象
:
返回一个新的 TextEditingValue,包含替换后的文本、调整后的选择范围和组合输入范围。