본문 바로가기
Project/안드로이드 프로젝트(RandomColorChart)

Android Studio, Kotlin] 9. 애니메이션을 통해 플로팅 메뉴 만들기

by 김마리님 2021. 3. 11.

 

이렇게 플로팅 버튼을 이용해서 메뉴를 만드는 앱이 좀 많긴 하다.

글로 만든 메뉴보다 시각적이고 단순하며, 또 애니메이션과 함께라면 너무나도 귀여운(!) 앱을 만들 수 있다.

의외로 간단하니까 천천히 보자.

 

itstudy-mary.tistory.com/275?category=955763

 

Android Studio, JAVA] ObjectAnimator 이용하기

앞서 두 포스팅의 단점은, view의 그래픽만 이동하고 실질적인 레이아웃은 원래 자리에 남아있다는 것이다. 그렇기 때문에 만일 지금처럼 뷰 애니메이팅을 이용하려 한다면, 만약 편지에 클릭 리

itstudy-mary.tistory.com

여기서 이전 애니메이션에도 말한 적이 있는데, 단순히 ValueAnimator을 이용하면 레이아웃 뷰만 변화하고, xml로 뷰의 위치가 변하는건 아니기 때문에, 뷰의 변화가 없어도 되는 add 버튼은 valueAnimator로, 나머지는 ObjectAnimator로 구현한다.

 

먼저 버튼을 xml로 만든다.

 

 

- main_activity.xml

...

    <ImageView
        android:id="@+id/imageViewWrite"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/ic_write_color"
        android:layout_gravity="bottom|end"
        android:background="@drawable/bg_round_button_white"
        android:translationZ="5dp"
        android:layout_marginEnd="25dp"
        android:layout_marginBottom="25dp"
        android:padding="15dp"
        />

    <ImageView
        android:id="@+id/imageViewPhoto"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/ic_camera_fffff3"
        android:layout_gravity="bottom|end"
        android:background="@drawable/bg_round_button_a593e0"
        android:translationZ="5dp"
        android:layout_marginEnd="25dp"
        android:layout_marginBottom="25dp"
        android:padding="15dp"
        />

    <ImageView
        android:id="@+id/imageViewAdd"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/ic_plus_fffff3"
        android:layout_gravity="bottom|end"
        android:background="@drawable/bg_round_button_566270"
        android:translationZ="10dp"
        android:layout_marginEnd="25dp"
        android:layout_marginBottom="25dp"
        android:padding="10dp"
        />

</FrameLayout>

(최상단 뷰를 frameLayout이나 relative, constantlayout으로 구현한다.)

이 때 add 버튼을 translationZ를 가장 높게, 나머지 두 뷰를 translationZ를 add 버튼보다는 낮게 구현한다. 

 

다음, add버튼이 돌아가는 로테이션 애니메이션을 구현한다.

 

- rotate_plus_to_close.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="400"
    android:fromDegrees="0"
    android:toDegrees="45"
    android:pivotX="50%"
    android:pivotY="50%"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:fillAfter="true">
</rotate>

 

- rotate_close_to_plus.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="400"
    android:fromDegrees="45"
    android:toDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:fillAfter="true">
</rotate>

 

다음 코틀린 코드를 구현한다.

 

먼저, 전역변수가 필요하다.

    var add: Boolean = true

왜냐면, 이 전역변수로 현재 메뉴가 열린 상태인지, 닫힌 상태인지 구분할 수 있도록 할 것이기 때문이다.

 

그리고, 메뉴를 구현할 뷰를 전역변수로 구현한다.

    private lateinit var imageViewAdd: ImageView
    private lateinit var imageViewWrite: ImageView
    private lateinit var imageViewPhoto: ImageView

 

add버튼을 눌렀을때 메뉴가 나와야 하므로, add 버튼에 대한 리스너를 구현한다.

    private fun setListener() {
    
...

        imageViewAdd.setOnClickListener {
            DlogUtil.d(TAG, "메뉴 클릭")
            popupMenu()

        }
    }

 

버튼을 누를때 호출되는 팝업 메서드를 보자.

   private fun popupMenu() {

        var px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60f, this.resources.displayMetrics)

        if (add) {

            var animation = AnimationUtils.loadAnimation(this, R.anim.rotate_plus_to_close)
            imageViewAdd.animation = animation
            imageViewAdd.startAnimation(animation)
            var writeAnimator = ObjectAnimator.ofFloat(imageViewWrite, "translationY", 0f, -px)
            writeAnimator.duration = 400
            writeAnimator.interpolator = OvershootInterpolator()
            writeAnimator.target = imageViewWrite
            writeAnimator.start()

            var photoAnimator = ObjectAnimator.ofFloat(imageViewPhoto, "translationY", 0f, -px*2)
            photoAnimator.duration = 500
            photoAnimator.interpolator = OvershootInterpolator()
            photoAnimator.target = imageViewPhoto
            photoAnimator.start()

            add = !add
        } else {
            var animation = AnimationUtils.loadAnimation(this, R.anim.rotate_close_to_plus)
            imageViewAdd.animation = animation
            imageViewAdd.startAnimation(animation)

            var writeAnimator = ObjectAnimator.ofFloat(imageViewWrite, "translationY", -px, 0f)
            writeAnimator.duration = 400
            writeAnimator.interpolator = OvershootInterpolator()
            writeAnimator.target = imageViewWrite
            writeAnimator.start()

            var photoAnimator = ObjectAnimator.ofFloat(imageViewPhoto, "translationY", -px*2, 0f)
            photoAnimator.duration = 500
            photoAnimator.interpolator = OvershootInterpolator()
            photoAnimator.target = imageViewPhoto
            photoAnimator.start()

            add = !add
        }
    }

 

버튼이 true가 되면 value 애니메이션으로 버튼을 close로 45도로 돌리고,

            var animation = AnimationUtils.loadAnimation(this, R.anim.rotate_plus_to_close)
            imageViewAdd.animation = animation
            imageViewAdd.startAnimation(animation)

 

write애니메이션과 photo 애니메이션을 Object 애니메이션으로 구현한다.

            var writeAnimator = ObjectAnimator.ofFloat(imageViewWrite, "translationY", 0f, -px)
            writeAnimator.duration = 400
            writeAnimator.interpolator = OvershootInterpolator()
            writeAnimator.target = imageViewWrite
            writeAnimator.start()

            var photoAnimator = ObjectAnimator.ofFloat(imageViewPhoto, "translationY", 0f, -px*2)
            photoAnimator.duration = 500
            photoAnimator.interpolator = OvershootInterpolator()
            photoAnimator.target = imageViewPhoto
            photoAnimator.start()

이 애니메이션은 위로 메뉴를 팝업하는 애니메이션이다.

이 때, 가장 위에 등장하는 메뉴가 가장 느려야 하며, 메뉴의 팝업속도가 다 같으면 굉장히 메뉴가 부자연스러워진다...

 

(속도가 같을때)
(속도가 다를 때)

 

다음, 통통 튀는 애니메이션을 위해 interploator을 부여한다.

interploator 파라메터는 애니메이션의 진행 상태를 부여하는데, 기본은 linear 으로, 애니메이션이 모두 같은 속도로 움직인다.

지금 쓴 overShoot의 경우, 예상 진행경로보다 약간 더 움직이게 되고, Accelerator는 애니메이션의 시작 혹은 끝에 가속도를 부여한다.

 

            writeAnimator.interpolator = OvershootInterpolator()

 

developer.android.com/reference/android/view/animation/Interpolator

 

Interpolator  |  Android 개발자  |  Android Developers

 

developer.android.com

다른 interpolator은 여기서 참고하자.

 

전체 코드는 다음과 같다.

 

- MainActivity.java

더보기
...

class MainActivity : AppCompatActivity() {

    ...
    private lateinit var imageViewAdd: ImageView
    private lateinit var imageViewWrite: ImageView
    private lateinit var imageViewPhoto: ImageView
    ...
    var add: Boolean = true
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        ...
        findView()
        setListener()
        ...
    }

    ...

    private fun findView() {
        ...
        imageViewAdd = findViewById(R.id.imageViewAdd)
        imageViewWrite = findViewById(R.id.imageViewWrite)
        imageViewPhoto = findViewById(R.id.imageViewPhoto)
        ...
    }

    private fun setListener() {
        ...

        imageViewAdd.setOnClickListener {
            DlogUtil.d(TAG, "메뉴 클릭")
            popupMenu()

        }
        ...
    }

    ...

    private fun popupMenu() {

        var px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60f, this.resources.displayMetrics)

        if (add) {

            var animation = AnimationUtils.loadAnimation(this, R.anim.rotate_plus_to_close)
            imageViewAdd.animation = animation
            imageViewAdd.startAnimation(animation)
            var writeAnimator = ObjectAnimator.ofFloat(imageViewWrite, "translationY", 0f, -px)
            writeAnimator.duration = 400
            writeAnimator.interpolator = OvershootInterpolator()
            writeAnimator.target = imageViewWrite
            writeAnimator.start()

            var photoAnimator = ObjectAnimator.ofFloat(imageViewPhoto, "translationY", 0f, -px*2)
            photoAnimator.duration = 400
            photoAnimator.interpolator = OvershootInterpolator()
            photoAnimator.target = imageViewPhoto
            photoAnimator.start()

            add = !add
        } else {
            var animation = AnimationUtils.loadAnimation(this, R.anim.rotate_close_to_plus)
            imageViewAdd.animation = animation
            imageViewAdd.startAnimation(animation)

            var writeAnimator = ObjectAnimator.ofFloat(imageViewWrite, "translationY", -px, 0f)
            writeAnimator.duration = 400
            writeAnimator.interpolator = OvershootInterpolator()
            writeAnimator.target = imageViewWrite
            writeAnimator.start()

            var photoAnimator = ObjectAnimator.ofFloat(imageViewPhoto, "translationY", -px*2, 0f)
            photoAnimator.duration = 400
            photoAnimator.interpolator = OvershootInterpolator()
            photoAnimator.target = imageViewPhoto
            photoAnimator.start()

            add = !add
        }
    }

    ...
}

 

애니메이션을 이용하면 정말 섬세하게 앱 구현이 가능해진다.

반응형