Android

[Android] 토스 화면 전환 애니메이션 따라 만들기 Fragment Animation

욘두로이드 2023. 1. 28. 21:03

목표

토스앱의 화면전환 따라 만들기!

 

토스앱이 정확히 어떻게 구현하였는지 모르지만

시작적으로 비슷하게 구현하기 위해 제가 사용한 방법을 소개합니다.

 

우선 요구사항을 대략적으로 정리해 보면 다음과 같습니다.

 

1. 하단 네비게이션 메뉴를 통해 화면전환을 한다.

2. 메뉴의 위치에 따라 오른쪽 또는 왼쪽에서 나오는 듯한 애니메이션을 구현한다.

3. 2번의 움직임에 투명도를 같이 조절해 준다. (fade in and out)

 

방법

Step 1

BottomNavigation에 사용할 메뉴를 구성합니다.

res 디렉토리 하위에 menu 리소스 디렉터리 생성

만들어진 menu 디렉토리에 bottom_navigation_menu라는 이름으로 xml파일을 생성하고

다음과 같이 작성합니다. icon은 vector asset을 사용했습니다.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/bottom_navigation_item_home"
        android:enabled="true"
        android:icon="@drawable/baseline_home_24"
        android:title="Home"/>

    <item
        android:id="@+id/bottom_navigation_item_chart"
        android:enabled="true"
        android:icon="@drawable/baseline_show_chart_24"
        android:title="Chart"/>

    <item
        android:id="@+id/bottom_navigation_item_settings"
        android:enabled="true"
        android:icon="@drawable/baseline_settings_24"
        android:title="Settings"/>

</menu>

Step 2

Container가 될 Activity에 FrameLayout과 BottomNavigation을 사용하여 화면을 구성합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/scene_basic">

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/navigation_bar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_navigation_menu" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

Step 3

3개의 메뉴에 맞는 각각의 Fragment를 만들어 줍니다.

화면 구성은 단순하게 구별만 할 수 있도록 배경색과 텍스트만 넣어줬습니다.

 

 

Step 4

여기가 핵심!

화면 전환에 사용할 3개의 애니메이션 파일을 만들어 줍니다.

제가 원하는 애니메이션의 종류는 다음과 같습니다.

 

1. 사라지는 화면의 fade out

2. 왼쪽에서 들어오는 듯한 애니메이션

3. 오른쪽에서 들어오는 듯한 애니메이션

 

이렇게 3가지 애니메이션을 담당하는 xml 파일을 만들어보겠습니다.

menu directory 생성할 때와 같이 이번에는 anim directory를 만들어줍니다.

raw 하위에 Anim Android Resource Directory 생성

그리고 이 anim 폴더 안에 위에 3가지 Animation Resource 파일을 만들어줍니다.

 

1. anim_fade_out.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="200"/>
</set>

set tag 는 없어도 무방합니다.

 

2. anim_slide_in_from_left_fade_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="200"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
    <translate
        android:duration="200"
        android:fromXDelta="-10%"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toXDelta="0%" />
</set>

 

android:interpolator

이 옵션은 애니메이션의 시작과 끝 두 점을 어떻게 연결할 것인가 하는 옵션입니다.

시작지점에서 끝까지 변화할 때 속도나 움직임을 좀 더 다양하게 구현할 수 있는 속성입니다.

설정하지 않으면 기울기 1의 일정한 속도로 움직이기 때문에 부자연스러울 수 있어서

이동하다가 마지막에 감속하는 decelerate_interpolator로 주었습니다.

 

각 옵션에 대한 자세한 설명은

이종현 개발자님의 블로그에 정말 잘 정리 되어있으니 참고해 보시면 좋을 것 같습니다.

https://gus0000123.medium.com/android-animation-interpolar-구현하기-8d228f4fc3c3

 

Android Animation Interpolator 구현하기

Interpolator는 한국어로 보간을 의미합니다. 보간은 두 점을 연결하는 방법이며 어떻게 궤적을 형성할 것인가를 나타냅니다.

gus0000123.medium.com

 

3. anim_slide_in_from_right_fade_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="200"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
    <translate
        android:duration="200"
        android:fromXDelta="10%"
        android:interpolator="@android:anim/decelerate_interpolator"
        android:toXDelta="0%" />
</set>

Step 5

마지막으로 Container Activity에 BottomNavigation 아이템 선택 콜백을 등록하고

거기에 전환 애니메이션 로직을 구현해 줍니다.

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.dev_yangkj.tistory.blogsampleapp.R
import com.dev_yangkj.tistory.blogsampleapp.databinding.ActivityTossTransitionBinding

class TossTransitionActivity : AppCompatActivity() {

    private lateinit var binding: ActivityTossTransitionBinding

    private val fragments = listOf(
        HomeFragment(),
        ChartFragment(),
        SettingsFragment()
    )

    // animation 방향 계산을 위해 가장 마지막 위치 값 저장
    private var recentPosition = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTossTransitionBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 첫 화면 초기화 HomeFragment
        supportFragmentManager.beginTransaction()
            .replace(binding.frameLayout.id, fragments[0])
            .commit()

        // BottomNavigation 아이템 선택 콜백
        binding.bottomNavigationView.setOnItemSelectedListener {
            val transaction = supportFragmentManager.beginTransaction()
            when (it.itemId) {
                R.id.bottom_navigation_item_home -> {
                    transaction.setCustomAnimations(
                        R.anim.anim_slide_in_from_left_fade_in,
                        R.anim.anim_fade_out
                    )
                    transaction.replace(binding.frameLayout.id, fragments[0])
                    transaction.commit()
                    recentPosition = 0
                    return@setOnItemSelectedListener true
                }
                R.id.bottom_navigation_item_chart -> {

                    // 두번째 보다 작은 왼쪽에서 이동해 올 경우 오른쪽에서 화면이 나타남
                    // 반대의 경우 왼쪽에서 나타남
                    if (recentPosition < 1) {
                        transaction.setCustomAnimations(
                            R.anim.anim_slide_in_from_right_fade_in,
                            R.anim.anim_fade_out
                        )
                    } else {
                        transaction.setCustomAnimations(
                            R.anim.anim_slide_in_from_left_fade_in,
                            R.anim.anim_fade_out
                        )
                    }
                    transaction.replace(binding.frameLayout.id, fragments[1])
                    transaction.commit()
                    recentPosition = 1
                    return@setOnItemSelectedListener true
                }
                R.id.bottom_navigation_item_settings -> {
                    transaction.setCustomAnimations(
                        R.anim.anim_slide_in_from_right_fade_in,
                        R.anim.anim_fade_out
                    )
                    transaction.replace(binding.frameLayout.id, fragments[2])
                    transaction.commit()
                    recentPosition = 2
                    return@setOnItemSelectedListener true
                }
            }
            return@setOnItemSelectedListener false
        }

    }


}

 

최종 결과물

 

끝.

 

 

P.S 전체 소스는 Github에 올려두었으니 참고 바랍니다.

https://github.com/wjjasd/BlogSampleApp

 

GitHub - wjjasd/BlogSampleApp

Contribute to wjjasd/BlogSampleApp development by creating an account on GitHub.

github.com