创建CR XXXX格式VisualTransformation遇偏移映射异常求助
问题描述
我需要实现一个支持长度参数的VisualTransformation,输入格式为CR XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX。当前代码在手动输入时表现正常,但编辑开头数字时,光标偏移映射出现错误。
现有代码
class InputAccountMaskTransformation(private val len: Int = 20) : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { val transformed = buildAnnotatedString { append("CR ") append( insertStringFromEnd( text.text, 4, " ", stringMaxLength = len, fillEmptyWith = "-", ) ) } return TransformedText( text = transformed, offsetMapping = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { val transformedSplit = transformed.split(" ") var increment = 2 + (transformedSplit.count() - 1) if (text.text.isNotBlank()) { transformedSplit.forEach { if (it.contains("----")) increment-- } } return offset.plus(increment) } override fun transformedToOriginal(offset: Int): Int { var decrement = 2 if (text.text.isNotBlank()) { for (c in transformed) { if (c == '-' || c == ' ') { decrement++ } } } val transform = offset.minus(decrement) val offsetValue = if (transform > 0 && transform < transformed.count()) { transform } else { if (transform > (transformed.count() / 2)) { transformed.count() } else { 0 } } return offsetValue } } ) } }
现象说明
- 手动输入时表现正常:

- 编辑开头数字时光标偏移错误:

解决方案
问题出在OffsetMapping的实现上,当前逻辑采用固定增量/减量计算偏移,没有考虑光标在不同位置时的动态偏移。正确的做法是逐字符映射,分别处理原始文本到转换后文本、转换后文本到原始文本的位置对应关系。
修复后的代码如下:
class InputAccountMaskTransformation(private val len: Int = 20) : VisualTransformation { // 固定前缀 private val prefix = "CR " // 每段字符长度 private val segmentLength = 4 // 段分隔符 private val separator = " " override fun filter(text: AnnotatedString): TransformedText { val cleanedText = text.text.take(len) val transformed = buildAnnotatedString { append(prefix) // 构建分段格式的输入内容 for (i in cleanedText.indices) { if (i != 0 && i % segmentLength == 0) { append(separator) } append(cleanedText[i]) } // 填充剩余的占位符 val remaining = len - cleanedText.length if (remaining > 0) { val fullSegments = remaining / segmentLength val partialSegment = remaining % segmentLength for (i in 0 until fullSegments) { if (cleanedText.isNotEmpty() || i != 0) { append(separator) } append("----") } if (partialSegment > 0) { if (cleanedText.isNotEmpty() || fullSegments > 0) { append(separator) } append("-".repeat(partialSegment)) } } } return TransformedText(transformed, object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { if (offset == 0) return prefix.length // 计算原始位置对应的转换后位置:前缀长度 + 字符数 + 已添加的分隔符数量 val separatorsCount = offset / segmentLength return prefix.length + offset + separatorsCount } override fun transformedToOriginal(offset: Int): Int { if (offset <= prefix.length) return 0 // 去除前缀长度的影响 val adjustedOffset = offset - prefix.length // 计算该位置前的分隔符数量 val separatorsCount = adjustedOffset / (segmentLength + 1) // 还原原始位置,限制在合法范围内 val originalOffset = adjustedOffset - separatorsCount return originalOffset.coerceIn(0, cleanedText.length) } }) } }
修复说明
原始到转换的映射(originalToTransformed):
- 光标在原始文本的第
offset位时,转换后的位置需加上前缀长度,再叠加该位置前的分隔符数量(每4个字符添加一个空格),确保光标对应到转换后文本的正确位置。
- 光标在原始文本的第
转换到原始的映射(transformedToOriginal):
- 先减去前缀长度得到转换后文本的有效内容位置,再减去该位置前的分隔符数量,还原出原始文本的对应位置。
- 通过
coerceIn方法限制位置范围,避免光标越界。
修改后,无论在文本开头、中间还是末尾编辑,光标都能正确对应到原始文本的位置,解决偏移错误问题。
内容的提问来源于stack exchange,提问作者Fernando Sanchez
相关产品推荐
相关产品推荐

