Android 앱 하단에 창으로 배치되는 소프트웨어 키보드가 있습니다. 그런데 이 키보드가 올라오고, 내려가는 동작을 감지해야 할 때가 있지 않으셨나요? 저는 키보드가 올라오면 하단 버튼이 사라져야 하는 요청이 있었습니다. 😄
그런데 소프트웨어 키보드는 시스템 UI에 속하기 때문에 직접 접근할 수가 없더라고요. 대신 키보드가 올라오면서 부모 뷰의 높이를 변경되는 원리를 이용하는 방법을 사용해야 했습니다. 어려운 코드는 아니지만, 정이 안 가는 코드였지만 말이죠
그러다 최근 시스템 UI를 제어할 수 있는 WindowInsetsCompat 클래스를 사용하는 방법을 알게 되어 소개하려고 합니다

WindowInsets
StatusBar, 소프트웨어 키보드처럼 시스템 UI의 크기와 위치 정보를 제공해 주는 클래스입니다.
이 클래스를 사용하기 위해선 사전 작업이 조금 필요합니다.
사전 작업
https://developer.android.com/develop/ui/compose/layouts/insets?hl=ko#insets-setup
앱에서 콘텐츠를 더 넓은 화면에 표시하고 Compose에서 창 인셋을 처리합니다. | Jetpack Compose |
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱에서 콘텐츠를 더 넓은 화면에 표시하고 Compose에서 창 인셋을 처리합니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를
developer.android.com
넓은 화면으로 확장
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
Activity onCreate 함수에서 enableEdgeToEdge 함수를 사용해 더 넓은 화면으로 확장해야 합니다. 이러면 시스템 UI의 inset를 모두 수신받아 직접 처리할 수 있습니다.
windowSoftInputMode를 adjustResize로 적용
<activity
...
android:windowSoftInputMode="adjustResize"
windowSoftInputMode 속성은 소프트웨어 키보드가 표시될 때 UI 응답 방식을 설정합니다. adjustResize는 소프트 웨어 키보드 높이만큼, 루트 뷰 높이가 Resize 되는 방식입니다.
이 설정을 사용해야 소프트웨어 키보드의 크기를 inset으로 수신할 수 있다고 합니다.
사전 작업은 이것으로 끝입니다. 간단하죠? 💪
setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
if (isImeVisible) {
Toast.makeText(view.context, "키보드 show", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(view.context, "키보드 hide", Toast.LENGTH_SHORT).show()
}
WindowInsetsCompat.CONSUMED
}
setOnApplyWindowInsetsListener 함수를 사용하면 뷰로 전달되는 insets를 확인하고, 직접 처리할 수 있습니다.
그래서 결론만 보면 WindowIsetsCompat.isVisible 함수를 이용하면 쉽게 키보드 Show/Hide를 확인할 수 있습니다.
Compose Sample Code
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun TextFieldSample() {
var text by remember { mutableStateOf("") }
val isImeVisible = WindowInsets.isImeVisible
val context = LocalContext.current
LaunchedEffect(isImeVisible) {
if (isImeVisible) {
Toast.makeText(context, "키보드 show", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "키보드 hide", Toast.LENGTH_SHORT).show()
}
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
TextField(
value = text,
onValueChange = { text = it}
)
}
}
compose 방식에서도 비슷한 기능이 있습니다. androidx.compose.foundation.layout에서 제공하는 WindowInsets 객체에도 비슷한 isImeVisible 값을 이용하면 됩니다.
@ExperimentalLayoutApi
val WindowInsets.Companion.isImeVisible: Boolean
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@ExperimentalLayoutApi
@Composable
@NonRestartableComposable
get() = WindowInsetsHolder.current().ime.isVisible
@Stable
internal class AndroidWindowInsets(
internal val type: Int,
private val name: String
) : WindowInsets {
internal var insets by mutableStateOf(AndroidXInsets.NONE)
/**
* Returns whether the insets are visible, irrespective of whether or not they
* intersect with the Window.
*/
var isVisible by mutableStateOf(true)
private set
@OptIn(ExperimentalLayoutApi::class)
internal fun update(windowInsetsCompat: WindowInsetsCompat, typeMask: Int) {
if (typeMask == 0 || typeMask and type != 0) {
insets = windowInsetsCompat.getInsets(type)
isVisible = windowInsetsCompat.isVisible(type)
}
}
...
}
각 시스템 UI의 Inset 정보는 AndroidWindowInsets 객체로 변환되며, 내부적으로 isVisible 상태를 들고 있습니다.
inset 정보가 변경되면 update 함수가 호출이 되고, isVisble 값이 계산되는 점을 확인할 수 있었습니다.
'Android > Common' 카테고리의 다른 글
[Android] 음성 인식 기능 추가하는 방법 정리 (0) | 2020.03.26 |
---|---|
[Android] AAC - View Binding (0) | 2020.03.12 |
[Android] RecyclerView의 최상단 최하단 감지하기 (0) | 2020.02.10 |
[Android] Retrofit 라이브러리 알아보기 (0) | 2020.02.09 |
[Android] Settings.Panel (0) | 2019.12.13 |
Android 앱 하단에 창으로 배치되는 소프트웨어 키보드가 있습니다. 그런데 이 키보드가 올라오고, 내려가는 동작을 감지해야 할 때가 있지 않으셨나요? 저는 키보드가 올라오면 하단 버튼이 사라져야 하는 요청이 있었습니다. 😄
그런데 소프트웨어 키보드는 시스템 UI에 속하기 때문에 직접 접근할 수가 없더라고요. 대신 키보드가 올라오면서 부모 뷰의 높이를 변경되는 원리를 이용하는 방법을 사용해야 했습니다. 어려운 코드는 아니지만, 정이 안 가는 코드였지만 말이죠
그러다 최근 시스템 UI를 제어할 수 있는 WindowInsetsCompat 클래스를 사용하는 방법을 알게 되어 소개하려고 합니다

WindowInsets
StatusBar, 소프트웨어 키보드처럼 시스템 UI의 크기와 위치 정보를 제공해 주는 클래스입니다.
이 클래스를 사용하기 위해선 사전 작업이 조금 필요합니다.
사전 작업
https://developer.android.com/develop/ui/compose/layouts/insets?hl=ko#insets-setup
앱에서 콘텐츠를 더 넓은 화면에 표시하고 Compose에서 창 인셋을 처리합니다. | Jetpack Compose |
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱에서 콘텐츠를 더 넓은 화면에 표시하고 Compose에서 창 인셋을 처리합니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를
developer.android.com
넓은 화면으로 확장
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
Activity onCreate 함수에서 enableEdgeToEdge 함수를 사용해 더 넓은 화면으로 확장해야 합니다. 이러면 시스템 UI의 inset를 모두 수신받아 직접 처리할 수 있습니다.
windowSoftInputMode를 adjustResize로 적용
<activity
...
android:windowSoftInputMode="adjustResize"
windowSoftInputMode 속성은 소프트웨어 키보드가 표시될 때 UI 응답 방식을 설정합니다. adjustResize는 소프트 웨어 키보드 높이만큼, 루트 뷰 높이가 Resize 되는 방식입니다.
이 설정을 사용해야 소프트웨어 키보드의 크기를 inset으로 수신할 수 있다고 합니다.
사전 작업은 이것으로 끝입니다. 간단하죠? 💪
setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
val isImeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
if (isImeVisible) {
Toast.makeText(view.context, "키보드 show", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(view.context, "키보드 hide", Toast.LENGTH_SHORT).show()
}
WindowInsetsCompat.CONSUMED
}
setOnApplyWindowInsetsListener 함수를 사용하면 뷰로 전달되는 insets를 확인하고, 직접 처리할 수 있습니다.
그래서 결론만 보면 WindowIsetsCompat.isVisible 함수를 이용하면 쉽게 키보드 Show/Hide를 확인할 수 있습니다.
Compose Sample Code
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun TextFieldSample() {
var text by remember { mutableStateOf("") }
val isImeVisible = WindowInsets.isImeVisible
val context = LocalContext.current
LaunchedEffect(isImeVisible) {
if (isImeVisible) {
Toast.makeText(context, "키보드 show", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "키보드 hide", Toast.LENGTH_SHORT).show()
}
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
TextField(
value = text,
onValueChange = { text = it}
)
}
}
compose 방식에서도 비슷한 기능이 있습니다. androidx.compose.foundation.layout에서 제공하는 WindowInsets 객체에도 비슷한 isImeVisible 값을 이용하면 됩니다.
@ExperimentalLayoutApi
val WindowInsets.Companion.isImeVisible: Boolean
@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
@ExperimentalLayoutApi
@Composable
@NonRestartableComposable
get() = WindowInsetsHolder.current().ime.isVisible
@Stable
internal class AndroidWindowInsets(
internal val type: Int,
private val name: String
) : WindowInsets {
internal var insets by mutableStateOf(AndroidXInsets.NONE)
/**
* Returns whether the insets are visible, irrespective of whether or not they
* intersect with the Window.
*/
var isVisible by mutableStateOf(true)
private set
@OptIn(ExperimentalLayoutApi::class)
internal fun update(windowInsetsCompat: WindowInsetsCompat, typeMask: Int) {
if (typeMask == 0 || typeMask and type != 0) {
insets = windowInsetsCompat.getInsets(type)
isVisible = windowInsetsCompat.isVisible(type)
}
}
...
}
각 시스템 UI의 Inset 정보는 AndroidWindowInsets 객체로 변환되며, 내부적으로 isVisible 상태를 들고 있습니다.
inset 정보가 변경되면 update 함수가 호출이 되고, isVisble 값이 계산되는 점을 확인할 수 있었습니다.
'Android > Common' 카테고리의 다른 글
[Android] 음성 인식 기능 추가하는 방법 정리 (0) | 2020.03.26 |
---|---|
[Android] AAC - View Binding (0) | 2020.03.12 |
[Android] RecyclerView의 최상단 최하단 감지하기 (0) | 2020.02.10 |
[Android] Retrofit 라이브러리 알아보기 (0) | 2020.02.09 |
[Android] Settings.Panel (0) | 2019.12.13 |