🐥

Androidのダイアログで表示しているキーボードを動的に消す

2021/07/19に公開

概要

自作のビューを表示したAlertDialogEditTextを置いていて、それを編集中に表示されているキーボードを何らかのアクションをトリガーに非表示にする方法で少しハマったのでメモ。

上の画像ではダイアログにあるEditTextをタップでキーボードが出現して、削除をタップでキーボードが消えて削除確認メッセージが表示される。
このダイアログの利用シーンとしては、一度登録したマスターデータなどを編集、削除するようなケースを想定。

結論

InputMethodManagerhideSoftInputFromWindowメソッドにdialog.currentFocus?.windowTokenを渡すのと、フォーカスを外すためにdialog.currentFocus?.clearFocus()する。

大体の実装

レイアウトファイルはこんな感じ。
(今回のテーマに関係しないテキストやカラーの部分は省いています)

view_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="android.view.View"/>
        <variable
            name="viewModel"
            type="com.myapp.SampleViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <EditText
            android:id="@+id/storeEdit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4dp"
            android:layout_marginVertical="24dp"
            android:hint="@string/store"
            android:textSize="16sp"
            android:visibility="@{viewModel.willDelete ? View.INVISIBLE : View.VISIBLE}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/deleteButton"/>

        <TextView
            android:id="@+id/deleteStoreText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4dp"
            android:layout_marginVertical="24dp"
            android:textSize="16sp"
            android:textAlignment="center"
            android:visibility="@{viewModel.willDelete ? View.VISIBLE : View.GONE}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@+id/deleteButton"/>

        <Button
            android:id="@+id/enterButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4dp"
            android:text="@string/enter"
            android:visibility="@{viewModel.willDelete ? View.GONE : View.VISIBLE}"
            android:onClick="@{() -> viewModel.enterButtonTapped()}"
            app:layout_constraintBottom_toBottomOf="@id/deleteButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/deleteButton"
            app:layout_constraintTop_toTopOf="@+id/deleteButton"/>

        <Button
            android:id="@+id/cancelButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4dp"
            android:text="@string/cancel"
            android:visibility="@{viewModel.willDelete ? View.VISIBLE : View.GONE}"
            android:onClick="@{() -> viewModel.cancelButtonTapped()}"
            app:layout_constraintBottom_toBottomOf="@+id/deleteButton"
            app:layout_constraintEnd_toStartOf="@+id/deleteButton"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="@+id/deleteButton" />

        <Button
            android:id="@+id/deleteButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="4dp"
            android:layout_marginBottom="12dp"
            android:text="@string/delete"
            android:onClick="@{() -> viewModel.deleteButtonTapped()}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toStartOf="@+id/enterButton"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/cancelButton"
            app:layout_constraintTop_toBottomOf="@+id/storeEdit" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

ViewModelがこんな感じ。
こちらも関係ない部分は省いてます。

SampleViewModel.kt
class SampleViewModel : ViewModel() {
    private val _willDelete = MutableLiveData(false)
    val willDelete = Transformations.distinctUntilChanged(_willDelete)

    init {
        _willDelete.postValue(false)
    }

    fun enterButtonTapped() {
        // 確定時の処理
    }

    fun deleteButtonTapped() {
        if (_willDelete.value == true) {
            // 削除時の処理
        } else {
            _willDelete.postValue(true)
        }
    }

    fun cancelButtonTapped() {
        _willDelete.postValue(false)
    }
}

Fragmentではこのように利用。
ViewModelwillDeleteobserveしている部分のコメントのところでキーボードを非表示にしています。

SampleFragment.kt
class SampleFragment : Fragment() {
    private var binding: FragmentSampleBinding? = null
    private lateinit var sampleViewModel: SampleViewModel
    private lateinit var dialog: AlertDialog

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentSampleBinding.inflate(inflater, container, false).apply {
            lifecycleOwner = viewLifecycleOwner
        }
        sampleViewModel = SampleViewModel()
        return binding?.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
	
	// SampleViewModel側の記述からは省いたが、RecyclerViewのセルをタップでダイアログを表示
        sampleViewModel.cellTapEvent.observeBy(viewLifecycleOwner) {
            editDialog = AlertDialog.Builder(activity)
                .setView(
                    ViewDialogBinding.inflate(LayoutInflater.from(context)).apply {
                        lifecycleOwner = viewLifecycleOwner
                        viewModel = sampleViewModel
                    }.root
                )
                .show()
        }

        sampleViewModel.willDelete.observe(viewLifecycleOwner) {
	    // 1回目に削除が押された場合(willDeleteにtrueが流れる場合)にキーボードを非表示
            if (it) {
                val system = activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
                system?.hideSoftInputFromWindow(editDialog.currentFocus?.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
                editDialog.currentFocus?.clearFocus()
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        binding = null
    }
}

以上です。

最後に

最初、フォーカス外すの忘れていて全然キーボード消えなくてナニコレ...ってなっていました。

Discussion