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