24.01.11 ViewPager2

KSang·2024년 1월 12일
0

TIL

목록 보기
31/101

ViewPager2

앱을 실행할때 인트로화면 이후에 이미지를 넘기면서 페이지를 소개하는 창을 흔히들 볼 수 있을것이다
KakaoTalk_20240108_033711069_09

예를 들어 이런 화면인데 양옆으로 스와이프가 가능하면서 가만히 있으면 자동으로 페이지를 이동하는등

로그인하기전에 화면을 꾸며준다

이런걸 꾸며줄때 사용하는 도구로 ViewPager를 쓴다

ViewPager'와 'TabLayout'

ViewPager는 사용자가 좌우로 스와이프하여 다양한 뷰나 프래그먼트를 전환할 수 있게 해주는 컴포넌트

TabLayout은 탭을 표시하기 위한 레이아웃으로, 각 탭은 서로 다른 뷰나 프래그먼트를 보여줄 수 있다

ViewPager를 이용하면 프래그먼트를 스와이프하면서 이동할 수 있는데

지금보면 많은 기능들이 deprecate됬다.

ViewPager2가 나오면서 더 강력한 성능으로 효율적으로 전환이 가능하게 됬는데

안드로이드 공식문서에서도 ViewPager2를 사용하는걸 권장한다

ViewPager2와 ViewPager

ViewPager2는 AndroidX가 발표된 이후 새롭게 나온 ViewPager로 안드로이드 공식 문서에서도 아래와 같은 이유로 ViewPager보다 ViewPager2를 활용하여 페이징 하는 것을 권장

Horizontal Paging에서 Vertical Paging도 지원 가능(orientation 속성 활용)
RTl(Right To Left) 페이징 지원(layoutDirection 속성 활용)
notifyDatasetChanged로 Mutable Fragment Collection을 활용하여 동적 페이징 구현
주로 배너광고, 최초 소개 페이지, 스와이프를 통한 메뉴 변경에 쓰이고 있다.

단, 스와이프를 통한 메뉴 변경은 구글 디자인 정책상 권장되지 않는 방법이다.

최초 소개 페이지로 구현을 시작해볼껀데 ViewPager2가 가장적합한것 같다

ViewPager2로 구현해보자

뷰페이저는 리사이클러뷰와 유사하다, 구성할 아이템 레이아웃을 사용하고 어댑터에서 설정하게된다 (응용하면 넘길때마다 다양한 레이아웃을 타입별로 구별해서 사용할수 도 있을것같다)

image

뷰페이저에 들어갈 아이템이다

간단하게 텍스트뷰랑 이미지뷰로 구성, layout은 너비와 높이는 꼭 match_parent로 해줘야한다

이제 뷰페이저가 들어간 레이아웃을 만들자

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".ui.signin.SignInActivity">


    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="7"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">
        <ImageView
        android:id="@+id/point_double"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:scaleX="-1"
        android:src="@drawable/baseline_brightness_1_24"
            android:layout_gravity="center_horizontal"/>
    <ImageView
        android:id="@+id/iv_sign_kakao"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/kakao_login_medium_wide"
        android:background="@drawable/custom_ripple_effect"
        android:clipToOutline="true"
        android:layout_gravity="center_horizontal"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="다른 방식으로 로그인"
        android:textStyle="bold"
        android:textSize="12sp"
        android:textColor="@color/dark_gray"
        android:background="@drawable/custom_ripple_effect"
        android:layout_marginTop="8dp"
        android:layout_gravity="center_horizontal"/>
    </LinearLayout>
</LinearLayout>

화면이 스와이프되는 상단과 스와이프 되지않고 고정된 위치에 있을 위젯들을 만들어준다

여기서 point_double은 페이지가 전환 됬을때 몇번째인지 표기해줄 이미지를 벡터이미지로만든건데

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="34"
    android:viewportHeight="34">

    <path
        android:fillColor="#E2E2E2"
        android:pathData="M12,12 m-10,0 a5,5 0,1 1,10 0 a5,5 0,1 1,-10 0"/>

    <path
        android:fillColor="@android:color/black"
        android:pathData="M12,12 m10,0 a5,5 0,1 1,10 0 a5,5 0,1 1,-10 0"/>
</vector>

이런식으로 구성되 있다

이제 레이아웃 구성이 끝낫으니 액티비티에서 수행할 로직드를 작성하자

ViewPager2 구현하기

data class ViewPagerData(
    val img: Int,
    val info: String
)
...

SignInActivity.kt
...

val data = arrayListOf(ViewPagerData(R.drawable.dummy, getString(R.string.viewpager_title_1)),ViewPagerData(R.drawable.dummy2, getString(R.string.viewpager_title_2)))

뷰페이저 데이터 베이스다.

있음좋고 없어도 되지만 편의성을 위해 만들었다

뷰페이저가 전환될때 바뀔 아이템들의 데이터 구성이다

사용할 뷰페이저 아이템 레이아웃이 TextView와 ImageView로 구성되어 있어

이미지 리소스 값과 TextView에 적을 글로 구별했다.

class SigninPagerAdapter(private val data: ArrayList<ViewPagerData>) : RecyclerView.Adapter<SigninPagerAdapter.ViewHolderPage>() {
    lateinit var binding: ItemViewpagerBinding
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderPage {
        binding = ItemViewpagerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolderPage(binding)
    }

    override fun getItemCount(): Int {
        return data.size
    }

    override fun onBindViewHolder(holder: ViewHolderPage, position: Int) {
        val viewHolder: ViewHolderPage = holder
        viewHolder.onBind(data[position])
    }

    inner class ViewHolderPage(binding: ItemViewpagerBinding) : RecyclerView.ViewHolder(binding.root) {
        fun onBind(data: ViewPagerData) {
            binding.tvViewpager.text = data.info
            binding.ivViewpager.setImageResource(data.img)
        }
    }
}

viewpager2를 관리할 어댑터를 만들었다

리사이클러뷰랑 유사한게 아니라 사실상 똑같다

어댑터도 같은걸 쓴다

inner class로 홀더를 만들어주고

아이템 레이아웃의 위젯들을 세팅

필수 override 3개도 불러주고 값을 세팅해주면 기본적인 뷰페이저 완성이 끝난다

Infinite Scroll

이제 좌우로 계속 스와이프할수 있고 데이터 끝까지 스와이프해도 다시 처음부터 반복되도록 만들어

계속 스크롤 할 수 있는 무한 스크롤을 구현할것이다

일단 위 어댑터에서 정한 아이템의 크기를 변경해준다

    override fun getItemCount(): Int {
        return Int.MAX_VALUE
    }

정확히 무한은 아니지만 몇억의 크기를 가지고 있는 데이터를 만들어 무한히 스크롤 가능한 것 처럼 만든다

이러면 포지션에 맞는 데이터도 정해줘야하니 onBindViewHolder도 수정해준다

    override fun onBindViewHolder(holder: ViewHolderPage, position: Int) {
        val viewHolder: ViewHolderPage = holder
        viewHolder.onBind(data[position % 2])
    }

이러면 데이터의 크기는 매우 커도 사용하는 데이터는 data[0],data[1]의 반복이 된다

    fun setPage(){
        val data = arrayListOf(ViewPagerData(R.drawable.dummy, getString(R.string.viewpager_title_1)),ViewPagerData(R.drawable.dummy2, getString(R.string.viewpager_title_2)))
        with(binding){
            viewPager.adapter = SigninPagerAdapter(data)
            viewPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
        }
        bannerPosition = Int.MAX_VALUE / 2 - ceil(data.size.toDouble()/2).toInt()
        binding.viewPager.setCurrentItem(bannerPosition, false)

액티비티로 돌아가서 현재 페이저의 위치를 나타내줄 함수를 선언해준다 (처음위치를 데이터의 중간 Index로 정해 처음부터 좌우로 계속 스크롤이 가능하게 만든다)

setCurrentItem으로 현재 위치를 정해주고 false를 사용해 처음에 세팅될때 애니메이션이 작동하지 않도록 한다

추가로 페이지를 구별하기 위해 사용한 벡터이미지 point_double의 모습도 전환될때마다 변하게 해준다

            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                if (position % 2 == 0)binding.pointDouble.scaleX = -1.0f else binding.pointDouble.scaleX = 1.0f
            }

페이지가 2개 밖에 없어서 그냥 좌우로 뒤집었는데 만약 더 늘어난다면

when문으로 포지션별 변화를 주면 될것같다

Auto Scroll

앱들을 살펴보면 가만히 있을땐 자기혼자서 움직이고

화면을 스크롤하기 시작하면 행동이 멈추는걸 볼 수 있다

필요한건 Resume 상태 일때 실행가지

화면 스크롤 되기 까지 delay 주기

딜레이 후 페이지 postion 변경하기

사용자가 다시 화면을 만질때 작동 멈추기가 있다

lateinit var job : Job
...

binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                if (position % 2 == 0)binding.pointDouble.scaleX = -1.0f else binding.pointDouble.scaleX = 1.0f
            }

            override fun onPageScrollStateChanged(state: Int) {
                super.onPageScrollStateChanged(state)
                when (state) {
                    ViewPager2.SCROLL_STATE_IDLE ->{
                        if (job.isActive.not()) {
                            scrollJobCreate()
                        }
                    }

                    ViewPager2.SCROLL_STATE_DRAGGING -> job.cancel()
                }
            }

            ...

    fun scrollJobCreate() {
        job = lifecycleScope.launchWhenResumed {
            delay(3000)
            binding.viewPager.setCurrentItem(++bannerPosition, true)
        }
    }
    override fun onResume() {
        super.onResume()
        scrollJobCreate()
    }

    override fun onPause() {
        super.onPause()
        job.cancel()
    }

lifecycleScope를 이용해서 Resume 상태일때 애니메이션이 수행하도록 만든다

delay를 3000을 줘서 3초에 한번씩 아이템의 현재 위치를 변경해주고 ,smoothScroll을 ture로 설정해 애니메이션을 넣어준다

SCROLL_STATE_DRAGGING으로 사용자가 스크롤할때나

Activity가 Pause가 되면 job.cancel로 코루틴을 종료해주면 완성된다

0개의 댓글