📝

Android キーボードを隠す

2024/11/22に公開

Fragmentでタッチイベントを使ってキーボードを隠す方法


はじめに

ChatGPT先生に聞いてみました。
この記事では、ChatGPTを活用して得られたアイデアを元に、Fragmentでのタッチイベントを使ったキーボード制御の実装方法をご紹介します。


背景

EditTextにフォーカスがある状態で他の部分をタップすると、キーボードを隠したい場面は多くのアプリで必要になります。

この記事では、Fragmentを用いて、EditText以外をタップした場合にキーボードを隠す汎用的な方法をご紹介します。


解決策

ルートビューとすべてのViewGroupsetOnTouchListenerを設定し、タッチイベントを検知してキーボードを隠す処理を実現しています。(複数回処理されます。)
自分はベースのフラグメントとして用意するFragmentに実装して、それを継承して処理します。


実装例

@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    /** ビュー階層を再帰的に探索 */
    fun traverseViews(view: View, callback: (View) -> Unit) {
        callback.invoke(view)

        if (view is ViewGroup) {
            for (i in 0 until view.childCount) {
                traverseViews(view.getChildAt(i), callback)
            }
        }
    }

    // 対象ビューにsetOnTouchListenerを設定
    traverseViews(view) { currentView ->
        currentView.setOnTouchListener { _, event ->
            processTouchEvent(event, view)
            false // デフォルトのタッチ動作を保持
        }
    }
}

private fun processTouchEvent(event: MotionEvent, rootView: View) {
    fun findViewAt(view: View, x: Int, y: Int): View? {
        if (view !is ViewGroup) {
            if (x >= view.left && x <= view.right && y >= view.top && y <= view.bottom) {
                return view
            }
            return null
        }
        for (i in 0 until view.childCount) {
            val child = view.getChildAt(i)
            val foundView = findViewAt(child, x - child.left, y - child.top)
            if (foundView != null) {
                return foundView
            }
        }
        return view // 必要に応じてコンテナビューを返す
    }

    fun findTouchedView(rootView: View, event: MotionEvent): View? {
        val location = IntArray(2)
        rootView.getLocationOnScreen(location)
        val x = event.rawX.toInt() - location[0]
        val y = event.rawY.toInt() - location[1]
        return findViewAt(rootView, x, y)
    }

    if (event.action == MotionEvent.ACTION_DOWN) {
        findTouchedView(rootView, event)?.let { touchedView ->
            if (touchedView !is EditText) {
                dismissKeyboard(touchedView)
            }
        }
    }
}

// キーボードを閉じる
private fun dismissKeyboard(view: View) {

  ContextCompat.getSystemService(view.context, InputMethodManager::class.java)?.let { imm ->
    imm.hideSoftInputFromWindow(view.windowToken, 0)
  }
}

Discussion