모션 레이아웃을 이용하면
앱을 구현할때 애니메이션 효과를 주는 등 다양하게 표현이 가능하다
이번에 만든 인트로 화면인데
스와이프하면서 통화하는걸 표현해봤다.
스와이프, 클릭등 액션 없이 그냥 재생만 되는 애니메이션이라 간단 할 줄 알았는데
어떻게 만드는지 자료를 찾기가 좀 힘들었다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/intro_motion"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:layoutDescription="@xml/activity_intro_scene">
<ImageView
android:id="@+id/iv_intro_skyline"
android:layout_width="match_parent"
...
우선 레이아웃을 만들때 모션레이아웃으로 전체를 감싸준다.
모션 레이아웃은 constraint Layout을 상속받기 때문에 그와 유사하게
뷰의 위치를 잡아주면 된다.
그렇게 만들어주면 xml에 scene파일이 하나 생기게 되는데 여기서 애니메이션을 설정해줄 수 있다.
intro_scene
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:motion="http://schemas.android.com/tools">
<ConstraintSet android:id="@+id/start">
<!-- Constraints for other views -->
<Constraint
android:id="@+id/tv_intro_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
android:fontFamily="@font/shrikhand_regular"
android:text="Color\n Contatcs"
android:textColor="@color/white"
android:textSize="56sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"/>
...
<ConstraintSet android:id="@+id/End">
<!-- Constraints for other views -->
<Constraint
android:id="@+id/tv_intro_title"
android:layout_width="wrap_content"
...
일단 트랜지션을 설정해줘야한다
start는 애니메이션 시작할때 뷰들의 위치와 상태를 정의해주고
end는 애니메이션이 끝날때 뷰들의 위치와 상태를 나타낸다.
모션레이아웃으로 돌아가 디자인 탭으로 보면 이런식으로 트랜지션이 생긴걸 볼 수 있다.
여기서 트랜지션을 눌러보면 아래에 체크할 수 있는 부분이있는데,
여기에서 scene따로 작성하지 않고도 모션 레이아웃의 뷰를 트랜지션에 추가 할 수 있다.
여기서 애니메이션의 효과를 주려면
<Transition
app:constraintSetEnd="@id/end"
app:constraintSetStart="@id/start"
motion:duration="4000">
<KeyFrameSet>
<KeyAttribute
android:alpha="0"
app:framePosition="10"
app:motionTarget="@id/iv_intro_skyline"
app:transitionEasing="accelerate" />
<KeyPosition
motion:keyPositionType="parentRelative"
app:percentX="1"
motion:framePosition="20"
app:motionTarget ="@+id/iv_intro_skyline"
app:keyPositionType="pathRelative"
app:framePosition="30" />
...
scence에선 이렇게 지정 할 수 가 있다.
Transition에서 시작 트랜지션과 끝 트랜지션을 적어주고
애니메이션이 몇동안 지속될지를 작성한다
그런다음 KeyAttribute로 뷰의 상태, 여기선 투명도를 정해줬고 그안에 app:framePosition="10"적어서 애니메이션의 10% 즉 0.4초까지 투명도가 0이되게 설정했다
시작시 투명도가 0이 아닌경우 0.4초동안 투명해지는 효과가 있고 투명도가 0인경우는 0.4초까지 계속해서 투명하게 나타낼 수 있다
KeyPosition으론 View의 위치를 나타내는데 여기서 위치가 어떻게 변할지를 정할 수 있다.
간단하겐 직선으로 쭉 이동할 수도 있지만 좌우로 휘며 이동하는 등 효과도 낼 수 있다.
이런 작업을 View하나하나에 전부 해줘야하는데 치다보니 좀 속이 울렁거리는 것 같았다
그런데 알고보니 모션레이아웃의 디자인탭에서도 수정이 가능했다
여기서 타이머를 눌러서 다양한 키프레임을 설정 할 수 있었다.
레이아웃도 그냥 마크업으로 작성했었는데
모션레이아웃 작업은 확실히 디자인탭을 이용하는게 훨씬 빠르고 편한것 같다
여기서 애니메이션을 중간중간 재생하며 확인 할 수도있고 상당히 편하게 작업 할 수 있었다.
이런식으로 작업을 하면 아까 위에서 보여준 짧은 애니메이션을 만들 수 있다.
이제 이걸 실제로 쓰려면 코드를 짜야하는데 모션레이아웃은 액티비티에서 어떻게 짤까?
IntroActivity.kt
private fun startMotion() {
binding.introMotion.apply {
transitionToStart()
binding.tvIntroTouch.startAnimation(blick)
Handler(Looper.getMainLooper()).postDelayed({
goMain()
}, 4000)
}
}
private fun goMain() {
binding.introMotion.transitionToEnd()
binding.introMotion.setOnClickListener {
startActivity(Intent(this@IntroActivity, MainActivity::class.java))
finish()
}
}
처음엔 이렇게 짰었다.
binding.introMotion.transitionToEnd()가 나오면 엔딩 까지 바로 점프되면서 애니메이션이 순식간에 지나가기 때문에 뭔가 시작이라는 느낌의 transitionToStart()를 넣고 딜레이를 4000을 줘서 애니 메이션이 실행 될 수 있지 않을까? 생각했는데
그냥 4초동안 가만히 있다가 End로 점프할 뿐이었다.
한참 고민하다가 binding.introMotion.Tran 까지 치고 자동완성되는 메소드들을 조사하기 시작했다.
그러다가 binding.introMotion.set 을 조사하기 시작했는데
setTransitionDuration이라는 딱 봐도 중요할것 같은 느낌의 메소드가 있었다
찾아보니 end가 되기전에 duration을 설정하면 그 시간동안 애니메이션이 실행되게 하는 것 이었다.
private fun goMain() {
binding.introMotion.setTransitionDuration(4000)
binding.introMotion.transitionToEnd()
binding.introMotion.setOnClickListener {
startActivity(Intent(this@IntroActivity, MainActivity::class.java))
finish()
}
}
최종적으로 이런식으로 수정해서 완성할 수 있었다.