Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TextField 表示スタイル変更の 有効活用例 5 選

akkie76
November 30, 2023

TextField 表示スタイル変更の 有効活用例 5 選

「DroidKaigi.collect { #7@Tokyo }」で発表した資料になります。
https://droidkaigi.connpass.com/event/301886/

akkie76

November 30, 2023
Tweet

More Decks by akkie76

Other Decks in Technology

Transcript

  1. ©2023 RAKUS Co., Ltd.
    TextField 表示スタイル変更の
    有効活用例 5 選
    DroidKaigi.collect { #7@Tokyo }
    2023/12/01
    @akkiee76

    View full-size slide

  2. 自己紹介
    ● Akihiko Sato
    ● Rakus Inc.
    ● 楽楽精算
    ● @akkiee76 / X
    2

    View full-size slide

  3. 表示スタイル変更の有効活用 5 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    3

    View full-size slide

  4. 表示スタイル変更の有効活用 5 3 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    4

    View full-size slide

  5. TextField カスタマイズの基本
    ● VisualTransformation(TextField / 表示スタイル変更)
    ● decorationBox(BasicTextField / カスタムデザイン)
    @Composable
    fun TextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    ・・・
    visualTransformation: VisualTransformation = VisualTransformation.None,
    ・・・
    colors: TextFieldColors = TextFieldDefaults.colors()
    )
    5

    View full-size slide

  6. VisualTransformation
    class OriginalVisualTransformation: VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    return TransformedText(
    text = AnnotatedString(buildString {
    TODO("表示形式へ変換処理")
    }),
    object : OffsetMapping {
    override fun originalToTransformed(offset: Int): Int {
    TODO("入力文字列の位置から表示文字列の位置へのマッピング")
    }
    override fun transformedToOriginal(offset: Int): Int {
    TODO("表示文字列の位置から入力文字列の位置へのマッピング")
    }
    }
    )
    VisualTransformation の拡張クラスを利用することで表示形式の変換を行うことができます
    6

    View full-size slide

  7. decorationBox(BasicTextField)
    @Composable
    fun BasicTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
      ・・・
    decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit =
    @Composable { innerTextField -> innerTextField() }
    )
    独自の Composable を設定し、入力欄のカスタマイズを行うことができます
    7

    View full-size slide

  8. 表示スタイル変更の有効活用 5 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    8

    View full-size slide

  9. 1. パスワード入力: TextField
    val visualTransformation = PasswordVisualTransformation('*')
    TextField(
    value = text,
    onValueChange = { value ->
    if (value.isDigitsOnly()) {
    text = value
    }
    },
    modifier = modifier,
    visualTransformation = visualTransformation,
    keyboardOptions = KeyboardOptions(
    keyboardType = KeyboardType.Number
    )
    )
    9

    View full-size slide

  10. 1. パスワード入力: VisualTransformation
    class PasswordVisualTransformation(val mask: Char = '\u2022') : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    return TransformedText(
    // 文字数分の mask に変換
    AnnotatedString(mask.toString().repeat(text.text.length)),
    OffsetMapping.Identity
    )
    }
    }
    10

    View full-size slide

  11. 表示スタイル変更の有効活用 5 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    11

    View full-size slide

  12. 2. クレジットカード番号入力: TextField
    val visualTransformation = CreditCardVisualTransformation()
    TextField(
    value = text,
    onValueChange = { value ->
    if (value.isDigitsOnly()) {
    text = value
    }
    },
    modifier = modifier,
    visualTransformation = visualTransformation,
    keyboardOptions = KeyboardOptions(
    keyboardType = KeyboardType.Number
    )
    )
    TextFieldはパスワード入力と同様の実装 12

    View full-size slide

  13. class CreditCardNumberVisualTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    val trimmed = if (text.text.length >= 16) {
    text.text.substring(0..15)
    } else {
    text.text
    }
    var output = ""
    for (i in trimmed.indices) {
    output += trimmed[i]
    if (i % 4 == 3 && i != 15) output += "-"
    }
    val creditCardOffsetTranslator = object : OffsetMapping { ・・・ }
    return TransformedText(AnnotatedString(output), creditCardOffsetTranslator)
    2. クレジットカード番号入力: VisualTransformation
    https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose
    13

    View full-size slide

  14. 2. クレジットカード番号入力: VisualTransformation
    class CreditCardNumberVisualTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    val creditCardOffsetTranslator = object : OffsetMapping {
    override fun originalToTransformed(offset: Int): Int {
    if (offset <= 3) return offset
    if (offset <= 7) return offset + 1
    if (offset <= 11) return offset + 2
    if (offset <= 16) return offset + 3
    return 19
    }
    override fun transformedToOriginal(offset: Int): Int {
    if (offset <= 4) return offset
    if (offset <= 9) return offset - 1
    if (offset <= 14) return offset - 2
    if (offset <= 19) return offset - 3
    return 16
    }
    }
    return TransformedText(AnnotatedString(output), creditCardOffsetTranslator) 14

    View full-size slide

  15. 表示スタイル変更の有効活用 5 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    15

    View full-size slide

  16. 3. カンマ区切り数値入力: TextField
    val visualTransformation = CommaSeparatorTransformation()
    TextField(
    value = text,
    onValueChange = { value ->
    if (value.isDigitsOnly()) {
    text = value
    }
    },
    modifier = modifier,
    visualTransformation = visualTransformation,
    keyboardOptions = KeyboardOptions(
    keyboardType = KeyboardType.Number
    )
    )
    TextFieldは同様の実装 16

    View full-size slide

  17. class CommaSeparatorTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    val output = buildString {
    val reversed = text.text.reversed()
    for (index in reversed.indices) {
    if (index > 0 && index % 3 == 0) {
    append(',')
    }
    append(reversed[index])
    }
    }.reversed()
    val offsetMapping = object : OffsetMapping { ・・・ }
    return TransformedText(text = AnnotatedString(output), offsetMapping = offsetMapping)
    }
    }
    3. カンマ区切り数値入力: VisualTransformation
    17

    View full-size slide

  18. class CommaSeparatorTransformation : VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    val offsetMapping = object : OffsetMapping {
    override fun originalToTransformed(offset: Int): Int {
    val totalSeparatorCount = (text.length - 1) / 3
    val rightSeparatorCount = (text.length - 1 - offset) / 3
    val leftSeparatorCount = totalSeparatorCount - rightSeparatorCount
    return offset + leftSeparatorCount
    }
    override fun transformedToOriginal(offset: Int): Int {
    val totalSeparatorCount = (text.length - 1) / 3
    val rightSeparatorCount = (output.length - offset) / 4
    val leftSeparatorCount = totalSeparatorCount - rightSeparatorCount
    return offset - leftSeparatorCount
    }
    }
    return TransformedText(text = AnnotatedString(output), offsetMapping = offsetMapping)
    3. カンマ区切り数値入力: VisualTransformation
    18

    View full-size slide

  19. 表示スタイル変更の有効活用 5 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    19

    View full-size slide

  20. 4. PINコード入力: TextField
    BasicTextField(
    value = text,
    onValueChange = { newValue ->
    if (newValue.length <= MaxDigits) {
    text = newValue
    }
    },
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword),
    interactionSource = interactionSource,
    modifier = modifier.border(1.dp, contentColor, RoundedCornerShape(8.dp))
    ) {
    CompositionLocalProvider(LocalContentColor provides contentColor) {
    PinDigits(text, interactionSource.collectIsFocusedAsState().value)
    }
    } 20

    View full-size slide

  21. @Composable
    fun PinDigits() {
    Row(horizontalArrangement = Arrangement.spacedBy(16.dp),
    modifier = modifier.padding(16.dp))
    {
    repeat(MaxDigits) { i ->
    Text(
    text = text.getOrNull(i)?.toString() ?: " ",
    modifier = Modifier.drawWithContent {
    if (i == text.length && focused) {
    drawRect(color = focusedColor)
    }
    drawContent()
    if (i >= text.length) {
    drawLine(・・・)
    }
    }
    4. PINコード入力: decorationBox
    21

    View full-size slide

  22. 表示スタイル変更の有効活用 5 選
    1. パスワード入力
    2. クレジットカード番号入力
    3. カンマ区切り数値入力
    4. PINコード入力
    5. プレフィックス文字入力
    22

    View full-size slide

  23. 5. プレフィックス文字入力: TextField
    val visualTransformation = PrefixVisualTransformation()
    TextField(
    value = text,
    onValueChange = { value ->
    if (value.isDigitsOnly() && value.length <= MAX_LENGTH) {
    text = value
    }
    },
    modifier = modifier,
    visualTransformation = visualTransformation,
    keyboardOptions = KeyboardOptions(
    keyboardType = KeyboardType.Number
    )
    )
    TextFieldはパスワード入力と同様の実装 23

    View full-size slide

  24. class PrefixVisualTransformation: VisualTransformation {
    override fun filter(text: AnnotatedString): TransformedText {
    return TransformedText(
    text = AnnotatedString(buildString {
    text.forEachIndexed { i, char ->
    if (i == 0) { append('X') }
    append(char)
    }
    }),
    object : OffsetMapping {
    override fun originalToTransformed(offset: Int): Int {
    return if (offset < 1) offset else offset + 1
    }
    override fun transformedToOriginal(offset: Int): Int {
    return if (offset < 1) offset else offset - 1
    }
    }
    5. プレフィックス文字入力: VisualTransformation
    24

    View full-size slide

  25. まとめ
    ● VisualTransformation で自由に表示テキストの変更が可能
    ● dacorationBox を使うとデザインの幅も広がる
    25

    View full-size slide

  26. Thank you !
    26

    View full-size slide