📱

Jetpack Compose TextFieldとKeyboardでの問題と解決

に公開

TextFieldの中のpaddingが調整できない

A. BasicTextFieldを使う
TextFieldはinnerPaddingが固定値設定されている

val value = remember { mutableStateOf("") }
    BasicTextField(
        modifier = modifier,
        value = value.value,
        onValueChange = { value.value = it},
        decorationBox = { innerTextField ->
            innerTextField()
        },
    )

複数のTextFieldのフォーカスを制御したい

A. focusRequestersを使う
この例は最初のBasicTextFieldで入力すると次のBasicTextFieldにフォーカスを移動する
フォーカスを先に進めたり戻したりする要件の場合使用する
BasicTextFieldの数だけfocusRequesterを用意する

    val values = remember { mutableStateListOf("", "") }
    val focusRequesters = remember { List(values.size) { FocusRequester() } }
    Row {
        BasicTextField(
            modifier = modifier.focusRequester(focusRequesters[0]),
            value = values[0],
            onValueChange = {
                values[0] = it
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                ) {
                    innerTextField()
                }
            },
        )
        BasicTextField(
            modifier = modifier.focusRequester(focusRequesters[1]),
            value = values[1],
            onValueChange = { values[1] = it },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Blue)
                ) {
                    innerTextField()
                }
            },
        )
    }

キーボードの基本的な制御をしたい

A. keyboardOptionsとkeyboardActionsを組み合わせる
この場合はキーボードを開いた時に数字で決定は完了にする
onDoneで制御できる

BasicTextField(
            modifier = modifier.focusRequester(focusRequesters[0]),
            value = values[0],
            onValueChange = {
                values[0] = it
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                ) {
                    innerTextField()
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Done,
            ),
            keyboardActions = KeyboardActions(
                onDone = {

                }
            )
        )

今フォーカスされているかどうか判定したい

A. MutableInteractionSourceを使う
この場合フォーカスされてる時とそうでない時でBorderの色を変える

val interactionSource = remember { MutableInteractionSource() }
    val isFocused = interactionSource.collectIsFocusedAsState().value

    val border = if (isFocused) Color.Black else Color.Green
BasicTextField(
            modifier = modifier.focusRequester(focusRequesters[0]),
            interactionSource = interactionSource,
            value = values[0],
            onValueChange = {
                values[0] = it
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                        .border(2.dp, border)
                ) {
                    innerTextField()
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Done,
            ),
            keyboardActions = KeyboardActions(
                onDone = {

                }
            )
        )

全てのフォーカスを外したい

A. focusManagerを使う
キーボードでonDoneしたらフォーカスが外れる

val focusManager = LocalFocusManager.current
BasicTextField(
            modifier = modifier.focusRequester(focusRequesters[0]),
            interactionSource = interactionSource,
            value = values[0],
            onValueChange = {
                values[0] = it
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                        .border(2.dp, border)
                ) {
                    innerTextField()
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Done,
            ),
            keyboardActions = KeyboardActions(
                onDone = {
                    focusManager.clearFocus()
                }
            )
        )

キーボードを閉じたい

A. keyboardControllerを使う
keyboardActionsをonNextに変更してonNextされたらキーボードを閉じるようにする

val keyboardController = LocalSoftwareKeyboardController.current
BasicTextField(
            modifier = modifier.focusRequester(focusRequesters[0]),
            interactionSource = interactionSource,
            value = values[0],
            onValueChange = {
                values[0] = it
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                        .border(2.dp, border)
                ) {
                    innerTextField()
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Next,
            ),
            keyboardActions = KeyboardActions(
                onNext = {
                    focusManager.clearFocus()
                    keyboardController?.hide()
                }
            )
        )

キーボードの特定のキーイベントを検知したい

A. onKeyEventを設定する
return@onKeyEvent trueすることでイベントを消費する

BasicTextField(
            modifier = modifier
                .focusRequester(focusRequesters[0])
                .onKeyEvent { keyEvent ->
                    if (keyEvent.key == Key.Backspace) {
                        // Handle backspace key event
                        return@onKeyEvent true
                    }
                    false
                },
            interactionSource = interactionSource,
            value = values[0],
            onValueChange = {
                values[0] = it
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                        .border(2.dp, border)
                ) {
                    innerTextField()
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Next,
            ),
            keyboardActions = KeyboardActions(
                onNext = {
                    focusManager.clearFocus()
                    keyboardController?.hide()
                }
            )
        )

カーソルの位置を制御したい

A. TextFieldValueをValueに設定する
selectionでカーソルの位置を指定できる

BasicTextField(
            modifier = modifier
                .focusRequester(focusRequesters[0])
                .onKeyEvent { keyEvent ->
                    if (keyEvent.key == Key.Backspace) {
                        // Handle backspace key event
                        return@onKeyEvent true
                    }
                    false
                },
            interactionSource = interactionSource,
            value = TextFieldValue(
                text = values[0],
                selection = TextRange(values[0].length),
            ),
            onValueChange = {
                values[0] = it.text
                focusRequesters[1].requestFocus()
            },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .background(Color.Red)
                        .border(2.dp, border)
                ) {
                    innerTextField()
                }
            },
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Number,
                imeAction = ImeAction.Next,
            ),
            keyboardActions = KeyboardActions(
                onNext = {
                    focusManager.clearFocus()
                    keyboardController?.hide()
                }
            )
        )

入力されたものを半角に変換したい

A. Normalizerを仕様し全ての文字を半角にする
NF: Normalization Form(正規化形式)
K: Compatibility(互換性)
C: Canonical composition(正規合成)
正規化する

private fun String.toHalfWidth(): String {
    return Normalizer.normalize(this, Normalizer.Form.NFKC)
}

入力中スクロールしたらキーボードを閉じたい

A. scrollStateのisScrollInProgressか判定しkeyboardControllerを使用しキーボードを隠す

val keyboardController = LocalSoftwareKeyboardController.current
    val scrollState = rememberScrollState()

    LaunchedEffect(scrollState.isScrollInProgress) {
        if (scrollState.isScrollInProgress) {
            keyboardController?.hide()
        }
    }

    Column(
        modifier
            .fillMaxSize()
            .verticalScroll(scrollState),
    ) {
        Row {
            BasicTextField(
                modifier = modifier
                    .focusRequester(focusRequesters[0])
                    .onKeyEvent { keyEvent ->
                        if (keyEvent.key == Key.Backspace) {
                            // Handle backspace key event
                            return@onKeyEvent true
                        }
                        false
                    },
                interactionSource = interactionSource,
                value = TextFieldValue(
                    text = values[0],
                    selection = TextRange(values[0].length),
                ),
                onValueChange = {
                    values[0] = it.text.toHalfWidth()
                    focusRequesters[1].requestFocus()
                },
                decorationBox = { innerTextField ->
                    Box(
                        modifier = Modifier
                            .size(100.dp)
                            .background(Color.Red)
                            .border(2.dp, border)
                    ) {
                        innerTextField()
                    }
                },
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Number,
                    imeAction = ImeAction.Next,
                ),
                keyboardActions = KeyboardActions(
                    onNext = {
                        focusManager.clearFocus()
                        keyboardController?.hide()
                    }
                )
            )
            BasicTextField(
                modifier = modifier.focusRequester(focusRequesters[1]),
                value = values[1],
                onValueChange = { values[1] = it },
                decorationBox = { innerTextField ->
                    Box(
                        modifier = Modifier
                            .size(100.dp)
                            .background(Color.Blue)
                    ) {
                        innerTextField()
                    }
                },
            )
        }
    }

パスワード入力のTextFieldを作りたい

A. keyboardOptionsとvisualTransformationを使用する

val showPassword = remember { mutableStateOf(false) }
BasicTextField(
            keyboardOptions = KeyboardOptions(
                keyboardType = KeyboardType.Password,
                imeAction = ImeAction.Done,
            ),
            singleLine = true,
            visualTransformation = if (showPassword.value) VisualTransformation.None else PasswordVisualTransformation(),
            value = values[2],
            onValueChange = { values[2] = it },
            decorationBox = { innerTextField ->
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .height(40.dp)
                        .background(Color.Magenta),
                    contentAlignment = Alignment.CenterStart
                ) {
                    innerTextField()
                    Icon(
                        modifier = Modifier
                            .size(26.dp)
                            .align(Alignment.CenterEnd)
                            .clickable { showPassword.value = !showPassword.value },
                        painter = painterResource(id = R.drawable.ic_launcher_foreground),
                        contentDescription = "表示・非表示",
                    )
                }
            },
        )

これでいいかも

modifier = Modifier
            .pointerInput(Unit) {
                detectTapGestures(
                    onPress = { 
                        keyboardController?.hide()
                    }
                )
            },

Discussion