WindowInsetsCompat
를 사용하면 앱이 시스템 표시줄과 상호작용하는 방식과 유사하게 터치 키보드 (IME라고도 함)를 쿼리하고 제어할 수 있습니다. 또한 앱은 WindowInsetsAnimationCompat
를 사용하여 소프트웨어 키보드가 열리거나 닫힐 때 원활한 전환을 만들 수 있습니다.
기본 요건
소프트웨어 키보드의 컨트롤 및 애니메이션을 설정하기 전에 더 넓은 화면을 표시하도록 앱을 구성하세요. 이렇게 하면 시스템 표시줄, 터치 키보드와 같은 시스템 창 인셋을 처리할 수 있습니다.
키보드 소프트웨어 공개 상태 확인
WindowInsets
를 사용하여 소프트웨어 키보드 공개 상태를 확인합니다.
Kotlin
val insets = ViewCompat.getRootWindowInsets(view) ?: return val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
Java
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view); boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
또는 ViewCompat.setOnApplyWindowInsetsListener
를 사용하여 소프트웨어 키보드 공개 상태의 변경사항을 관찰할 수 있습니다.
Kotlin
ViewCompat.setOnApplyWindowInsetsListener(view) { _, insets -> val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom insets }
Java
ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> { boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); int imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom; return insets; });
소프트웨어 키보드와 애니메이션 동기화
다음 예와 같이 사용자가 텍스트 입력란을 탭하면 키보드가 화면 하단에서 제자리에 슬라이드됩니다.
그림 2의 '비동기식'으로 라벨이 지정된 예는 Android 10(API 수준 29)의 기본 동작을 보여줍니다. 여기서 앱의 텍스트 필드와 콘텐츠가 키보드의 애니메이션과 동기화되는 대신 제자리에 맞춰집니다(시각적으로 부자연스럽게 보일 수 있는 동작).
Android 11 (API 수준 30) 이상에서는
WindowInsetsAnimationCompat
를 사용하여 키보드가 화면 하단에서 위아래로 슬라이드되는 상태로 앱 전환을 동기화할 수 있습니다. 그림 2의 '동기화됨'으로 라벨이 지정된 예에서와 같이 이 작업이 더 원활하게 표시됩니다.
키보드 애니메이션과 동기화될 뷰로 WindowInsetsAnimationCompat.Callback
를 구성합니다.
Kotlin
ViewCompat.setWindowInsetsAnimationCallback( view, object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) { // Override methods. } )
Java
ViewCompat.setWindowInsetsAnimationCallback( view, new WindowInsetsAnimationCompat.Callback( WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP ) { // Override methods. });
WindowInsetsAnimationCompat.Callback
에는 재정의할 여러 메서드, 즉 onPrepare()
, onStart()
, onProgress()
, onEnd()
가 있습니다.
레이아웃이 변경되기 전에 onPrepare()
를 호출하는 것으로 시작합니다.
onPrepare
는 인셋 애니메이션이 시작될 때, 그리고 애니메이션으로 인해 뷰가 다시 배치되기 전에 호출됩니다. 이를 사용하여 시작 상태(여기서는 뷰의 하단 좌표)를 저장할 수 있습니다.
![루트 뷰의 시작 상태 하단 좌표를 보여주는 이미지입니다.](https://developer.android.google.cn/static/images/guide/navigation/software-keyboard-3.png?authuser=7&hl=ko)
onPrepare()
를 사용하여 시작 상태를 기록합니다.
다음 스니펫은 onPrepare
에 대한 샘플 호출을 보여줍니다.
Kotlin
var startBottom = 0f override fun onPrepare( animation: WindowInsetsAnimationCompat ) { startBottom = view.bottom.toFloat() }
Java
float startBottom; @Override public void onPrepare( @NonNull WindowInsetsAnimationCompat animation ) { startBottom = view.getBottom(); }
onStart
는 인셋 애니메이션이 시작되면 호출됩니다. 이 메서드를 사용하여 모든 뷰 속성을 레이아웃 변경의 최종 상태로 설정할 수 있습니다. 뷰로 설정된 OnApplyWindowInsetsListener
콜백이 있다면 이 시점에 이미 호출되어 있습니다. 이때 뷰 속성의 최종 상태를 저장하는 것이 좋습니다.
![뷰의 종료 상태 하단 좌표를 보여주는 이미지](https://developer.android.google.cn/static/images/guide/navigation/software-keyboard-4.png?authuser=7&hl=ko)
onStart()
를 사용하여 최종 상태를 기록합니다.
다음 스니펫은 onStart
에 대한 샘플 호출을 보여줍니다.
Kotlin
var endBottom = 0f override fun onStart( animation: WindowInsetsAnimationCompat, bounds: WindowInsetsAnimationCompat.BoundsCompat ): WindowInsetsAnimationCompat.BoundsCompat { // Record the position of the view after the IME transition. endBottom = view.bottom.toFloat() return bounds }
Java
float endBottom; @NonNull @Override public WindowInsetsAnimationCompat.BoundsCompat onStart( @NonNull WindowInsetsAnimationCompat animation, @NonNull WindowInsetsAnimationCompat.BoundsCompat bounds ) { endBottom = view.getBottom(); return bounds; }
애니메이션 실행 중에 인셋이 변경될 때 onProgress
가 호출되므로 키보드 애니메이션 중에 인셋을 재정의하고 모든 프레임에서 알림을 받을 수 있습니다. 뷰가 키보드와 동기화되어 애니메이션 처리되도록 뷰 속성을 업데이트합니다.
이 시점에서 모든 레이아웃 변경이 완료됩니다. 예를 들어 View.translationY
를 사용하여 뷰를 이동하면 이 메서드가 호출될 때마다 값이 점차 감소하여 결국 원래 레이아웃 위치까지 0
에 도달합니다.
onProgress()
를 사용하여 애니메이션을 동기화합니다.
다음 스니펫은 onProgress
에 대한 샘플 호출을 보여줍니다.
Kotlin
override fun onProgress( insets: WindowInsetsCompat, runningAnimations: MutableList<WindowInsetsAnimationCompat> ): WindowInsetsCompat { // Find an IME animation. val imeAnimation = runningAnimations.find { it.typeMask and WindowInsetsCompat.Type.ime() != 0 } ?: return insets // Offset the view based on the interpolated fraction of the IME animation. view.translationY = (startBottom - endBottom) * (1 - imeAnimation.interpolatedFraction) return insets }
Java
@NonNull @Override public WindowInsetsCompat onProgress( @NonNull WindowInsetsCompat insets, @NonNull List<WindowInsetsAnimationCompat> runningAnimations ) { // Find an IME animation. WindowInsetsAnimationCompat imeAnimation = null; for (WindowInsetsAnimationCompat animation : runningAnimations) { if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) { imeAnimation = animation; break; } } if (imeAnimation != null) { // Offset the view based on the interpolated fraction of the IME animation. view.setTranslationY((startBottom - endBottom) * (1 - imeAnimation.getInterpolatedFraction())); } return insets; }
필요한 경우 onEnd
를 재정의할 수 있습니다. 이 메서드는 애니메이션이 끝난 후에
호출됩니다. 이 단계에서 임시 변경사항을 정리하는 것이 좋습니다.
추가 리소스
- GitHub의 WindowInsetsAnimation