본문 바로가기
기타/세미나

if kakao 2020 - Shared Elements Transition

by 헤콩 2020. 11. 18.
반응형
이 게시물에서는 if kakao 2020 1일차에 열린 Android 관련 세션 영상 중 Shared Elements Transition 이라는 세션을 정리해보려 합니다.
어디까지나 제가 세션영상을 보며 주관적으로 정리한 내용이기 때문에 부족한 점이 있을 수 있습니다.

이번 세션은 if kakao 2020에서 Huey님이 카카오 스토리에 적용한 화면 전환 애니메이션에 대해 설명합니다. 그리고 개발 중에 있었던 이슈 몇 가지를 설명합니다. 저는 평소 부족한 점부터 채워나가려다보니 공부해야 할 기능들이 많아 애니메이션 기능은 저에게 우선순위가 많이 밀려나 있는 상태였습니다. 하지만, 이번 기회로 애니메이션은 단순히 앱의 추가기능이 아니라, 사용자의 편의를 위해 앱의 완성도를 높이는 작업이 된다는 것을 알 수 있었습니다.

 

Shared Elements Transition

 

✏️ Shared Elements Transition이란??

  • 기본적으로 안드로이드에서의 화면 전환은 페이드 효과나 슬라이드 효과 형식의 간단한 애니메이션 전환을 사용하고 있습니다.
  • Shared Elements Transition을 사용하면 화면과 화면의 공유 요소를 통하여 하나의 액티비티에서 동작하는 듯한 효과를 주어 좀 더 자연스러운 사용자 경험을 줄 수 있습니다. 즉, 사용자에게 화면전환에 대한 거부감과 피로감을 줄여 앱의 완성도를 높일 수 있습니다.

 

✏️ 기본 동작 과정

1. Activity A의 View1을 클릭하여 Activity B를 시작할 때, 클릭된 View의 위치나 크기와 같은 형태 정보를 Activity B로 전달합니다.
2. Activity B에서 A로부터 전달된 정보를 View2에 전달하여 View1과 동일한 크기, 위치로 설정하여 animation을 시작합니다.
3. 이후 최종적인 View2의 원래 위치와 크기로 변해가는 animation을 보여줍니다.

 

 

✏️ 적용 방법

Android 5.0 (API 21) 롤리팝 이상부터 사용 가능합니다.

(1) Transition 효과를 적용합니다.
👉 Theme의 Style을 적용할 때, windowSharedElementEnterTransition과 windowSharedElementExitTransition 속성에 transition을 추가해주면 됩니다.
👉 아래 샘플코드에서 사용된 `transition/move`는 안드로이드에서 기본적으로 제공하고 있는 transition set입니다.
👉 예를 들어 설명하자면 동작방식에서 설명했던 Activity A의 효과가 windowSharedElementExitTransition이고, Activity B의 효과가 windowSharedElementEnterTransition입니다.

👉transition/move는 게시글 중간에 따로 설명이 있습니다.

 

<style name="AppTheme" parent="Theme.AppCompat.Light">
    <item name="android:windowSharedElementEnterTransition" tools:ignore="NewApi">
        @android:transition/move
    </item>
    <item name="android:windowSharedElementExitTransition" tools:ignore="NewApi">
        @android:transition/move
    </item>
</style>

 

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    window.run {
        sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
        sharedElementExitTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
    }
}

 

(2) 화면 전환에 사용되는 코드

👉Activity A에서 B를 띄우기 위해 Intent를 생성하고 startActivity를 하게 되는데, 이 때 makeSceneTransitionAnimation 메서드에 공유 요소의 view와 name을 파라미터로 넣어 액티비티 옵션을 생성하여 넣어줍니다.
👉 Activity B에서는 공유요소로 사용할 view에 setTransitionName으로 Activity A에서 사용한 것과 같은 name을 넣어주면, 코드작성은 완료됩니다.

 

/* Activity A */
val intent = Intent(context, ActivityB::class.java)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(context, view, name)

ActivityCompat.startActivity(context, intent, options.toBundle())


/* Activity B */
ViewCompat.setTransitionName(view, name)

 

(3) 여러 개의 뷰를 공유 요소로 사용할 때

 

/* Activity A */
val pair1: Pair<View, String> = Pair(view1, name1)
val pair2: Pair<View, String> = Pair(view2, name2)

val intent = Intent(context, ActivityB::class.java)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(context, pair1, pair2)

ActivityCompat.startActivity(context, intent, options.toBundle())


/* Activity B */
ViewCompat.setTransitionName(view1, name1)
ViewCompat.setTransitionName(view2, name2)

 

 

✏️ transition/move

👉 안드로이드에서 기본으로 제공하는 transition set입니다.

  • changeBounds : 위치와 크기를 변경하는 애니메이션
  • changeTransform : 배열과 회전을 변경하는 애니메이션
  • changeClipBounds : 지정된 영역을 변경하는 애니메이션
  • changeImageTransform : 이미지 크기 및 배율을 변경하는 애니메이션
<transitionSet xmls:android="https://schemas.android.com/apk/res/android">
    <changeBounds/>
    <changeTransform/>
    <changeClipBounds/>
    <changeImageTransform/>
</transitionSet>

 

또한, 이는 상황에 따라 아래와 같이 상용될 수 있습니다.

👉transitionOrdering은 transition set에 있는 transition들을 한 번에 동작시킬지, 순차적으로 동작시킬지를 결정합니다. (together, sequential)

 

<transitionSet 
    xmls:android="https://schemas.android.com/apk/res/android"
    android:transitionOrdering="together"
    android:startDelay="25"
    android:duration="500"
    android:interpolator="@android:interpolator/fast_out_slow_in"
    >
    <changeBounds/>
    <changeTransform/>
</transitionSet>

 


카카오 스토리 개발 과정에서 발생한 이슈 - 모서리의 라운드 이슈

👉이슈 해결 내용 : transition을 수행할 때, 이미지의 라운드 변화가 자연스럽지 못했던 이슈를 RoundTransition을 구현하여 해결

 

🖍RoundTransition 구현 방법

👉 Transition 클래스를 상속받아 구현

👉 Transition은 Capture와 Animator로 나뉘어집니다.

  • Capture
    • 시작과 종료 화면을 캡쳐하여 사용할 정보를 transition value에 저장하고, createAnimator가 호출되면 저장된 value가 파라미터로 전달되어 이를 애니메이터 생성에 사용하는 구조로 되어있습니다.
    • public void captureStartValues(TransitionValues transitionValues)
    • public void captureEndValues(TransitionValues transitionValues)
  • Animator
    • public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)

 

🖍 RoundTransition 클래스 다이어그램

👉Round Animation 효과를 주기 위해 RoundTransition을 구현한 클래스 다이어그램

 

1. Transition을 상속받은 RoundTransition 클래스에서 captureStartValues, captureEndValues, createAnimator를 구현하고, 동시에 RoundAnimation을 반환하는 RoundAnimation 인터페이스를 구현합니다.
2. createAnimator 메서드가 호출되면, Round 효과를 주는 Round ImageView에 radius 값을 변경시킬 수 있는 RoundAnimator를 생성하여 반환합니다.
3. 반환된 Animator는 그림에서 보이는 것처럼 안드로이드 플랫폼에서 화면 전환시에 동작시키게 됩니다.

 

/* RoundAnimator 인터페이스 */
interface RoundAnimator {
    fun animator(startRadius: Float, endRadius: Float, updateRadius: (radius: Float) -> Unit): Animator
}


/* animator 메서드 */
override fun animator(startRadius: Int, endRadius: Int, updateRadius: (radius: Float) -> Unit): Animator {
    val radiusAnimator: ValueAnimator = ValueAnimator.ofFloat(startRadius, endRadius)
    radiusAnimator.addUpdateListener { animator: ValueAnimator ->
        val animationRadius = animator.animatedValue as Float
        updateRadius(animationRadius)
    }
    return radiusAnimator
}


/* RoundTransition 클래스 */
class RoundTransition: Transition {
    override fun captureStartValues(transitionValues: TransitionValues?) {...}
    override fun captureEndValues(transitionValues: TransitionValues?) {...}
    override fun createAnimator(
        sceneRoot: ViewGroup?,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {
        val startRadius = startValues.values[RADIUS] as Float
        val endRadius = endValues.values[RADIUS] as Float
        val startView = startValues.view as RoundImageView

        return animator(startRadius, endRadius) { radius ->
            startView.setRadius(radius)
        }
    }
}

 

👉이렇게 만들어진 RoundTransition을 아래와 같이 위쪽에서 설명한 transitionSet에 추가하여 사용하면 Round 효과가 적용되게 됩니다.

 

<transitionSet xmls:android="https://schemas.android.com/apk/res/android">
    <changeBounds/>
    <changeTransform/>
    <transition
        class = "com.test.transition.RoundTransition"/>
</transitionSet>

 

카카오 스토리 개발 과정에서 발생한 이슈 - RxJava를 이용한 클릭 이벤트 제어

👉이슈 해결 내용 : 사용자의 여러번 클릭으로 인해 오류가 발생하는 것을 RxJava로 해결

👉observable에 throttleFirst를 설정하여 1초 간 가장 첫 번째로 들어오는 터치 이벤트만 수행하도록 구현

 

val observable: PublishSubject<String> = PublishSubject.create()
val onClickListener = View.OnClickListener {
    observable.onNext("click")
}

imageView1.setOnClickListener(onClickListener)
imageView2.setOnClickListener(onClickListener)
imageView3.setOnClickListener(onClickListener)

observable
    .throttleFirst(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe {
        call()
    }

 

 

 

 

 

 

 

 

if(kakao)2020

오늘도 카카오는 일상을 바꾸는 중

if.kakao.com

 

반응형

'기타 > 세미나' 카테고리의 다른 글

if kakao 2021 - UI 테스트 경험기  (0) 2021.11.18
if kakao 2020 - 에디터 프로젝트와 Text Span  (0) 2020.11.19
2020 NAVER TECH CONCERT 후기  (0) 2020.08.21

댓글