📱 안드로이드 Android ~ Kotlin

[Android/Kotlin] ViewPager2 를 이용한 스크롤 시 애니메이션 적용(smoothScroll, PageTransformer)

핑크빛연어 2022. 11. 3. 13:59

 

이전 글 ViewPager2 를 이용한 자동 스크롤에 이어서, 자동 스크롤 시 애니메이션을 적용해 보았습니다.

ViewPager2 를 사용해서 뷰 스크롤 시, 뷰 이동이 너무 딱딱 끊기는 느낌이 있어서 부드러운 스크롤을 적용하기 위한 방법입니다.

 

그리고 ViewPager2 의 PageTransformer 를 사용한 애니메이션 맞춤설정을 사용해 보았습니다. 

 

SliderMainActivity 의 일부만 변경하였습니다.

나머지는 이전 글과 동일한 소스입니다!

https://eunoia3jy.tistory.com/182

 

[Android/Kotlin] ViewPager2 를 이용한 무한 스크롤(Infinite Scroll)

ViewPager2 기존 ViewPager 라이브러리의 개선된 버전으로, ViewPager 를 사용 시 발생하는 일반적인 문제를 해결해줍니다. Android 공식 문서에서도 ViewPager2 사용을 권장하고 있습니다. 🚨 ViewPager2 의 이

eunoia3jy.tistory.com

 

 

🚨 코틀린 확장함수를 사용한 부드러운 스크롤 적용 (ValueAnimator)

 

1. SliderMainActivity.kt

viewpager2.setCurrentItem(item: Int, smoothScroll: Boolean) 의 파라미터 중

smoothScroll 여부 값이 있으나 해당 값을 true 로 설정해도 그닥 부드럽지 않습니다 ㅠ_ㅠ

 

그래서 setCurrentItemWithDuration 이라는 코틀린 확장함수를 만들어서 사용하였습니다.

ViewPager2 는 상속이 안되기 때문에 클래스로 만들지 않고 코틀린 확장함수를 사용합니다.

 

ValueAnimator 객체 중 interpolator 객체는 뷰를 이동하는 애니메이션 속도의 효과를 표현할 수 있습니다.

animator.interpolator = LinearInterpolator()  // 일정한 속도로 이동

animator.interpolator = AccelerateDecelerateInterpolator()  // 점차 빠른 속도로 이동

animator.interpolator = DecelerateInterpolator()  // 점차 느린 속도로 이동

 

Runnable 내에 viewpager2.setCurrentItem() 대신 viewpager2.setCurrentItemWithDuration() 을 사용해줍니다.

package com.eun.myappkotlin02.function

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.viewpager2.widget.ViewPager2
import com.eun.myappkotlin02.R
import com.eun.myappkotlin02.databinding.ActivitySliderMainBinding


class SliderMainActivity : AppCompatActivity() {

    lateinit var binding: ActivitySliderMainBinding
    private lateinit var mSliderPagerAdapter: SliderPagerAdapter
    private lateinit var mSliderRecyclerAdapter: SliderRecyclerAdapter

    private var sliderList: MutableList<SliderEntity> = mutableListOf()

    /* viewPagerHorizontal 에 사용돠는 handler, runnable */
    private val hHandler = Handler()
    private val hRunnable =
        Runnable {
//            binding.viewPagerHorizontal.currentItem = binding.viewPagerHorizontal.currentItem + 1
            var position = binding.viewPagerHorizontal.currentItem + 1
            binding.viewPagerHorizontal.setCurrentWithDuration(position++, 1000)
        }

    /* viewPagerVertical 에 사용돠는 handler, runnable */
    private val vHandler = Handler()
    private val vRunnable =
        Runnable {
//            binding.viewPagerVertical.currentItem = binding.viewPagerVertical.currentItem + 1
            var position = binding.viewPagerVertical.currentItem + 1
            binding.viewPagerVertical.setCurrentWithDuration(position++, 2000)
        }


.....


    /**
     * ViewPager2.setCurrentWithDuration()
     * viewPager2 의 부드러운 스크롤 구현
     * @param viewPager2
     * @param item
     * @param duration
     */
    private fun ViewPager2.setCurrentWithDuration(item: Int, duration: Long) {
        var pxTopDrag : Int = 0
        if(orientation == ViewPager2.ORIENTATION_HORIZONTAL) {  // 가로 방향인 경우
            pxTopDrag = width * (item - currentItem)
        } else {  // 세로 방향인 경우
            pxTopDrag = height * (item - currentItem)
        }
        val animator = ValueAnimator.ofInt(0, pxTopDrag)

        animator.addUpdateListener(object : AnimatorUpdateListener {
            var previousValue = 0
            override fun onAnimationUpdate(valueAnimator: ValueAnimator) {
                val currentValue = valueAnimator.animatedValue as Int
                val currentPxToDrag = (currentValue - previousValue).toFloat()
                fakeDragBy(-currentPxToDrag)
                previousValue = currentValue
            }
        })

        animator.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationStart(animation: Animator) {
                super.onAnimationStart(animation)
                beginFakeDrag()
            }
            override fun onAnimationEnd(animation: Animator) {
                super.onAnimationEnd(animation)
                endFakeDrag()
            }
            override fun onAnimationCancel(animation: Animator) {
                super.onAnimationCancel(animation)
            }
            override fun onAnimationRepeat(animation: Animator) {
                super.onAnimationRepeat(animation)
            }
        })

        animator.interpolator = AccelerateDecelerateInterpolator()
        animator.duration = duration
        animator.start()
    }

}

 

 

결과 화면

처음보단 조금 꿀렁꿀렁 느릿느릿 뷰가 이동하게 됩니다!

 

 

🚨 PageTransformer 를 사용한 애니메이션 맞춤설정

 

https://developer.android.com/training/animation/screen-slide-2?hl=ko 

 

ViewPager2로 프래그먼트 간 슬라이드  |  Android 개발자  |  Android Developers

ViewPager2로 프래그먼트 간 슬라이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 화면 슬라이드는 하나의 전체 화면에서 다른 전체 화면으로 전환하는 것

developer.android.com

 

1. SliderMainActivity.kt

구글 공식 문서에 나와있는 페이지 축소 변환기 ZoomOutPageTransformer 클래스와

심도 페이지 변환기 DepthPageTransformer 클래스를 작성하여 setPageTransformer() 를 사용해 적용하였습니다.

 

// 페이지 축소 변환기 ZoomOutPageTransformer 클래스의 애니메이션 적용

binding.viewPagerHorizontal.setPageTransformer(ZoomOutPageTransformer())

 

// 심도 페이지 변환기 DepthPageTransformer 클래스의 애니메이션 적용

binding.viewPagerVertical.setPageTransformer(DepthPageTransformer())

 

(위에 구현한 부드러운 스크롤도 같이 적용하였습니다.)

package com.eun.myappkotlin02.function

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.viewpager2.widget.ViewPager2
import com.eun.myappkotlin02.R
import com.eun.myappkotlin02.databinding.ActivitySliderMainBinding


class SliderMainActivity : AppCompatActivity() {

    lateinit var binding: ActivitySliderMainBinding
    private lateinit var mSliderPagerAdapter: SliderPagerAdapter
    private lateinit var mSliderRecyclerAdapter: SliderRecyclerAdapter

    private var sliderList: MutableList<SliderEntity> = mutableListOf()

    /* viewPagerHorizontal 에 사용돠는 handler, runnable */
    private val hHandler = Handler()
    private val hRunnable =
        Runnable {
//            binding.viewPagerHorizontal.currentItem = binding.viewPagerHorizontal.currentItem + 1
            var position = binding.viewPagerHorizontal.currentItem + 1
            binding.viewPagerHorizontal.setCurrentWithDuration(position++, 1000)
        }

    /* viewPagerVertical 에 사용돠는 handler, runnable */
    private val vHandler = Handler()
    private val vRunnable =
        Runnable {
//            binding.viewPagerVertical.currentItem = binding.viewPagerVertical.currentItem + 1
            var position = binding.viewPagerVertical.currentItem + 1
            binding.viewPagerVertical.setCurrentWithDuration(position++, 2000)
        }

.....

    /**
     * initViewForHorizontal()
     * RecyclerView.Adapter 를 사용한 ViewPager2 구현
     */
    private fun initViewForHorizontal() {
        if(sliderList.size > 0) {
            binding.viewPagerHorizontal.isVisible = true
            binding.tvEmptyHorizontal.isGone = true

            mSliderRecyclerAdapter = SliderRecyclerAdapter()

            binding.viewPagerHorizontal.adapter = mSliderRecyclerAdapter
            binding.viewPagerHorizontal.orientation = ViewPager2.ORIENTATION_HORIZONTAL
            mSliderRecyclerAdapter.setSliderList(this, binding.viewPagerHorizontal, sliderList)
            binding.viewPagerHorizontal.setPageTransformer(ZoomOutPageTransformer())

            binding.viewPagerHorizontal.registerOnPageChangeCallback(object :
                ViewPager2.OnPageChangeCallback() {
                override fun onPageSelected(position: Int) {
                    super.onPageSelected(position)
                    hHandler.removeCallbacks(hRunnable)
                    hHandler.postDelayed(hRunnable, 2000) // Slide duration 2 seconds
                }
            })

        } else {  // sliderList 가 없는 경우 viewPager2 hidden 처리
            binding.viewPagerHorizontal.isGone = true
            binding.tvEmptyHorizontal.isVisible = true
        }
    }


    /**
     * initViewForVertical()
     * FragmentStateAdapter 를 사용한 ViewPager2 구현
     */
    private fun initViewForVertical() {
        if(sliderList.size > 0) {
            binding.viewPagerVertical.isVisible = true
            binding.tvEmptyVertical.isGone = true

            mSliderPagerAdapter = SliderPagerAdapter(this)

            /*for(i in 0 until sliderList.size) {
                mSliderPagerAdapter.addFragment(SliderPagerFragment().newInstance1(i, sliderList))
            }*/

            binding.viewPagerVertical.adapter = mSliderPagerAdapter
            binding.viewPagerVertical.orientation = ViewPager2.ORIENTATION_VERTICAL
            mSliderPagerAdapter.setSliderList(sliderList)
            binding.viewPagerVertical.setPageTransformer(DepthPageTransformer())

            binding.viewPagerVertical.registerOnPageChangeCallback(object :
                ViewPager2.OnPageChangeCallback() {
                override fun onPageSelected(position: Int) {
                    super.onPageSelected(position)
                    vHandler.removeCallbacks(vRunnable)
                    vHandler.postDelayed(vRunnable, 2000) // Slide duration 2 seconds
                }
            })

        } else {  // sliderList 가 없는 경우 viewPager2 hidden 처리
            binding.viewPagerVertical.isGone = true
            binding.tvEmptyVertical.isVisible = true
        }
    }

.....

}

 

 

2. ZoomOutPageTransformer.kt

페이지 축소 변환기 ZoomOutPageTransformer 클래스 입니다.

package com.eun.myappkotlin02.function

import android.view.View
import androidx.viewpager2.widget.ViewPager2

class ZoomOutPageTransformer : ViewPager2.PageTransformer {

    companion object {
        const val TAG = "ZoomOutPageTransformer"
        private const val MIN_SCALE = 0.85f
        private const val MIN_ALPHA = 0.5f
    }


    override fun transformPage(page: View, position: Float) {
        page.apply {
            val pageWidth = width
            val pageHeight = height
            when {
                position < -1 -> { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    alpha = 0f
                }
                position <= 1 -> { // [-1,1]
                    // Modify the default slide transition to shrink the page as well
                    val scaleFactor = Math.max(Companion.MIN_SCALE, 1 - Math.abs(position))
                    val vertMargin = pageHeight * (1 - scaleFactor) / 2
                    val horzMargin = pageWidth * (1 - scaleFactor) / 2
                    translationX = if (position < 0) {
                        horzMargin - vertMargin / 2
                    } else {
                        horzMargin + vertMargin / 2
                    }

                    // Scale the page down (between MIN_SCALE and 1)
                    scaleX = scaleFactor
                    scaleY = scaleFactor

                    // Fade the page relative to its size.
                    alpha = (Companion.MIN_ALPHA +
                            (((scaleFactor - Companion.MIN_SCALE) / (1 - Companion.MIN_SCALE)) * (1 - Companion.MIN_ALPHA)))
                }
                else -> { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    alpha = 0f
                }
            }
        }
    }

}

 

 

3. DepthPageTransformer.kt

심도 페이지 변환기 DepthPageTransformer 클래스 입니다.

package com.eun.myappkotlin02.function

import android.view.View
import androidx.viewpager2.widget.ViewPager2

class DepthPageTransformer : ViewPager2.PageTransformer {

    companion object {
        const val TAG = "DepthPageTransformer"
        private const val MIN_SCALE = 0.75f
    }


    override fun transformPage(page: View, position: Float) {
        page.apply {
            val pageWidth = width
            when {
                position < -1 -> { // [-Infinity,-1)
                    // This page is way off-screen to the left.
                    alpha = 0f
                }
                position <= 0 -> { // [-1,0]
                    // Use the default slide transition when moving to the left page
                    alpha = 1f
                    translationX = 0f
                    translationZ = 0f
                    scaleX = 1f
                    scaleY = 1f
                }
                position <= 1 -> { // (0,1]
                    // Fade the page out.
                    alpha = 1 - position

                    // Counteract the default slide transition
                    translationX = pageWidth * -position
                    // Move it behind the left page
                    translationZ = -1f

                    // Scale the page down (between MIN_SCALE and 1)
                    val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position)))
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                else -> { // (1,+Infinity]
                    // This page is way off-screen to the right.
                    alpha = 0f
                }
            }
        }
    }

}

 

 

결과 화면

뷰 이동 시 애니메이션 효과가 나타나는 것을 확인할 수 있습니다.

 

 

 

감사합니다 ^__^

728x90
반응형