0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】TextFieldのフォーカスをダイアログの外側タップでも解除したい!!

Posted at

はじめに

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のフォーカスを外すことができるようになりました

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?