안드로이드 : 이웃 Fragment 간 애니메이션 구현

Hank
mojitok
Published in
5 min readSep 22, 2022

그림은 대개 글자보다 빠른 전달력을 가집니다. 우리가 많이 쓰는 이모지, 이모티콘 또한 그렇습니다. mojitok은 이모티콘 제공 솔루션이며 여러분이 만든 채팅과 같은 메시지 애플리케이션에 비주얼화된 소통방법을 제공합니다.

단순히 메시지 화면에 그림만 노출하는 것은 재미가 없습니다. 애니메이션은 사용자 인터페이스에 있어서 흥미를 일으키는 활력소가 됩니다. 만일 우리가 누른 이모티콘이 직접 움직여서 메시지 창에 위치할 수 있다면 재미있을 겁니다.

안드로이드에서 구현된 이 샘플은 메시지 창과 사용할 이모티콘을 노출하는 패널을 한 화면에 노출하고 있습니다. 메시지 창과 이모티콘 패널은 서로 다른 Fragment로 구성되어 있습니다. Fragment는 서로의 UI 컴포넌트 공유를 허락하지 않습니다. 이 때 우리는 Fragment 간의 애니메이션을 어떻게 구현할 수 있을까요?

여기에 보다 단순화한 샘플이 있습니다. 화면은 서로 다른 Fragment로 양분되어 있고 각기 자신의 UI 컴포넌트를 하나씩 가지고 있습니다. 애니메이션을 동작시키면 UI 컴포넌트는 동일한 좌표에 위치하고 원래의 자리로 돌아갑니다.

이 샘플은 마치 UI 컴포넌트가 프래그먼트 간에 전달되는 듯한 눈속임을 설명합니다. 실제로 Fragment 간 UI 컴포넌트에 대한 공유, 전달은 이루어지지 않습니다. 단지 그 위치를 알 뿐입니다.

그럼 Fragment 간에 UI 컴포넌트가 미끄러지듯 움직이는 애니메이션은 어떻게 설명할 수 있을까요? 아마 짐작하였다시피 이는 Activity를 활용하여 그려냅니다. Activity는 화면 전체를 그리고 있고 두 Fragment의 부모입니다.

각각의 View에는 드로잉을 할 수 있는 여분의 ViewOverlay 레이어가 있습니다. View를 상속받은 ViewGroup은 ViewGroupOverlay를 제공하며 재미있게도 여기에는 View를 올려볼 수도 있습니다.

그렇다면 Activity의 최상위 View의 ViewGroupOverlay를 활용하면 Fragment 간 UI 컴포넌트가 이동하는 듯한 애니메이션을 그려볼 수 있지 않을까요? 최종적으로 해결책은 이 아이디어를 활용합니다.

private fun animate() {
val overlay = binding.root.overlay
val topView = topFragment.getViewBinding().sampleTopTextView
val bottomView = bottomFragment.getViewBinding().sampleBottomTextView

val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)

val bufferIntArray = IntArray(2)
val (topViewWindowPositionX, topViewWindowPositionY) =
topView.getLocationInWindow(bufferIntArray).run { bufferIntArray }

val (bottomViewWindowPositionX, bottomViewWindowPositionY) =
bottomView.getLocationInWindow(bufferIntArray).run { bufferIntArray }

val topViewToStartMovementX = bottomViewWindowPositionX - topViewWindowPositionX
val topViewToStartMovementY = bottomViewWindowPositionY - topViewWindowPositionY
topView.translationX = topViewToStartMovementX.toFloat()
topView.translationY = topViewToStartMovementY.toFloat()

overlay.add(topView)
topView
.animate()
.translationXBy(-topViewToStartMovementX.toFloat())
.translationYBy(-topViewToStartMovementY.toFloat())
.setInterpolator(AccelerateDecelerateInterpolator())
.setDuration(800L)
.withEndAction {
overlay.remove(topView)
topFragment.getViewBinding().root.addView(topView)
}
}

감사합니다.

--

--