[英]Phone number visual transformation in Jetpack compose
You can just modify some params from the example link that you provided according to the pattern you need.您可以根据需要的模式修改您提供的示例链接中的一些参数。 You need to consider the max length you want, how many spaces you need between each section.
您需要考虑所需的最大长度,每个部分之间需要多少空间。
For example in your given link here: http://zenandroid.io/using-the-jetpack-composes-visualtransformation-to-create-a-credit-card-text-input/例如,在您给定的链接中: http://zenandroid.io/using-the-jetpack-composes-visualtransformation-to-create-a-credit-card-text-input/
AnnotatedString.Builder()
You need it on 1, 4, 6.AnnotatedString.Builder()
中每第 4 个字符后添加空格,您需要在 1、4、6 上使用它。originalToTransformed
but you need 1,2,3 & same for deducting in transformedToOriginal
originalToTransformed
中添加像 2,4,6 这样的空格,但是在transformedToOriginal
中你需要 1,2,3 和相同的空间来扣除Complete code example:完整代码示例:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Test()
}
}
}
}
}
@Composable
fun Test() {
var mobileNumber by rememberSaveable { mutableStateOf("") }
Column {
Row(modifier = Modifier.padding(all = 10.dp)) {
Text(
text = "Mobile number",
fontSize = 14.sp,
modifier = Modifier.weight(1f)
)
BasicTextField(
value = mobileNumber,
onValueChange = { mobileNumber = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
visualTransformation = { mobileNumberFilter(it) }
)
}
Box(
modifier = Modifier
.height(1.dp)
.padding(start = 10.dp)
.fillMaxWidth()
.background(Color.Gray)
)
Spacer(modifier = Modifier.height(20.dp))
Text(text = "Actual value:\n$mobileNumber")
}
}
const val mask = "xx xxx xx xx"
fun mobileNumberFilter(text: AnnotatedString): TransformedText {
// change the length
val trimmed =
if (text.text.length >= 9) text.text.substring(0..8) else text.text
val annotatedString = AnnotatedString.Builder().run {
for (i in trimmed.indices) {
append(trimmed[i])
if (i == 1 || i == 4 || i == 6) {
append(" ")
}
}
pushStyle(SpanStyle(color = Color.LightGray))
append(mask.takeLast(mask.length - length))
toAnnotatedString()
}
val phoneNumberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 1) return offset
if (offset <= 4) return offset + 1
if (offset <= 6) return offset + 2
if (offset <= 9) return offset + 3
return 12
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 1) return offset
if (offset <= 4) return offset - 1
if (offset <= 6) return offset - 2
if (offset <= 9) return offset - 3
return 9
}
}
return TransformedText(annotatedString, phoneNumberOffsetTranslator)
}
Output: Output:
For a north American version:对于北美版本:
const val mask = "(xxx) xxx-xxxx"
fun mobileNumberFilter(text: AnnotatedString, formType: FormType):
TransformedText {
if (formType != FormType.SHIPPING_PHONE) {
return VisualTransformation.None.filter(text)
}
// change the length
val trimmed =
if (text.text.length >= 14) text.text.substring(0..13) else text.text
val annotatedString = AnnotatedString.Builder().run {
for (i in trimmed.indices) {
val trimmedPortion = trimmed[i]
if (i == 0) {
append("($trimmedPortion")
} else {
append(trimmedPortion)
}
if (i == 2) {
append(") ")
}
if (i == 5) {
append("-")
}
}
pushStyle(
SpanStyle(color = Color.LightGray)
)
try {
append(mask.takeLast(mask.length - length))
} catch (e: IllegalArgumentException) {
Timber.d(e.localizedMessage?.plus(" reached end of phone number"))
}
toAnnotatedString()
}
val translator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 1) return offset
if (offset <= 4) return offset + 1
if (offset <= 9) return offset + 2
return 14
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 1) return offset
if (offset <= 4) return offset - 1
if (offset <= 9) return offset - 2
return 14
}
}
return TransformedText(annotatedString, translator)
}
For an even cleaner version, see this Medium Article:如需更简洁的版本,请参阅这篇 Medium 文章:
The dynamic phone number formatter.动态电话号码格式化程序。
Some examples with different masks.一些带有不同面具的例子。
@Composable
fun LoginScreen() {
var phoneNumber by rememberSaveable { mutableStateOf("") }
Column {
PhoneField(phoneNumber,
mask = "000 000 00 00",
maskNumber = '0',
onPhoneChanged = { phoneNumber = it })
Spacer(modifier = Modifier.padding(8.dp))
PhoneField(phoneNumber,
mask = "(000) 000 00 00",
maskNumber = '0',
onPhoneChanged = { phoneNumber = it })
Spacer(modifier = Modifier.padding(8.dp))
PhoneField(phoneNumber,
mask = "+7-000-000-00-00",
maskNumber = '0',
onPhoneChanged = { phoneNumber = it })
}
}
@Composable
fun PhoneField(
phone: String,
modifier: Modifier = Modifier,
mask: String = "000 000 00 00",
maskNumber: Char = '0',
onPhoneChanged: (String) -> Unit
) {
TextField(
value = phone,
onValueChange = { it ->
onPhoneChanged(it.take(mask.count { it == maskNumber }))
},
label = {
Text(text = "Phone number")
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
visualTransformation = PhoneVisualTransformation(mask, maskNumber),
modifier = modifier.fillMaxWidth(),
)
}
class PhoneVisualTransformation(val mask: String, val maskNumber: Char) : VisualTransformation {
private val maxLength = mask.count { it == maskNumber }
override fun filter(text: AnnotatedString): TransformedText {
val trimmed = if (text.length > maxLength) text.take(maxLength) else text
val annotatedString = buildAnnotatedString {
if (trimmed.isEmpty()) return@buildAnnotatedString
var maskIndex = 0
var textIndex = 0
while (textIndex < trimmed.length && maskIndex < mask.length) {
if (mask[maskIndex] != maskNumber) {
val nextDigitIndex = mask.indexOf(maskNumber, maskIndex)
append(mask.substring(maskIndex, nextDigitIndex))
maskIndex = nextDigitIndex
}
append(trimmed[textIndex++])
maskIndex++
}
}
return TransformedText(annotatedString, PhoneOffsetMapper(mask, maskNumber))
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PhonedVisualTransformation) return false
if (mask != other.mask) return false
if (maskNumber != other.maskNumber) return false
return true
}
override fun hashCode(): Int {
return mask.hashCode()
}
}
private class PhoneOffsetMapper(val mask: String, val numberChar: Char) : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
var noneDigitCount = 0
var i = 0
while (i < offset + noneDigitCount) {
if (mask[i++] != numberChar) noneDigitCount++
}
return offset + noneDigitCount
}
override fun transformedToOriginal(offset: Int): Int =
offset - mask.take(offset).count { it != numberChar }
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.