Jetpack Compose中TextField结合TransformedText失焦再聚焦崩溃问题
修复Jetpack Compose手机号输入格式化半输入时聚焦崩溃问题
问题分析
崩溃报错java.lang.IllegalStateException: OffsetMapping.transformedToOriginal returned invalid mapping: 15 -> 10 is not in range of original text [0, 5],核心原因是当前OffsetMapping的逻辑是针对完整10位手机号硬编码的,当原始文本长度不足10位时,transformedToOriginal返回的位置超出了原始文本的实际范围。比如输入5位字符时,原始文本长度仅为5,但代码里直接返回固定值10,触发了Compose对偏移映射的合法性校验,导致崩溃。
修复方案
需要根据原始文本的实际长度动态计算偏移映射,同时确保转换后的原始位置始终在[0, 原始文本长度]范围内。修改phoneNumberInputFormatter中的OffsetMapping实现,结合当前输入的文本长度调整逻辑:
修改后的完整格式化器代码
private fun phoneNumberInputFormatter(text: AnnotatedString): TransformedText { val mask = "+1 xxx-xxx-xxxx" val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text val originalLength = trimmed.length val annotatedString = AnnotatedString.Builder().run { for (i in trimmed.indices) { if (i == 0) { append("+1 ") } append(trimmed[i]) if (i == 2 || i == 5) { append("-") } } pushStyle(SpanStyle(color = Color.LightGray)) append(mask.takeLast(mask.length - length)) toAnnotatedString() } val phoneNumberOffsetTranslator = object : OffsetMapping { override fun originalToTransformed(offset: Int): Int { var transformedOffset = offset // 加上固定前缀"+1 "的长度 if (offset > 0) transformedOffset += 2 // 第一个分隔符"-",仅当原始偏移超过3位时累加 if (offset > 3) transformedOffset += 1 // 第二个分隔符"-",仅当原始偏移超过6位时累加 if (offset > 6) transformedOffset += 1 // 限制偏移不超过掩码总长度 return min(transformedOffset, mask.length) } override fun transformedToOriginal(offset: Int): Int { var originalOffset = offset - 2 // 减去第一个分隔符的偏移,仅当转换后的位置超过"+1 xxx"区域时生效 if (offset > 5) originalOffset -= 1 // 减去第二个分隔符的偏移,仅当转换后的位置超过"+1 xxx-"区域时生效 if (offset > 9) originalOffset -= 1 // 强制限制原始偏移在合法范围内,避免越界 return originalOffset.coerceIn(0, originalLength) } } return TransformedText(annotatedString, phoneNumberOffsetTranslator) }
关键修复点
originalToTransformed:改为根据原始偏移动态累加分隔符长度,避免硬编码判断范围,同时用min限制偏移不超过掩码总长度。transformedToOriginal:- 先处理固定前缀
"+1 "的偏移 - 仅当转换后的位置超过对应分隔符的实际位置时,才减去分隔符的偏移量
- 最后用
coerceIn强制保证返回的原始位置始终在[0, originalLength]范围内,彻底杜绝越界问题
- 先处理固定前缀
额外优化
可以在onValueChange中限制输入仅为数字,避免非数字字符干扰格式化逻辑:
onValueChange = { input -> num = input.filter { it.isDigit() } }
内容的提问来源于stack exchange,提问作者Murphler
相关产品推荐
相关产品推荐

