EditText에서 하드웨어 키보드 대응하기 with 가상 키보드, 커서
DoDoBest
·2024. 5. 2. 19:29
하드웨어 엔터 또는 가상 키보드의 완료 버튼에 해당하는 값
EditorInfo는 키 입력을 완료했을 때의 동작에 해당하는 값입니다. EditText의 imeOptions attribute에서 설정할 수 있습니다.
아무 것도 설정하지 않으면 기본 값인 actionDone(S23/Android14) 또는 actionNext(S9/Android10)로 설정됩니다.
완료 : EditorInfo.IME_ACTION_DONE, EditorInfo.IME_ACTION_SEARCH, EditorInfo.IME_ACTION_SEND, EditorInfo.IME_ACTION_NEXT
<EditText
...
android:imeOptions="actionSearch"
... />
actionDone은 다음과 같습니다.
actionSearch는 다음과 같습니다.
actionSend는 다음과 같습니다.
엔터에 해당하는 KeyEvent 값 : KeyEvent.KEYCODE_ENTER
Action이 아닌 KeyEvent에 해당하는 값도 있습니다. 하지만 KeyEvent class 주석에 KeyEvent가 발생한다는 보장이 없으므로 IME를 사용하라는 설명이 있습니다.
There is no guarantee that any key press on a soft keyboard will generate a key event: this is left to the IME's discretion, and in fact sending such events is discouraged. You should never rely on receiving KeyEvents for any key on a soft input method. In particular, the default software keyboard will never send any key event to any application targetting Jelly Bean or later, and will only send events for some presses of the delete and return keys to applications targetting Ice Cream Sandwich or earlier. Be aware that other software input methods may never send key events regardless of the version.
액션에 Listener 등록
EditText에 setOnEditorActionListener를 설정하고, action과 event에 따른 조건문을 설정해서 원하는 동작을 설정합니다.
binding.edtSearch.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
// 원하는 동작 수행하기
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
가상 키보드 내리기
아래 코드를 실행하면 EditText를 입력했을 때 올라오는 하단 가상 키보드를 내릴 수 있습니다.
private fun hideInput() {
getSystemService(requireContext(), InputMethodManager::class.java)?.hideSoftInputFromWindow(
binding.root.windowToken,
0
)
}
Focus 없애기
가상 키보드를 내리더라도 Focus를 없애지 않으면 EditText에 커서가 깜박이는 상태를 유지합니다.
Focus를 어떻게 없애는 것이 좋을까요?
관련 정보를 검색해보시면 Root Layout에 focus와 focusInTouchMode를 true로 설정해서, Root Layout에 focus를 설정하라는 방법이 많이 소개됩니다.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
tools:context=".presentation.search.SearchFragment">
그런데 위 방식은 치명적인 단점이 있습니다. ConstraintLayout이 focus를 갖게 되면 아래와 같이 화면이 어둡게 처리됩니다.
그래서 구글에서는 어떻게 처리하는지 확인하기 위해 플레이스토어를 확인해봤습니다.
아래 영상은 실기기에 블루투스 키보드를 연결해서 테스트했습니다.
감자 -> 엔터 -> 엔터 순으로 입력했습니다.
플레이 스토어에서는 IME_ACTION_SEARCH가 발생하면 뒤로가기 버튼에 focus를 준다고 추론해볼 수 있습니다.
어디로 focus를 옮길지는 개발 정책에 따라 다르겠지만, 저는 사용자가 엔터를 실수로 여러 번 연속해서 누르는 경우에 대응해야 한다고 생각했습니다.
그래서 포커스가 가더라도 화면에 음영처리가 가는 영향도 주지 않고, 기능도 하지 않는 dummy View를 만들었습니다.
<View
android:id="@+id/v_for_remove_focus"
android:layout_width="1dp"
android:layout_height="1dp"
android:focusable="true"
android:focusableInTouchMode="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
다음으로 setOnEditorActionListener 리스너에서 원하는 동작을 수행하기 전, dummy로 focus를 옮기기 위해 requestFocus를 호출했습니다.
private fun setListener() {
binding.edtSearch.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
hideInput()
vForRemoveFocus.requestFocus()
// 원하는 동작 수행하기
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
}
requestFocus는 호출한 View에게 Focus를 요청하는 함수입니다. 호출 대상 View가 다음 조건을 충족하지 않으면 Focus를 받을 수 없습니다.
- focusable이 true인가?
- focusableInTouchMode가 true인가?
- View가 크기를 가지는가?
- width와 height가 동시에 0이 아니다.(width != 0 || height != 0)
- visibility가 Visible이다(invisible, gone이 아니다).
dummy View가 focus를 가진 상태에서 엔터와 같은 입력을 받으면 회색 처리가 되겠지만, 1dp x 1dp로 크기를 설정했기 때문에 사용자에게는 보이지 않습니다.
결과
감자 입력 -> 엔터 -> 엔터 -> 엔터 순으로 테스트했습니다.
'학습' 카테고리의 다른 글
Room에 초기 값을 넣으려면 어떻게 해야할까? (0) | 2024.05.18 |
---|---|
Fragment에서 뒤로가기 동작 커스텀하기 (0) | 2024.05.04 |
FrameLayout에서 Fragment 올바르게 사용하기 (0) | 2024.05.02 |
Fragment에서 ViewPager2 + TabLayout 사용하기 (0) | 2024.04.24 |
직렬화란 무엇이고, 왜 필요하며, 어떻게 직렬화를 할 수 있을까? (0) | 2024.04.18 |