简体   繁体   English

Jetpack compose 中的电话号码视觉转换

[英]Phone number visual transformation in Jetpack compose

How can I implement phone number visual transformation in jetpack compose?如何在 Jetpack Compose 中实现电话号码视觉转换? I have read this article for the card number.我已经阅读了这篇关于卡号的文章。

And I want to format my phone number like this xx xxx xx xx我想像这样格式化我的电话号码xx xxx xx xx

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/

  1. They're adding space after every 4th character in AnnotatedString.Builder() You need it on 1, 4, 6.他们在AnnotatedString.Builder()中每第 4 个字符后添加空格,您需要在 1、4、6 上使用它。
  2. then they've added 2 spaces that's why they're adding spaces like 2,4,6 in originalToTransformed but you need 1,2,3 & same for deducting in transformedToOriginal然后他们添加了 2 个空格,这就是为什么他们在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 文章:

Adding Phone Number Visual transformation 添加电话号码视觉转换

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM