🐥
Androidのダイアログで表示しているキーボードを動的に消す
概要
自作のビューを表示したAlertDialog
にEditText
を置いていて、それを編集中に表示されているキーボードを何らかのアクションをトリガーに非表示にする方法で少しハマったのでメモ。
上の画像ではダイアログにあるEditTextをタップでキーボードが出現して、削除をタップでキーボードが消えて削除確認メッセージが表示される。
このダイアログの利用シーンとしては、一度登録したマスターデータなどを編集、削除するようなケースを想定。
結論
InputMethodManager
のhideSoftInputFromWindow
メソッドに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
ではこのように利用。
ViewModel
のwillDelete
をobserve
している部分のコメントのところでキーボードを非表示にしています。
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