본문 바로가기

안드로이드/레이아웃 구현과 활용

[AOS] 토스 하단 버튼 애니메이션 구현하기

반응형

앱 개발을 하다보면 '00처럼 해주세요'를 많이 듣게 됩니다. 같은 부류의 후발주자가 아닌데도 그런 얘기를 많이 듣는 이유는 많은 사람들이 그 앱을 사용하고 있고 그로 인해 좋은 경험을 했기 때문이 아닌가 생각합니다.

저는 개발자로서 디자이너가 요청한 UI는 최대한 수용해주고자 하는데 제가 봐도 이해되지 않는 UI가 아닌 이상 디자이너의 생각을 실현해주는게 개발자의 역할중 하나라고 생각하기 때문입니다.

 

참고 화면 

제가 구현하는 화면은 토스처럼 정보수정 화면 아니였지만 하단에 버튼이 있고 스크롤이 가능하고 입력폼이 있는 화면이였습니다.

그리고 사용자 경험이 많이 발생하는 화면은 아니였지만 간단한 화면이라도 사용자에게 좋은 경험을 줄수 있다면..시간을 투자해야죠! 

 

분석 

어려운 애니메이션은 아니지만 명확하게 분석하기 위해서 '개발자 옵션 > Animator 비율' 을 조정해 느리게 보면서 분석합니다.

키보드 유무에 따라 버튼의 애니메이션이 동작하는걸 확인하고 키보드 show/hide시 캐치 가능한지 검색했습니다.

기존에 'globalLayoutListener'을 활용하는 방법도 있지만 키보드가 올라오고 내려가는 속도와 동일한 애니메이션을 구현하기엔 어려움이 있다고 판단했습니다.

 

WindowInsets을 활용한 애니메이션 

검색중 알게된 좋은 포스팅이 있어 공유드립니다. WindowInsets을 이용한 키보드 애니메이션은 해당 포스팅을 참고해주세요 

 

안드로이드 WindowInsets으로 키보드 애니메이션 구현하기 (1)

#1 — WindowInsets 개념 익히기

sungbin.land

제가 찾던 내용과 너무 부합하여 구현했지만.. android 8 기기에서는 'setOnApplyWindowInsetsListener'이 작동하지 않았습니다. 토스도 구버전 기기에선 작동하지 않을까 기대를 했지만 작동하더라구요 ㅠ.ㅠ  내 시간 ㅠ.ㅠ 

 

OnGlobalLayoutListener 활용

결국 OnGlobalLayoutListener을 활용해서 구현해야 했습니다. 

큰 틀은 onGlobalLayoutListener으로 화면 크기 변화를 측정해 키보드 show/hide에 맞는 애니메이션을 실행합니다.


    private fun initView(){

        window.decorView.viewTreeObserver.addOnGlobalLayoutListener {

            var rect = Rect()
            window.decorView.getWindowVisibleDisplayFrame(rect)

            var height = rect.height()

            if (lastVisibleViewHeight != 0) {
                if (lastVisibleViewHeight > height + VAL_KEYBOARD_MIN_HEIGHT_PX) {
                    // 키보드 보일때
                    startKeyboardUpAnim()
                } else if (lastVisibleViewHeight + VAL_KEYBOARD_MIN_HEIGHT_PX < height) {
                    startKeyboardDownAnim(height - lastVisibleViewHeight)
                }
            }

            lastVisibleViewHeight = height
        }
    }

 

키보드 올라올때 애니메이션

editText를 클릭해서 키보드가 노출되는 순간 키보드가 보이지 않아도 키보드 높이만큼 화면은 이미 올라가 있기 때문에 변화가 감지된 순간 애니메이션을 실행합니다.

    private fun startKeyboardUpAnim(){
        val dpY = Utils.convertDpToPixel(16,this)

        val param = view.layoutParams

        if(updateBtnFullWidth == 0){
            param.width = view + (dpY*2)
            updateBtnFullWidth = param.width
        }else{
            param.width = updateBtnFullWidth
        }

        view.layoutParams= param

        if(updateBtnInitYPos == 0f){
            updateBtnInitYPos = view.y
        }
        var startYPos =  updateBtnInitYPos

        animation.addUpdateListener {
            val value = it.animatedValue // 이동 하는 값
            view.translationY = value as Float
        }

        animation.start()
    }

이동 

키보드가 올라오기전 버튼 하단에 여백이 있지만 키보드 위로 버튼이 올라왔을땐 여백이 없는걸 볼 수 있습니다.

기존에 설정된 여백만큼 더 이동하도록 설정해줍니다. ( 코드상 dpY값 ) 

 

크기 변화

초기 버튼에는 좌우 여백이 들어가 있기 때문에 애니메이션 실행시 넓이를 채워줍니다. 그리고 그 값을 저장해 활용합니다 ( 코드상 updateBtnFullWidth ) 

키보드 내려갈때 애니메이션 

키보드 올라올때와 마찬가지로 키보드가 내려가는 순간 화면이 확장되기 때문에 버튼 이동 애니메이션의 좌표 시작점을 현재 버튼의 위치가 아닌 키보드 높이에서 시작해야합니다.( 현재 버튼 위치에서 이동하도록 코딩하면 키보드 뒤에 감쳐져서 안보이게 됩니다. )

그리고 도착점을 0f(초기와 동일한 상태)으로 지정합니다.  


    private fun startKeyboardDownAnim(keyboardHeight: Int){
        var basicDuration = 200L //1 배율일때

        val dpY = Utils.convertDpToPixel(16,this)

        val startY = (keyboardHeight - dpY).toFloat()

        val animation = ValueAnimator.ofFloat(-startY,0f).apply {
            duration = basicDuration
        }

        val beforeWidth = view.width
        val changeWidth = dpY*2// 이만큼 넓이가 줄어야함

        animation.addUpdateListener {
            val value = it.animatedValue // 이동 하는 값
            view.translationY = value as Float

            val param = view.layoutParams
            param.width = (beforeWidth - ( changeWidth * ( -(startY + value) / -startY ))).toInt()
            view.layoutParams = param
        }

        animation.start()
    }

크기 변화

위치가 이동함에 따라 키보드 보일때 확장된 버튼의 넓이만큼 다시 축소합니다.

 

아쉬운점

위처럼 구현했을때 토스와 완벽하게 똑같지 않습니다. 토스의 경우 키보드 애니메이션 duration과 버튼 애니메이션 duration이 동일하게 동작하는데 제 코드는 그렇지 않습니다. 이점 유의해주세요. 

반응형