안드로이드 애니메이션 돌잔치 Part 1

MJ Studio
MJ Studio
Published in
13 min readDec 10, 2019

시중에 존재하는 많은 앱들은 결코 정적인 스크린으로 유저의 사용자 경험을 100% 충족시켜주지 않습니다. 매끄럽지만 절제된 애니메이션을 통해 유저들에게 더 나은 사용자 경험과 직관적인 앱의 기능을 나타내 줍니다.

안드로이드 프레임워크엔 다양한 종류의 애니메이션이 존재합니다. UI 구현 상황에 따라서 써야 하는 애니메이션이 있고, 동일한 효과를 낼 수 있지만 특정 상황에 더 알맞고 효율적인 애니메이션이 있습니다. 다양한 애니메이션 방식들 중에서 현 상황에 필요한 애니메이션을 선택하고 구현해내는 것은 개발자의 몫입니다.

이번 포스팅에서는 안드로이드의 다양한 애니메이션 구현 방식들을 훑어보고 그것들의 특징과 어떤 상황에 사용해야 하는지에 대해 실용성과 난이도 순으로 정렬을 하여 가볍게 알아보는 시간을 가질 것입니다. 제가 모든 내용을 상세히 설명할 순 없으므로, 각각 애니메이션들에 대해 부족하다고 생각되는 부분들은 제가 공부했었던 링크들을 첨부하겠습니다.

이는 이전 스터디 활동에서 만들어두었던 애니메이션들을 정리한 프로젝트의 연장선입니다. 고로 깃허브 저장소에 관련 코드가 있으니 천천히 훑어보시는 것도 추천드립니다.

Contents

Part1

  • XML 애니메이션
  • LayoutTransition
  • LayoutAnimation
  • Lottie

Part2

  • Animator
  • AnimatedVectorDrawable
  • SceneTransition
  • ConstraintSetTransition

Part3

  • Shared Element Transition
  • CoordinatorLayout
  • MotionLayout

1. XML 을 이용한 뷰 애니메이션

난이도 : ⭐️⭐️, 실용성 : ⭐️⭐️

간단한 Transformation 효과가 필요할 때

첫 애니메이션은 가장 간단하면서도 가벼운 효과를 주기에 유용한 XML을 이용한 애니메이션입니다. activity_main.xml 같은 레이아웃 파일을 LayoutInflater 를 이용해 객체화하듯이 res/anim 폴더에 저장된 애니메이션을 의미하는 xml 파일들은 AnimationUtils.loadAnimation() 함수를 이용해 객체화되어 사용됩니다. 코드로 살펴볼까요?

fadeInAnim = AnimationUtils.loadAnimation(requireContext(),R.anim.fade_in)
fadeOutAnim = AnimationUtils.loadAnimation(requireContext(),R.anim.fade_out)

Context 객체와 R.anim.만든에니메이션이름 을 인자로 전달해주면 Animation 객체를 얻어올 수 있습니다. 이를 뷰에 적용시키는 것은 더욱 간단합니다.

imagefade.startAnimation(fadeInAnim)

여기서 imagefade 는 뷰 객체입니다.

이제 xml에서 애니메이션을 어떻게 정의하는지 살펴볼까요?

위의 코드에서 불러왔던 R.anim.fade_in 으로 참조되던 xml 파일입니다. <set> 태그 안에 <alpha> 라는 태그가 들어갑니다.

  1. duration : 애니메이션이 지속될 시간 (밀리초)
  2. fillAfter : 애니메이션이 끝나고 transformation이 그대로 유지될 것인지(뒤에서 다시 설명)
  3. fromAlpha, toAlpha : 0~1 사이의 값, 투명도를 의미

XML 을 이용한 애니메이션은 두 가지 단점을 갖습니다.

  1. Transformation 에 관련된 기능만 존재한다.

저희가 xml 파일에서 < 를 눌러 자동완성 기능을 사용하면 alpha(투명도), scale(크기 배율), translate(좌표 변환), rotate(회전) 네 가지 애니메이션 만을 사용할 수 있음을 알 수 있습니다. 예를 들어, 저희가 색을 점진적으로 변화시키는 애니메이션을 만들고 싶을 때는 사용할 수가 없습니다.

2. 실제로 뷰의 속성이 변하는 것이 아니다.

첨부한 gif 이미지를 보시면 아시겠지만, scale은 계속 1이고, alpha도 계속 1입니다. 분명히 두배로 커지고 투명화되는데 말입니다. 이는 XML 를 이용한 애니메이션의 특징입니다. 실제로 뷰의 속성이 변하는 것이 아니고, 보이는 것만 애니메이션 처리가 됩니다. 제가 개인적으로 이 애니메이션을 선호하지 않는 이유 중 하나인데요, 만약 클릭이 가능한 뷰가 보이는 위치가 변화했는데 사실 위치가 그대로고 변한 위치에서 클릭이 안된다면 골때리는 상황이 아닐 수 없겠죠?

이전에 설명한 xml 파일에서 fillAfter라는 속성을 true로 설정해두지 않는다면, 애니메이션이 끝나고 다시 기존 뷰의 속성대로 보이는 것이 돌아가버립니다. fade_out 파일에서 fillAfter를 지워보겠습니다. default는 false값입니다.

Fade Out 애니메이션이 끝나면 투명한 채로 남아있는 것이 아니고 뷰의 속성인 alpha = 1 로 돌아와 버립니다. 하지만 fillAfter를 를 true로 설정하면 이를 막을 수 있습니다.

2. LayoutTransition

난이도 : ⭐️, 실용성 : ⭐️⭐️⭐️

자동으로 그럴듯한 애니메이션을 주고싶을 때

LayoutTransition이란 클래스는 ViewGroup 내에 존재합니다. 즉, FrameLayout, ConstraintLayout 같은 레이아웃 컨테이너들(ViewGroup을 상속하는 View들)이 갖고 있는 필드입니다. LayoutTransition을 설정하면 자식 뷰들이 레이아웃이 변화할 때, 자동으로 애니메이션을 실행시켜줍니다.

LayoutTransition엔 2가지 애니메이션 형식이 있습니다.

  1. APPEARING/DISAPPEARING : ViewView.VISIBLE 이나 View.GONE이 될 때
  2. CHANGE_APPEARING/CHANGE_DISAPPEARING : View가 뷰 계층에 추가되거나 삭제될 때

이 각각의 타입에 대해 애니메이션을 정의해두거나 딜레이를 설정할 수 있습니다.

코드를 살펴보겠습니다.

버튼이 눌릴 때 isVisible 속성을 변경해주어서 View.VISIBLE / View.GONE 을 설정해주었습니다. 그리고 첫번째 이미지 뷰는 0.5초 있다가 실행이 되게 했습니다.

button_visible onDebounceClick {
imageViewAppear.isVisible = true
imageViewAppear2.postDelayed({
imageViewAppear2.isVisible = true
},500L)

}
button_gone onDebounceClick {
imageViewAppear.isVisible = false
imageViewAppear2.postDelayed({
imageViewAppear2.isVisible = false
},500L)
}

저희가 해준건 View의 visibility 속성을 변경해준 것 밖에 없는 것이죠!

위는 화면의 루트 뷰의 LayoutTransition 을 가져와서 이것저것 애니메이터도 달아주고 딜레이도 설정하고 애니메이션 시간도 설정한 예제입니다. 당장 위에서 나오는 ObjectAnimator를 이해하지 못해도 괜찮습니다.

그런데 위의 코드만 봐도 충분히 복잡한데 난이도가 ⭐️인 이유는 무엇일까요? 그건 바로 레이아웃 XML 파일에서 컨테이너 뷰에 android:animateLayoutChanges=”true” 속성만 설정해주면, 자식 뷰들이 추가/삭제/숨김/보임 처리될 때마다 자동으로 그럴듯한 애니메이션이 실행되기 때문입니다. 실제로 볼까요?

<FrameLayout

...
android:animateLayoutChanges="true">
...

이번엔 코드상에서 LayoutTransition는 아예 건들지도 않았습니다.

코드 단 한줄로 레이아웃의 뷰들을 멋있게 애니메이션시킬 수 있습니다.

reference

3. LayoutAnimation

난이도 : ⭐️⭐️, 실용성 : ⭐️⭐️⭐️

ViewGroup에 순차적인 애니메이션을 주고싶을 때, Cool RecyclerView

세 번째는 LayoutAnimation입니다. LayoutTransition에서도 보셨겠지만, 앞에 Layout- 이 붙는 애니메이션들은 ViewGroup에 설정하는 애니메이션으로, 자식 뷰들에게 일정한 애니메이션을 실행시켜줄 수 있습니다. LayoutAnimation은 View의 visibility를 변화시키지 않아도, 우리가 원하는 시점에 자식 뷰들에게 애니메이션을 순차적으로 실행시켜줄 수 있습니다. 특히, RecyclerView에 설정을 하면 위에서 보이듯 간단하지만 화려한 이펙트를 넣어줄 수 있습니다.

사용법은 그리 어렵지 않습니다.

<androidx.recyclerview.widget.RecyclerView
...
android:layoutAnimation="@anim/layout_animation_fall_down"
/>

android:layoutAnimation 속성에 애니메이션 xml 파일만 넣어주면 됩니다. 이제 저 xml 파일이 무엇인지 알아볼 것입니다. 우선, 저희는 두 개의 xml 애니메이션 파일이 필요합니다.

  1. 아이템 하나하나가 어떻게 애니메이션이 될 것인지 결정해주는 애니메이션 xml
  2. 전체적인 LayoutAnimation이 어떻게 실행이 될지 결정하는 LayoutAnimationController 객체와 상응되는 애니메이션 xml

1번은 저희가 XML을 이용한 애니메이션을 구현할 때 사용했던 애니메이션 파일과 동일합니다. 저는 왼쪽 위에서 등장하며, 투명도가 0에서 1이 되고, 크기가 커졌다 원래대로 되는 애니메이션을 넣어주었기 때문에 다음과 같이 정의했습니다.

<set> 은 자바에서 AnimationSet 객체와 상응되는 태그인데, 이 안에 여러가지 애니메이션을 한번에 넣는것도 가능합니다.

2번은 다음과 같이 정의되어 있습니다.

속성 이름만 보시고도 어떤 역할을 하는 속성들인지 감이 오시나요? android:animation은 방금 1번의 애니메이션을 지정해주고, android:delay는 1번 애니메이션 파일에서 duration을 1초로 지정해주었다면, 20%으로 설정하면 0.2초씩 자식 뷰들이 애니메이션이 딜레이 되면서 순차적으로 진행이 된다는 뜻입니다. animationOrdernormal, reverse, random이 있는데 레이아웃에 있는 자식 뷰들의 순서에 따라 어떻게 진행이 될 것인지를 결정합니다. reverse를 설정한다면 아래에서 위로 애니메이션이 진행되는 것을 볼 수 있습니다. 그리고 모든 것을 <layoutAnimation> 태그로 감싸줍니다.

xml 레이아웃 파일에서 android:layoutAnimation 으로 설정해줄 수도 있지만, 코드상에서 2번 파일을 AnimationUtils.loadLayoutAnimation() 으로 불러와 설정해줄 수 있습니다.

flowContainer.layoutAnimation = AnimationUtils.loadLayoutAnimation(requireContext(),R.anim.layout_animation_fall_down)

애니메이션은 처음에 레이아웃이 될 때 한번 실행되고, 그 이후에 명시적으로 실행을 시켜주고 싶다면,

flowContainer.startLayoutAnimation()

와 같이 startLayoutAnimation()을 호출해주기만 하면 됩니다.

reference

4. Lottie

난이도 : ⭐️, 실용성 : ⭐️⭐️⭐️⭐️

스플래시 화면, 로딩 아이콘, 그림과도 같은 애니메이션

https://lottiefiles.com/11555-night-vs-day-slider?lang=en#

Lottie는 에어비앤비사에서 만든 애니메이션 라이브러리입니다. 디자이너가 에프터이펙트 툴을 통해 애니메이션을 json파일로 추출해주면, Android/iOS/Web 용으로 나온 라이브러리들을 이용해서 편하게 그 애니메이션을 실행시켜줄 수 있습니다. json 파일이기 때문에 gif를 이용하는 것보다 부드럽고 효율적인 애니메이션이 가능합니다. 디자이너에게 json 파일을 요청해서 사용하면 됩니다. 하지만 에프터이펙트에서 만드는건 어려우니 너무 보채지는 마세요.

사용법은 간단합니다. 우선 모듈 레벨의 build.gradle에 라이브러리 의존성을 추가합니다.

implementation 'com.airbnb.android:lottie:3.2.2'

그리고 main/assets 폴더 안에 다운로드 받은 json 파일을 넣어줍니다.

그리고 우리가 애니메이션을 넣고 싶은 부분을 LottieAnimationView 뷰를 넣어주고 속성을 정의해주면 됩니다.

<com.airbnb.lottie.LottieAnimationView
...
app:lottie_autoPlay="true"
app:lottie_fileName="daynight.json"
app:lottie_loop="true"
app:lottie_repeatMode="reverse"
/>

많은 속성들이 있으니 직접 알아보는 것을 추천드립니다.

중요한 것은 app:lottie_fileName 속성에 다운로드 받은 json 파일의 이름을 넣어주어야 한다는 것입니다.

Part1에서는 쉽지만 실용적인 안드로이드의 애니메이션들에 대한 내용을 담아보았습니다. Part2부터는 좀 더 Code-base 하고 강력한 애니메이션들을 알아보겠습니다.

--

--