Android 공부/Android UI

Material Design - Motion - 1

0. 서론

 앱 개발을 하면서 꾸준하게 관심을 갖고 있던 부분이 있다. 그것은 바로, 레이아웃을 내가 원하는대로 자유롭게 그리는 것과 레이아웃을 내가 원하는대로 자유롭게 움직이는 것이다.

 

2019/03/25 - [Android 공부/Android UI] - Android Motionlayout 삽질기

2018/10/24 - [Android 공부/Android UI] - 안드로이드 ConstraintLayout 사용법

2019/04/10 - [Android 공부/Android UI] - [안드로이드 UI 공부] Android Shared-Element Transitions - 1

2019/04/12 - [Android 공부/Android UI] - [안드로이드 UI 공부] Android Shared-Element Transitions - 2

2019/02/05 - [나의 일기] - 부스트캠프 2주차 - 서울살이

2019/02/17 - [나의 일기] - 부스트캠프 3주차 - 서울살이

2019/02/25 - [나의 일기] - 부스트캠프 4주차 - 서울살이

 

 과거의 '나'는 레이아웃을 잘 움직이게 하기 위해서 꾸준하게 삽질을 했었고, 프로젝트에도 반영하고 싶을 정도로 도전적이었다.

 그리고, 현재 '나'는 '담다'에 Material Design을 도입해서, 출시할 수 있을 정도의 퀄리티를 내는 Motion을 구현하는 것이 목표이다.

 그래서, 안드로이드 머터리얼 디자인에서 Motion이라는 문서를 알게 되었고, 또 해당 기능을 만들기 위해서 다양한 시도를 하게 되었다.

 머터리얼 디자인의 문서에는 정말 잘 설명되어있으니, 꼭 참조했으면 좋겠다.

 

https://material.io/design/motion/the-motion-system.html

 

Material Design

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

 

 1. 결과

 1-0. 플로팅버튼을 누르고, 액티비티가 실행되는 모션

 1-1. 뷰를 눌렀을 때, 액티비티가 실행되는 모션

 

 두 가지의 애니메이션을 구현했고, 각종 삽질을 하고, 애니메이션을 만들어보니 감이 오기 시작했다.

 

1-0. 플로팅버튼을 누르고, 액티비티가 실행되는 모션,  1-1. 뷰를 눌렀을 때, 액티비티가 실행되는 모션

 

2. 이러한 점들이 어려웠어요.

 

 애니메이션을 만들면서 느끼는 점이 몇 가지 있고, 이러한 점들이 어렵다고 느꼈다.

 

❌ 안드로이드에는 다양한 애니메이션이 있는데, 언제 무엇을 사용할 지, 아는 것이 참 어렵다.

❌ 애니메이션은 사용하는 사람이 마음에 들어야한다.

❌ 애니메이션을 이상하게 만들어도 런타임이 잘 된다.

❌ 참고 자료들이 많지 않았다. 등등

  

 그렇기 때문에 애니메이션은 항상 만들고 싶지만, 우선순위에서 밀려나는 존재였고, 예쁜 Motion을 만든 개발자를 보면서 부러움을 많이 느꼈다.

 

3. Motion 사용 방법

 

 

3-0. 모션을 보내는 액티비티(StartActivity)

 

 모션을 사용하기 위해서 크게 주의해야할 점은 2가지이다.

 requestFeature() : 트랜지션을 사용하겠다는 것을 호출

 setExitSharedElementCallback() : 모션을 보내고, 돌아왔을 때 레이아웃이 정상적으로 작동하기 위함

 

override fun onCreate(savedInstanceState: Bundle?) {

	//super.onCreate의 호출시점에서 가장 먼저 해야하는 작업
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    	//해당 액티비티는 Transition을 사용한다.
		window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
		window.sharedElementsUseOverlay = false
        //돌아왔을 때, 액티비티의 레이아웃을 유지하기 위해서 사용하는 로직
		setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
	}
    
    super.onCreate(savedInstanceState)
}

 

3-1. 트랜지션을 사용하겠다고 하기(StartActivity)

 

 클릭 이벤트가 발생해서, 인텐트를 통해서 액티비티를 이동시켜준다면, startActivity 혹은 startActivityForResult를 사용할 것이다. 그 시점 전에 ActivityOptions.makeSceneTransitionAnimation 메소드를 만들어서 같이 보내주도록 하자. 

 해당 로직은 클릭한 view로부터 transition을 시작하는 것이고, 모션을 받는 액티비티에서는 'transitionName'을 기준으로 모션을 계속해서 진행해서 확장시킨다. 

 

fun goToActivityWithTransition(view: View, intent: Intent, transitionName: String) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val options = ActivityOptions.makeSceneTransitionAnimation(
                    this,
                    view,
                    transitionName  // The transition name to be matched in Activity B.
            )
            startActivityForResult(intent, MainUtils.SEND_DATE_HANDLE_NOTI, options.toBundle())
        } else {
            startActivityForResult(intent, MainUtils.SEND_DATE_HANDLE_NOTI)
        }
}

 

3-2. 모션을 받는 액티비티(DestinationActivity)

 

 모션을 받는 액티비티에서도 requestFeature()를 이용해서 트랜지션을을 사용하겠다는 것을 알리고,

 트랜지션을 만들어서 이전 액티비티로부터 트랜지션을 받고, 트랜지션을 진행시켜주면 된다.

 

override fun onCreate(savedInstanceState: Bundle?) {
	//이전과 동일한 작업
	//super.onCreate의 호출시점에서 가장 먼저 해야하는 작업
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    	//해당 액티비티는 Transition을 사용한다.
		window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
		window.sharedElementsUseOverlay = false
        //돌아왔을 때, 액티비티의 레이아웃을 유지하기 위해서 사용하는 로직
	}
    //transition 만들기
    makeTransition(binding.root, "TRANSITION_NAME")

    super.onCreate(savedInstanceState)
}

 

3-3. makeTransition() (DestinationActivity)

 

 액티비티를 기준으로, 모션이 만들어진다면, 액티비티가 만들어질 때 모션이 발생해서 enter가 된다고 하는 움직임,

모션이 종료되괴, 액티비티가 종료될 때, 모션이 발생하는 return이 되는 움직임인 2가지가 있다.

 

 해당 로직에서는 enterTransition, returnTransition이라고 할 것이다.

아래의 로직에서 enterTransition과 returnTransition은 추후에 transitionManager를 통해서 만들어줄 것이다.

 

 makeTransition() 메소드에서는 이름에 맞게 트랜지션을 만들어주는 메소드를 만들 것인데, 우리가 기대하는 애니메이션은 뷰로부터 액티비티로의 애니메이션을 기대할 것이다.

 그렇기 때문에 레이아웃의 루트뷰를 넘겨주고, 해당 루트뷰에 transtionName을 이전에 모션을 보내는 액티비티와 똑같은 transitionName을 지정해주면 된다.

 

 그리고, 앞에서 말한 enterTransition과 returnTransition을 어떤 뷰에 타겟을 할 것인지, 지정해주면 되고, setAllContainerColor를 이용해 트랜지션시, 컨테이너의 색을 지정해줄 수 있다.

 

 그리고 모션을 보내는 액티비티에서 한 것처럼, setEnterSharedElementCallback을 이용해서 트랜지션을 받을 때 뷰의 상태를 동기화해주자.

 트랜지션은 window에서 그려지게 되니, window.sharedElmentEnterTransition과 sharedElementReturnTransition을 지정해서, enter와 return으로 분리된다는 사실을 명시하자.

 

fun makeTransition(rootView: View, transitionName:String) {
        val enterTransition = transitionManager.setStartArcFadeInTransition()
        val returnTransition = transitionManager.setReturnArcFadeOutTransition()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            rootView.transitionName = transitionName

            enterTransition?.addTarget(rootView)
            enterTransition?.setAllContainerColors(MaterialColors.getColor(
                    rootView, R.attr.colorSurface
            ))
            returnTransition?.addTarget(rootView)
            returnTransition?.setAllContainerColors(MaterialColors.getColor(
                    rootView, R.attr.colorSurface
            ))

            setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
            setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
            window.sharedElementsUseOverlay = false
            window.sharedElementEnterTransition = enterTransition
            window.sharedElementReturnTransition = returnTransition
        }
}

 

3-4. TransitionManager

 모션을 이용하기 위해서는 트랜지션으로 MaterialContainerTransform을 전달하고 있다. 우리는 이전에 봤던 결과물을 만들기 위해서, 뷰로 부터 둥근 곡선을 그리는 MaterialContainerTransform을 만들게 된다.

 scrimColor, duration, interpolator, pathMotion, fadeMode를 지정해주고 있고, 더 자세한 내용은 문서에 나와있다.

 처음 해당 모션을 만들면서, 삽질을 했던 부분은 startTransition과 enterTransition으로 나누어서 fadeMode를 상반되게 지정해야했는데, 그렇게 하지 않아서 애니메이션이 이상하게 그려졌던 경험이 있으니, 참고하자.

 

interface DamdaTransitionManager {
    fun setStartArcFadeInTransition(): MaterialContainerTransform?
    fun setReturnArcFadeOutTransition(): MaterialContainerTransform?
}

class DamdaTransitionManagerImpl : DamdaTransitionManager {
    override fun setStartArcFadeInTransition(): MaterialContainerTransform? {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return MaterialContainerTransform().apply {
                scrimColor = Color.TRANSPARENT
                duration = TransitionConfigure.START_DURATION
                interpolator = FastOutSlowInInterpolator()
                pathMotion = MaterialArcMotion()
                fadeMode = MaterialContainerTransform.FADE_MODE_IN
            }
        }
        return null
    }

    override fun setReturnArcFadeOutTransition(): MaterialContainerTransform? {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return MaterialContainerTransform().apply {
                scrimColor = Color.TRANSPARENT
                duration = TransitionConfigure.END_DURATION
                interpolator = FastOutSlowInInterpolator()
                pathMotion = MaterialArcMotion()
                fadeMode = MaterialContainerTransform.FADE_MODE_OUT
            }
        }

        return null
    }
}

 

4. 삽질 모음기

버그 0.

 

 해당 버그는 테스트 단계를 지나고, BaseActivity에 모션 로직을 넣게 되니 발생하게 되었다. 모션이 되고 난 후에, 백그라운드가 검정색으로 변하는 현상인데, 해당 버그를 개선하기 위해서 단순히 백그라운드를 직접 흰색으로 지정해줬다.

 

 

버그 1.

 버그라기 보다는 transitionName을 지정해주지 않고, 트랜지션이 사용되었을 때, 해당 애니메이션이 실행된다. 혹시 만들면서, 트랜지션을 연결했는데, 왜 안될까라고 생각하면 transitionName을 확인해보자.

 

 

버그 2.

 돌아온 액티비티에서 모션을 진행했던 뷰가 깜빡이고, 돌아온 레이아웃이 유지가 되지 않았다.

해당 현상은 모션을 전송하는 액티비티에서 setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback()) 을

입력하지 않았을 때 발생했다.

 

 

 

5. 참고자료

 

https://material.io/design/motion/the-motion-system.html

 

Material Design

Build beautiful, usable products faster. Material Design is an adaptable system—backed by open-source code—that helps teams build high quality digital experiences.

material.io

https://proandroiddev.com/material-design-motion-for-android-396da62edb1c

 

Material Design Motion for Android

One of the main features in Material Design is the transition of elements and components to express relationships between them or outcomes…

proandroiddev.com