Androidの文字入力がある画面で表示されたソフトウェアキーボードは、デフォルトではユーザが閉じる操作をしない限り表示され続けます。
EditTextなどの対象のView以外をタップしたときにフォーカスを外してキーボードを非表示にしたい、という要件に対応します。
注: composeは考慮していません
タップイベントをフックする
Activity.dispatchTouchEvent(MotionEvent)
を使います。
筆者は執筆時にnavigationを利用していたので、目的のFragmentではなく親のActivityに実装しました
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
binding.navHostFragment.requestFocus()
return super.dispatchTouchEvent(ev)
}
requestFocus()
ではフォーカスを移動しています。フォーカスを移動することで、キーボードを非表示にしていい操作なのかどうかを判別しています。逆に言うと、何をしてもキーボードを閉じてもいい場合はrequestFocus()
は実装しなくて良いと思います。
フォーカスさせるViewはフォーカス可能である必要があるので、設定を加えます。focusable
とandroid:focusableInTouchMode
が目的の設定です
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph"
/>
キーボードを閉じる
先程の実装によって実装されたフォーカスの変更をフックして、閉じる処理を書いていきます。
webで調べるとEditTextからフォーカスが外れた時、を起点にしている記事が多く見かけますがそれだとすべてのEditTextに実装を追加する必要があります。
こんな感じ
binding.editText.setOnFocusChangeListener { view, b ->
if (!b) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}
それでも良いですがフォーカスさせたView自体に実装を加えると、少し楽になります。
binding.navHostFragment.setOnFocusChangeListener { view, b ->
if (b) { // 条件を反転させた
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}
制御フローとしては
- タップする
- Activity(View)でハンドリングする
- フォーカスを移動させる
- 移動先はキーボード表示不要なViewなので、フォーカスされている場合は閉じる処理を実行する
参考