はじめに
Androoid のアプリ開発中に、Jetpack Compose で ダイアログに配置した TextField
のフォーカスを、ダイアログの外側をタップしてフォーカスが外れるように実装しようとしました。
その時に少し困ったので備忘録的に書き残します。
基本実装(サンプル)
ダイアログの内外をタップでフォーカスが外れるようにしたかったので、まずはダイアログ内をタップするとフォーカスが外れるように実装してみます。
実装コード
@Composable
fun KeyboardDialog(modifier: Modifier, onClick: () -> Unit = {}, onDismissRequest: () -> Unit = {}) {
Dialog(
properties = DialogProperties(),
onDismissRequest = onDismissRequest
) {
var value by remember { mutableStateOf("") }
val focusManager = LocalFocusManager.current
Column(
modifier = modifier
.width(290.dp)
.background(color = Color.White, shape = RoundedCornerShape(20.dp))
.padding(10.dp)
.pointerInput(Unit) {
detectTapGestures(onTap = { focusManager.clearFocus() }) // タップでフォーカスを外す
},
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "以下の項目を入力してください",
fontSize = 16.fontDp,
fontWeight = FontWeight.SemiBold
)
Text("・好きな食べ物", modifier = Modifier.padding(top = 10.dp), fontSize = 16.fontDp)
TextField(
value = value,
onValueChange = { value = it },
label = { Text("テキストを入力してください") },
modifier = Modifier.padding(vertical = 4.dp)
)
Text(
text = "入力完了",
modifier = Modifier
.padding(top = 100.dp)
.size(height = 40.dp, width = 180.dp)
.background(Color.Red, shape = RoundedCornerShape(10.dp))
.padding(vertical = 6.dp)
.clickable{ onClick() },
color = Color.White,
fontSize = 16.fontDp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
)
}
}
}
フォーカスを外すようにする処理はいたって簡単。
val focusManager = LocalFocusManager.current
で FocusManager を取得して、focusManager.clearFocus()
を利用するだけです。
これでダイアログの内部をタップしてフォーカスが外れるようになりました。
ダイアログの外側をタップしてフォーカスを外れるようにしたかった......(失敗)
基本実装をもとに、ダイアログの外側をタップしてもフォーカスが外れるようにしていきます。
とりあえずDialog
の外側にBox
を配置して、fillMaxSize
してfocusManager.clearFocus()
を渡しておけばいいかな?と思っていました。
実装コード
@Composable
fun KeyboardDialog(
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
onDismissRequest: () -> Unit = {},
) {
val focusManager = LocalFocusManager.current
// 外側のタップを検知する Box
Box(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(onTap = { focusManager.clearFocus() }) // タップでフォーカスを外す
},
contentAlignment = Alignment.Center
) {
Dialog(
properties = DialogProperties(),
onDismissRequest = onDismissRequest
) {
var value by remember { mutableStateOf("") }
Column(
modifier = modifier
.width(290.dp)
.background(color = Color.White, shape = RoundedCornerShape(20.dp))
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "以下の項目を入力してください",
fontSize = 16.fontDp,
fontWeight = FontWeight.SemiBold
)
Text(
"・好きな食べ物",
modifier = Modifier.padding(top = 10.dp),
fontSize = 16.fontDp
)
TextField(
value = value,
onValueChange = { value = it },
label = { Text("テキストを入力してください") },
modifier = Modifier
.padding(vertical = 4.dp)
)
Text(
text = "入力完了",
modifier = Modifier
.padding(top = 100.dp)
.size(height = 40.dp, width = 180.dp)
.background(Color.Red, shape = RoundedCornerShape(10.dp))
.padding(vertical = 6.dp)
.clickable { onClick() },
color = Color.White,
fontSize = 16.fontDp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
)
}
}
}
}
するとどうでしょう、フォーカスは外れません。
どうやらDialog
が独自のfocusManager
を持っているようで、Dialog
の外側でLocalFocusManager.current
しても内部のfocusManager
を取得できず、フォーカスが外せないようです。
これは困った。
解決:Dialog を使わずに Box でカスタムダイアログを作成する
で、結局どうしたかというと、Dailog
を使うのをやめました。
Box
を活用して カスタムダイアログを作成 すると、ダイアログの外側をタップしたときにフォーカスを外すことができます。
実装コード
@Composable
fun KeyboardDialog(
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
onDismissRequest: () -> Unit = {},
) {
val focusManager = LocalFocusManager.current
// 外側のタップを検知する Box
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.6f))
.pointerInput(Unit) {
detectTapGestures(onTap = { focusManager.clearFocus() }) // タップでフォーカスを外す
},
contentAlignment = Alignment.Center
) {
var value by remember { mutableStateOf("") }
BackHandler(onBack = onDismissRequest)
Column(
modifier = modifier
.width(290.dp)
.background(color = Color.White, shape = RoundedCornerShape(20.dp))
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "以下の項目を入力してください",
fontSize = 16.fontDp,
fontWeight = FontWeight.SemiBold
)
Text(
"・好きな食べ物",
modifier = Modifier.padding(top = 10.dp),
fontSize = 16.fontDp
)
TextField(
value = value,
onValueChange = { value = it },
label = { Text("テキストを入力してください") },
modifier = Modifier
.padding(vertical = 4.dp)
)
Text(
text = "入力完了",
modifier = Modifier
.padding(top = 100.dp)
.size(height = 40.dp, width = 180.dp)
.background(Color.Red, shape = RoundedCornerShape(10.dp))
.padding(vertical = 6.dp)
.clickable { onClick() },
color = Color.White,
fontSize = 16.fontDp,
fontWeight = FontWeight.SemiBold,
textAlign = TextAlign.Center,
)
}
}
}
先ほどのうまくいかなったコードから変更したのは以下の通り
- Dialogを取り外した
- 戻るボタンで
dismiss
できるようにしたかったので、BackHandler
を利用
これでダイアログの外をタップして、TextFieldのフォーカスを外すことができるようになりました