-
ViewPager와 PageTransformer를 활용한 Intro Screen안드로이드 2019. 4. 16. 11:22
이번 포스팅의 주제는 ViewPager와 PageTransformer를 활용하여 애니메이션이 들어간 Intro Screen을 만들어보도록 하겠습니다.
시작에 앞서서 Google Drive의 Intro를 보시겠습니다.
Google Drive Intro
위는 Google Drive의 Intro인데요. 실제로 보시면 기존의 ViewPager보다는 좀 더 사용자와 인터렉션이 더욱 원할히 할 수 있습니다.
사용자의 스와이핑에 맞춰서 화면의 색이 변하고 페이지 내의 object들이 서로 다른 애니메이션 효과를 가지고 이동하는 것을 볼 수 있습니다. 이것을 보통 Parallax 스크롤이라고 합니다.
일반적인 방식으로 ViewPager를 구현해 보면 아래와 같습니다.
기본적으로 View는 3가지 Object로 이루어져 있습니다.
-
Title
-
Icon
-
Description
위의 Sample을 보면 처음의 Google Drive에 비하면 많이 부족하고 심심한 것을 볼 수 있습니다.
그럼 어떻게 해야 화려한 애니메이션 효과를 줄 수 있을까요? 답은 PageTransformer 를 이용하는 것입니다.
PageTransformer 활용
PageTransformer를 활용하면 아래와 같은 결과를 나타낼 수 있습니다.
위를 보시면 스와이핑에 맞춰서 Title과 Description은 가만히 있고 아이콘만이 이동하면서,
바탕화면의 색이 그라디에이션 효과로 바뀌는 것을 볼 수 있습니다.위의 화면을 구현하기 위해서 중요한 부분은 2가지가 있습니다.
첫번째, PageTransformer의 transformPage(View page, float position)
두번째, Gradation 효과를 주기 위한 ArgbEvaluator 가 있습니다.
transformPage메서드는 현재 해당하는 page의 View 정보와 해당 page의 position 정보가 있습니다.
아래는 SorceCode입니다
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewpager.adapter = IntroAdapter(supportFragmentManager) viewpager.setPageTransformer(false, IntroPageTransformer(viewpager)) } }
const val BACKGROUND_COLOR = "backgroundColor" const val PAGE = "page" class IntroFragment : Fragment() { private var mBackgroundColor: Int? = null private var mPage: Int? = null companion object { fun newInstance(backgroundColor: Int, page: Int): IntroFragment { val frag = IntroFragment() val bundle = Bundle() bundle.putInt(BACKGROUND_COLOR, backgroundColor) bundle.putInt(PAGE, page) frag.arguments = bundle return frag } } fun getBackgroundColor(): Int? = mBackgroundColor override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBackgroundColor = arguments?.getInt(BACKGROUND_COLOR) mPage = arguments?.getInt(PAGE) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = if (mPage == 0) inflater.inflate(R.layout.activity_first, container, false) else inflater.inflate(R.layout.activity_second, container, false) view.tag = mPage return view } }
class IntroAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { lateinit var frag1: IntroFragment lateinit var frag2: IntroFragment override fun getItem(position: Int): Fragment { when (position) { 0 -> { frag1 = IntroFragment.newInstance(Color.parseColor("#03A9F4"), position) return frag1 } else -> { frag2 = IntroFragment.newInstance(Color.parseColor("#4CAF50"), position) return frag2 } } } fun getFragmentBackgroundColor(position: Int): Int? { when (position) { 0 -> return frag1.getBackgroundColor() else -> return frag2.getBackgroundColor() } } override fun getCount(): Int = 2 }
class IntroPageTransformer(var viewPager: ViewPager) : ViewPager.PageTransformer, ViewPager.OnPageChangeListener { private val argbEvaluator: ArgbEvaluator private var color: Int = -1 init { viewPager.addOnPageChangeListener(this) argbEvaluator = ArgbEvaluator() } override fun onPageScrollStateChanged(state: Int) { //Not Use } override fun onPageSelected(position: Int) { //Not Use } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { color = argbEvaluator.evaluate(positionOffset, (viewPager.adapter as IntroAdapter).getFragmentBackgroundColor(position), (viewPager.adapter as IntroAdapter).getFragmentBackgroundColor(position + 1)) as Int viewPager.setBackgroundColor(color) } override fun transformPage(page: View, position: Float) { val pageWidth = page.width val pageWidthTimesPosition = pageWidth * position val absPosition = Math.abs(position) if (position < 0) { page.title.alpha = 1.0f - absPosition page.title.translationX = -pageWidthTimesPosition * 0.92f page.des.alpha = 1.0f - absPosition page.des.translationX = -pageWidthTimesPosition * 0.92f } else { page.title.alpha = 1.0f - absPosition page.title.translationX = -pageWidthTimesPosition page.des.alpha = 1.0f - absPosition page.des.translationX = -pageWidthTimesPosition } } }
<?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" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/intro_background" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" tools:context=".MainActivity"> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/tv_icon" app:layout_constraintBottom_toTopOf="@+id/des" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/first_title" android:textColor="#ffffff" android:textSize="30dp" app:layout_constraintBottom_toTopOf="@+id/image" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/des" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/first_des" android:textColor="#ffffff" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/image" /> </androidx.constraintlayout.widget.ConstraintLayout>
<?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" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/intro_background" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" tools:context=".MainActivity"> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/live_tv_icon" app:layout_constraintBottom_toTopOf="@+id/des" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title" /> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/second_title" android:textColor="#ffffff" android:textSize="30dp" app:layout_constraintBottom_toTopOf="@+id/image" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/des" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/second_des" android:textColor="#ffffff" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/image" /> </androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?> <androidx.viewpager.widget.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> </androidx.viewpager.widget.ViewPager>
위의 전체 소스코드는 깃허브에 올려놓았습니다.
세계 최강을 꿈꾸는 안드로이드 Front-End 개발자입니다.
Github 주소: github.com/rkswlrbduf
이전 블로그 주소: blog.naver.com/rkswlrbduf => 블로그 이전중...
'안드로이드' 카테고리의 다른 글
Android App With Multi Module (0) 2019.04.18 CustomView - BottomNavigationView (0) 2019.04.14 아키텍처 패턴 - RAW, MVC, MVP (1) 2019.04.12 안드로이드 생명주기-Android LifeCycle (0) 2019.04.12 Android Q - RoleManager (0) 2019.04.12 댓글
-