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

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

akkiee76
November 30, 2023

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

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

akkiee76

November 30, 2023
Tweet

More Decks by akkiee76

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. decorationBox(BasicTextField) @Composable fun BasicTextField( value: String, onValueChange: (String) -> Unit,

    modifier: Modifier = Modifier,   ・・・ decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit = @Composable { innerTextField -> innerTextField() } ) 独自の Composable を設定し、入力欄のカスタマイズを行うことができます 7
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. @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
  14. 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
  15. 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