🗝️
JetpackCompose よくあるSMS入力フォームを作ってみた
こういうやつ。
サンプルコード
@Composable
fun SmsContentWidget(
onValueChange: (String, Int) -> Unit,
sendPhoneValue: String,
backPressed: () -> Unit
) {
val smsValueMap: MutableMap<Int, String> = mutableMapOf()
val latestPosition = remember { mutableStateOf(-1) }
Box(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.contentTitleSms),
)
}
Spacer(
modifier = Modifier
.height(8.dp)
)
Text(
text = sendPhoneValue
+ stringResource(id = R.string.contentSms),
)
Spacer(
modifier = Modifier
.height(16.dp)
)
SmsAuthTextField(
latestPosition = latestPosition.value,
numMap = smsValueMap,
digit = 6,
onNumChange = onValueChange
) {
latestPosition.value = it
}
Spacer(
modifier = Modifier
.height(16.dp)
)
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.BottomEnd
) {
// 電話番号再送信ボタン
SendAgainButton {
inputEntityを初期化する処理
latestPosition.value = -1
phoneNumberAgainAction.send()
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun SmsAuthTextField(
latestPosition: Int,
numMap: Map<Int, String>,
digit: Int,
backPressed: () -> Unit,
onNumChange: (String, Int) -> Unit,
updateLatestPosition: (Int) -> Unit
) {
val keyboardController = LocalSoftwareKeyboardController.current
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
repeat(digit) {
SmsAuthTextFieldUnit(
onDone = {
keyboardController?.hide()
},
position = it,
latestPosition = latestPosition,
num = numMap[it] ?: "",
onNumChange = { s, i ->
updateLatestPosition(
if (s.isBlank()) i - 2
else i
)
if (i == digit - 1) keyboardController?.hide()
onNumChange(s, i)
Timber.tag("latestPosition").d("$latestPosition")
Timber.tag("numMap").d("$numMap")
}
)
}
}
}
@ExperimentalComposeUiApi
@Composable
private fun SmsAuthTextFieldUnit(
onDone: () -> Unit,
position: Int,
latestPosition: Int,
num: String,
onNumChange: (String, Int) -> Unit,
backPressed: () -> Unit
) {
val hasFocus = latestPosition == position - 1
val focusRequester = remember { FocusRequester() }
SideEffect {
if (hasFocus) focusRequester.requestFocus()
}
Box(
contentAlignment = Alignment.Center
) {
TextField(
modifier = Modifier
.size(48.dp)
.focusRequester(focusRequester)
.onKeyEvent {
when (it.nativeKeyEvent.keyCode) {
KEYCODE_DEL -> onNumChange(" ".trim(), position)
KEYCODE_BACK -> backPressed()
}
true
},
value = num.takeIf { hasFocus } ?: " ",
onValueChange = {
onNumChange(it.trim(), position)
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
onDone()
}
),
visualTransformation = PasswordVisualTransformation(),
singleLine = true,
maxLines = 1
)
if (num.isBlank() && !hasFocus) Spacer(
modifier = Modifier
.background(Colors.textField)
.size(8.dp)
)
}
}
ハマりポイント
- 入力したら次にフォーカス、削除したら前にフォーカスするのが大変だった。
- カーソルが左側にあっても消せるようにするのが大変だった。
- 変な値が入らないようにするのが大変だった。
解決ポイント
- 入力したら次にフォーカス、削除したら前にフォーカスするのが大変だった。
- 入力している値を監視し、値が入っていれば次にフォーカス
- 値がなければ手前にフォーカス
- カーソルが左側にあっても消せるようにするのが大変だった。
- KeyEventという関数があり、それで入力しているキーに対してイベントを追加することで対応した。
https://developer.android.com/reference/android/view/KeyEvent
- KeyEventという関数があり、それで入力しているキーに対してイベントを追加することで対応した。
TextField(
modifier = Modifier
.size(48.dp)
.focusRequester(focusRequester)
.onKeyEvent {
if (it.nativeKeyEvent.keyCode == KEYCODE_DEL) {
onNumChange(" ".trim(), position)
}
true
},
ここですね。
Modifierに対してonKeyEventをくっつけることができるので、
キーを押した時に指定のComposeに対してイベントを発火させることができる。
なんか色々書いてあったけどどれも当てにならなくて、
結局自分で探った結果
nativeKeyEventでスマホのキーを拾うことができて、
それのkeyCodeを拾って指定することでやりたい動きを実現することができた。
- 変な値が入らないようにするのが大変だった。
- これはひたすらデバッグで止めて確認しただけ()
実は大変なバグがある
別にこのコードに限ったことではないのですが、
JetpackComposeのキーボードタイプ指定がバグってて、
フォーカスが当たるたびに一瞬元の言語のキーボードがチラつきます......
この記事でも取り上げられGoogleIssueTrackerにも挙げられているようですが、
今のところ解決策はなさそうです......
何か知ってる人いたら教えてください(泣)
更なるハマりポイントがあった
onKeyEvent
こちらですが完全に端末のキー操作をハックしてしまうようで、
it.nativeKeyEvent.keyCode == KEYCODE_BACK -> backPressed()
こうやってあげないと戻るキーで前画面に戻れなくなってしまいました.......
もしかしたら他のキーも聞かなくなっている可能性があるので、
onKeyEventは気をつけて使わないと危ないですね;;;;;;
参考記事
本当はもっと参考記事が色々あったのですが、
chomeが落ちてタブにしか残ってなかったので全部消えました......
Discussion
これやばいですね、気をつけんと、、w
ヤバ過ぎて震えましたね😇
指定したキーにイベントを持たせるのかと思ったら全制御奪っちゃうとか何事ーーーーーーw