CMC 9기에서 진행한 세미나 발표 자료 입니다.
유튜브에서 보기: https://youtu.be/k_s3gaQWXos (발표에 미숙해서 많이 발음이 뭉개져요)
--- [대본] ---
1. 안녕하세요 저는 아썸한 키보드 애니메이션 이라는 주제로 발표할
2. 지성빈 입니다. 안드로이드 개발한지는 7년정도 됬으며, 우리 디스코드 서버에 있는 노래하는하리보 봇 개발자로도 참여했습니다.
3. 왜 이 주제로 발표를 하게 됐을까요? 기존 키보드는 adjustPan 사용시 상황에 따라서 최소 1번은 UI가 키보드에 가려지게 됩니다.
4. 또한 뷰 애니메이션 없이 그냥 키보드가 올라오다 보니 UI가 안 이쁘게 됩니다.
5. 이런 불편함을 구글도 알았는지 재작년 안드로이드 11 개발자 프리뷰 2 에서 IME 동기화 애니메이션에 대해 공개 되었습니다.
하지만 이런 엄청난 발표가 있었음에도 불구하고, 식햄도 처음 들어본다고 했던 기술이고, 아직 국내에는 관련하여 작성된 글이 없는거 같아 아쉬웠습니다. 따라서 이 주제로 발표를 하게 되었습니다.
6. 우리는 프리뷰에서 이 한 줄을 잘 보아야 합니다. 강조돼 있는 InsetAnimationListener를 통해 키보드 애니메이션을 구현할 수 있습니다. 여기서 말하는 인셋이 무엇일까요?
7. 인셋은 직사각형의 네 모서리의 크기를 갖고 있는 객체이며
8. API 29 에서 추가되었습니다. 모든 사람들이 최신 안드로이드 버전을 썻으면 좋곘지만, 현실은 그렇지 않으므로
9. AndroidX의 Compat 버전인 WindowInsetCompat을 사용해 API 21 부터 가능하게 하겠습니다.
10. 이 인셋에는 화면과 같은 총 4가지의 인셋이 있습니다. 말로만 들어서는 이해가 잘 되지 않으므로 예제를 보겠습니다.
11. 이 발표에서는 키보드 애니메이션을 구현하면서 시스템 바의 크기를 구하게 됩니다.
오른쪽에 안드로이드 메인 화면이 있습니다. 이 화면에 모든 모서리를 영역으로 표시해 두었습니다. 시스템 바의 인셋은 SystemWindowInset으로 구할 수 있으며 이렇게 구하게 되면
12. 이처럼 인셋에 시스탬 바에 속하는 상태바와 네비게이션 바의 높이가 구해지게 됩니다. 앞으로 이 SystemWindowInset을 활용하여 키보드 높이를 구해,
자연스러운 애니메이션을 구현할 것입니다. SystemWindowInset의 값인 WindowInset에 대해 자세히 알아봅시다.
13. WindowInset이란 윈도우 컨텐츠에 대한 인셋들의 집합을 뜻하며,
14. 이 인셋들의 변화를 WindowInsetAnimation Callback을 사용하여 한 틱 단위로 받아올 수 있습니다. 지금까지 인셋에 대해 간단히 알아보았습니다.
15. 이제 인셋을 활용하여 이 영상과 같이 키보드에 따라 뷰가 같이 올라가는 애니메이션을 구현해 보겠습니다.
16. 설명의 편의를 위해 아까 본 화면에서 채팅 목록을 conversationRecyclerview, 메시지 입력 레이아웃을 messageHolder, 그리고 이 전체를 root 라고 부르겠습니다.
17. 애니메이션 구현을 하기 전에, WindowInset 사용 설정을 해주어야 합니다. 이는 DecorFitSystemWindow를 false로 주어 할 수 있습니다. 왜 이래야 할까요?
WindowInset은 루트 뷰에서 자식 뷰로 게속 전파되는데 중간에 consume을 받게 되면 그 이후로 나오는 자식 뷰들은 WindowInset 이벤트를 받지 못하게 됩니다.
이 속성을 기본 값인 true로 주게 되면 안드로이드 기본값 인셋을 적용하면서 consume을 받게 됩니다.
따라서 WindowInset이 작동하지 않게 되고, 앱을 만들고 별도 설정 없이 실행했을 때 UI가 시스템 영역에 가려지지 않는 이유가 이것 때문 입니다. 따라서 setDecorFitSystemWindow를 false로 설정해 주겠습니다.
18. 이렇게 하면 당연히 UI가 시스템 영역과 겹치게 됩니다. 이 문제는 루트 뷰에 상태바와 네비게이션 바의 높이 만큼 패딩을 주어 해결할 수 있습니다. 또한 키보드가 올라올 때마다 키보드의 높이만큼도 같이 류트 뷰를 올려 주어야 합니다.
19. 이 두 가지 조건을 한 번에 충족시킬 수 있게 WindowInset 요소들을 구현하는 클래스를 만들어 보겠습니다. 재사용성을 위해 한 가지의 인셋 Type에만 국한되는게 아닌 모든 인셋 Type과 작동될 수 있게 만들겠습니다. 이는 or 비트연산자를 이용해 쉽게 구현할 수 있습니다.
20. OnApplyWindowInsetListener라는 새로운 인터페이스가 등장했습니다. 이는 뷰에 기본적으로 설정돼 있는 OnApplyWindowInset을 커스텀 하기 위한 인터페이스 입니다. 또한 WindowInsetCompat.CONSUMED 도 새로 나왔습니다.
21. 이 클래스 의외에 더 이상 WindowInset에 접근할 일이 없으므로 CONSUME을 리턴해 주었고,
22. OnApplyWindowInset은 내부의 정책에 따라 뷰가 인셋을 적용해야 할 때 호출됩니다.
23. 이처럼 비교적 간단하게 패딩은 설정해줄 수 있습니다. 이제 ViewCompat으로 루트 뷰에 적용해 줍니다. 성공적으로 모든 패딩이 적용된걸 확인할 수 있습니다. 하지만 애니메이션이 없어 매우 이상합니다. 따라서 이제 키보드에 따라 뷰가 같이 올라가는 애니메이션을 구현해 보도록 하겠습니다.
24. 키보드의 움직임을 받아오기 위해 WindowInsetAnimation Callback을 사용하여 클래스를 만들어 주었습니다. 애니메이션은 뷰 translation을 활용하여 쉽게 구현할 수 있습니다.
따라서 애니메이션이 끝나는 onEnd에는 translation을 초기화 해주었고, 애니메이션이 진행되는 onProgress에서 뷰에 translation 값을 주게 됩니다. onProgress 구현은 나중에 보도록 하고,
25. 새로 등장한 WindowInsetAnimation Callback과 Dispatch Mode에 대해 알아보도록 하겠습니다.
26. WindowInsetAnimation Callback에는 대표적으로 이와 같은 3가지의 메서드가 있습니다. 이 중에서 onProgress와 onEnd를 이 클래스에서 사용하게 됩니다.
27. 다음으로 Dispatch Mode는 이와 같은 2가지가 존재합니다. 이 클래스 이후로 더 이상 인셋을 전달해줄 뷰가 없기 때문에 MODE_STOP을 사용했습니다.
28. 이제 애니메이션의 onProgress 구현부를 보겠습니다. 간단하게 인셋 sub tract 메서드로 두 인셋을 뺀 차이만큼 뷰를 translation 해주는 로직을 작성해 주었습니다.
29. 이렇게 해서 완성된 클래스를 똑같이 ViewCompat으로 적용해 주면 애니메이션이 성공적으로 적용이 된 걸 확인할 수 있습니다.
하지만 messageHolder 애니매이션이 시작되기 전부터 이미 패딩이 키보드 높이 만큼 적용돼 있습니다. 따라서 애니메이션이 적용되기 전보다 더 못생겨 졌습니다. 이젠 애니메이션을 아름답게 만들어 보겠습니다.
30. 위 문제를 해결하기 위해선 키보드의 높이만큼 패딩을 바로 주는게 아닌, 애니메이션이 끝난 후에 패딩을 넣어줘야 합니다. 따라서 아까 만들어줬던 패딩 클래스를 수정해 주겠습니다. 애니메이션의 상태를 알기 위해 WindowInsetAnimation Callback를 추가로 상속해 주고,
키보드 인셋을 할지 미룰지 결정해줄 DeferredInset 이라는 변수를 만들어 주었습니다. 이 변수를 onPrepare에서 true를, onEnd에서 false를 주는 식으로 우선 구현해 줍니다.
이제 이 변수를 이용하여 인셋이 적용되는 OnApplyWindowInset 부분에서 처리해 주어야 합니다. 하지만 애니메이션이 끝난 이후에 OnApplyWindowInset은 다시 호출되지 않습니다. 따라서 onEnd 에서 직접 호출해 주어야 합니다.
31. 이는 뷰에 RequestApplyInset을 통해 해줄 수 있지만, 속도가 느려 플리커가 발생할 수 있습니다.
32. 따라서 다른 방법인 DispatchApplyWindowInset을 통해 수동으로 디스패치를 요청해야 합니다.
33. 그러기 위해서 맨 처음 실행되는 OnApplyWindowInset에서 뷰와 인셋을 onEnd에서 사용해 주기 위해 다른 변수에 저장해 주어야 합니다.
또한 OnApplyWindowInset에서 처리할 인셋을 DeferredInset 변수에 맞게 설정하는 로직도 같이 구현해야 합니다. 이렇게 모두 구현하게 되면 최종적으로 이와 같이 클래스가 완성됩니다.
34. 이제 다시 적용을 해 보면 Awesome한 키보드 애니메이션이 완성됩니다.
35. 이러한 과정이 너무 길어 저처럼 귀찮으신 분들을 위해 한 줄로 간단하게 사용할 수 있도록 라이브러리로 만들었습니다. 이 라이브러리에 대해서는 화면에 보이는 링크를 통해 알아보실 수 있습니다.
36. 또한 발표 가능한 시간이 적다보니 많은 내용이 생략되었습니다. 성빈 랜드라는 개인 개발 블로그에 총 3편에 걸쳐 관련 내용들을 정리해 두었으니 오른쪽 상단 링크에 방문해 주시면 더 자세한 정보를 보실 수 있습니다.
37. 이 발표가 여러분들의 안드로이드 개발에 도움이 됐으면 좋겠습니다. 지금까지 긴 발표 들어주셔서 감사합니다.