① Module 수준의 build.gradle 파일에 아래의 의존성을 추가한다.
implementation ("androidx.viewpager2:viewpager2:1.0.0")
② fragment_home.xml의 배너는 현재 ImageView로 나타나있다. 이를 ViewPager로 바꿔주기 위해 주석으로 변경한다.
<!--<ImageView-->
<!--android:id="@+id/home_banner_vp"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="0dp"-->
<!--android:adjustViewBounds="true"-->
<!--android:src="@drawable/img_home_viewpager_exp"-->
<!--android:layout_marginTop="15dp"-->
<!--app:layout_constraintStart_toStartOf="parent"-->
<!--app:layout_constraintEnd_toEndOf="parent"-->
<!--app:layout_constraintTop_toBottomOf="@id/home_today_music_oversea_hs" />-->
③ 기존 ImageView가 있던 자리에 ViewPager 태그를 아래와 같이 추가한다.
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_banner_vp"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="15dp"
app:layout_constraintEnd_toEndOf="@+id/home_pannel_btn_setting_iv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/home_today_music_oversea_hs"/>
④ default 패키지 하위로, BannerVPAdapter라는 kotlin class를 추가한다.
class BannerVPAdapter(fragment : Fragment) : FragmentStateAdapter(fragment) {
}
⑤ BannerVBAdpater에 커서를 올려두고, 우클릭한 후 Show Context Actions > Implement members를 클릭하면 override 메서드를 자동 생성해준다.
⑥ BannerVPAdapter에 아래의 내용을 입력한다.
class BannerVPAdapter(fragment : Fragment) : FragmentStateAdapter(fragment) {
private val fragmentList : ArrayList<Fragment> = ArrayList()
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
fun addFragment(fragment: Fragment) {
fragmentList.add(fragment)
notifyItemInserted(fragmentList.size-1)
}
}
⑦ 이제 fragmentList에 넣어줄 Fragment를 만들어야 한다. default 패키지 하위로, BannerFragment를 추가한다.
⑧ fragment_banner.xml 파일에 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/banner_image_iv"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="fitXY"
android:src="@drawable/img_home_viewpager_exp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
⑨ ViewBinding을 사용하기 위해 Module 수준의 build.gradle 파일에 아래의 내용을 추가한다.
android {
...
buildFeatures{
dataBinding = true // 데이터 바인딩
}
buildFeatures {
viewBinding = true // 뷰 바인딩
}
}
⑩ BannerFragment에 아래의 내용을 입력한다.
class BannerFragment(val imgRes : Int) : Fragment() {
lateinit var binding : FragmentBannerBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBannerBinding.inflate(inflater, container, false)
binding.bannerImageIv.setImageResource(imgRes)
return binding.root
}
}
⑪ 이제 HomeFragment에서 ViewPager와 VPAdapter를 연결해야 한다.
val bannerAdapter = BannerVPAdapter(this)
bannerAdapter.addFragment(BannerFragment(R.drawable.img_home_viewpager_exp))
bannerAdapter.addFragment(BannerFragment(R.drawable.img_home_viewpager_exp2))
binding.homeBannerVp.adapter = bannerAdapter
binding.homeBannerVp.orientation = ViewPager2.ORIENTATION_HORIZONTAL
return binding.root
이제 코드를 실행해보면 배너가 ViewPager로 표시되는 것을 확인할 수 있을 것이다.
① fragment_home.xml의 배경 패널은 현재 ImageView이다. 이 ImageView를 ViewPager로 바꿔주어야 하므로 기존 ImageView 태그를 삭제한다.
② 기존 ImageView가 있던 자리에 ViewPager 태그를 아래와 같이 추가한다.
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_pannel_background_vp"
android:layout_width="match_parent"
android:layout_height="430dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
③ PannelVPAdapter라는 kotlin class를 추가한다.
class PannelVpAdapter (fragment : Fragment) : FragmentStateAdapter(fragment) {
private val fragmentList : ArrayList<Fragment> = ArrayList()
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
fun addFragment(fragment: Fragment) {
fragmentList.add(fragment)
notifyItemInserted(fragmentList.size-1)
}
}
④ 이제 fragmentList에 넣어줄 Fragment를 만들어야 한다. PannelFragment를 추가하자.
⑤ fragment_pannel.xml에 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageView
android:id="@+id/pannel_image_iv"
android:layout_width="match_parent"
android:layout_height="430dp"
android:scaleType="fitXY"
android:src="@drawable/img_first_album_default"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
⑥ PannelFragment에 아래의 내용을 입력한다.
class PannelFragment(val imgRes : Int) : Fragment() {
lateinit var binding : FragmentPannelBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentPannelBinding.inflate(inflater, container, false)
binding.pannelImageIv.setImageResource(imgRes)
return binding.root
}
}
⑦ 마지막으로, HomeFragment에 ViewPager와 VPAdapter를 연결하는 내용을 추가한다.
val pannelAdpater = PannelVpAdapter(this)
pannelAdpater.addFragment(PannelFragment(R.drawable.img_first_album_default))
pannelAdpater.addFragment(PannelFragment(R.drawable.img_first_album_default))
binding.homePannelBackgroundVp.adapter = pannelAdpater
binding.homePannelBackgroundVp.orientation = ViewPager2.ORIENTATION_HORIZONTAL
return binding.root
이제 코드를 실행시켜보면 HomeFragment의 Pannel에 ViewPager가 적용된 것을 확인할 수 있을 것이다.
① CircleIndicator 라이브러리를 이용하기 위해 Module 수준의 build.gradle 파일에 아래의 의존성을 추가한다.
implementation ("me.relex:circleindicator:2.1.6")
② drawable 디렉토리 하위로, blue_radius라는 이름의 리소스 파일을 추가한다.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/select_color"/>
</shape>
③ 이번엔 drawable 디렉토리 하위로, gray_radius라는 이름의 리소스 파일을 추가한다.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/gray_color"/>
</shape>
④ Banner와 Pannel에 모두 CircleIndicator를 적용하도록 하자. fragment_home.xml 파일의 Banner와 Pannel의 ViewPager 아래에 각각 아래와 같이 CircleIndicator3 태그를 추가한다.
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_pannel_background_vp"
android:layout_width="match_parent"
android:layout_height="430dp"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<me.relex.circleindicator.CircleIndicator3
android:id="@+id/home_pannel_indicator"
android:layout_width="match_parent"
android:layout_height="32dp"
app:ci_drawable_unselected="@drawable/gray_radius"
app:ci_drawable="@drawable/blue_radius"
app:ci_animator="@animator/scale_with_alpha"
app:layout_constraintTop_toBottomOf="@id/home_pannel_background_vp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
...
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_banner_vp"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="15dp"
app:layout_constraintEnd_toEndOf="@+id/home_pannel_btn_setting_iv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/home_today_music_oversea_hs"/>
<me.relex.circleindicator.CircleIndicator3
android:id="@+id/home_banner_indicator"
android:layout_width="match_parent"
android:layout_height="32dp"
app:ci_drawable_unselected="@drawable/gray_radius"
app:ci_drawable="@drawable/blue_radius"
app:ci_animator="@animator/scale_with_alpha"
app:layout_constraintTop_toBottomOf="@id/home_banner_vp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
⑤ HomeFragment에서 ViewPager와 Indicator를 연결한다.
binding.homeBannerIndicator.setViewPager(binding.homeBannerVp)
binding.homePannelIndicator.setViewPager(binding.homePannelBackgroundVp)
코드를 실행시켜보자. HomeFragment의 Pannel과 Banner의 ViewPager에 Indicator가 적용된 것을 확인할 수 있다.
① HomeFragment를 아래와 같이 수정한다.
class HomeFragment : Fragment() {
lateinit var binding : FragmentHomeBinding
private val timer = Timer()
private val handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false)
...
val bannerAdapter = BannerVPAdapter(this)
bannerAdapter.addFragment(BannerFragment(R.drawable.img_home_viewpager_exp))
bannerAdapter.addFragment(BannerFragment(R.drawable.img_home_viewpager_exp2))
binding.homeBannerVp.adapter = bannerAdapter
binding.homeBannerVp.orientation = ViewPager2.ORIENTATION_HORIZONTAL
binding.homeBannerIndicator.setViewPager(binding.homeBannerVp)
autoSlide(bannerAdapter)
val pannelAdpater = PannelVpAdapter(this)
pannelAdpater.addFragment(PannelFragment(R.drawable.img_first_album_default))
pannelAdpater.addFragment(PannelFragment(R.drawable.img_first_album_default))
binding.homePannelBackgroundVp.adapter = pannelAdpater
binding.homePannelBackgroundVp.orientation = ViewPager2.ORIENTATION_HORIZONTAL
binding.homePannelIndicator.setViewPager(binding.homePannelBackgroundVp)
return binding.root
}
private fun autoSlide(adapter: BannerVPAdapter) {
timer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
handler.post {
val nextItem = binding.homeBannerVp.currentItem + 1
if (nextItem < adapter.itemCount) {
binding.homeBannerVp.currentItem = nextItem
} else {
binding.homeBannerVp.currentItem = 0 // 순환
}
}
}
}, 3000, 3000)
}
}
코드를 실행시켜보면 3초마다 배너 이미지가 변경되는 것을 확인할 수 있다.
① 기존에는 fragment_album.xml 파일에 수록곡 / 상세정보 / 영상을 TextView의 LinearLayout으로 나타냈었다. 이것을 TabLayout으로 변경해주도록 하겠다.
<LinearLayout
android:id="@+id/linearLayout4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@+id/album_album_iv"
app:layout_constraintStart_toStartOf="@+id/album_album_iv"
app:layout_constraintTop_toBottomOf="@+id/album_album_iv">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="수록곡"
android:textColor="@color/select_color"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="100dp"
android:text="상세정보"
android:textColor="@color/black" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="100dp"
android:text="영상"
android:textColor="@color/black" />
</LinearLayout>
<View
android:id="@+id/album_line_view"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="15dp"
android:background="#dcdcdc"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout4" />
② 위에서 삭제한 내용의 아래 부분은 나중에 새로운 Fragment에 넣어줄 것이기 때문에 일단은 모두 주석 처리한다.
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
③ 주석 처리한 부분 바로 위에 아래의 내용을 추가한다.
<com.google.android.material.tabs.TabLayout
android:id="@+id/album_content_tb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="@+id/album_album_iv"
app:layout_constraintStart_toStartOf="@+id/album_album_iv"
app:layout_constraintTop_toBottomOf="@+id/album_album_iv"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/album_content_vp"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@+id/album_content_tb"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
④ SongFragment, DetailFragment, VideoFragment를 추가한다.
⑤ AlbumVPAdpater라는 kotlin class를 추가하고 아래의 내용을 입력한다.
class AlbumVPAdapter(fragment : Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when(position) {
0 -> SongFragment()
1 -> DetailFragment()
else -> VideoFragment()
}
}
}
⑥ fragment_album.xml을 아래와 같이 수정한다.
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".SongFragment">
...
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
android:orientation="vertical"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
코드를 실행시켜보면, AlbumFragment의 ViewPager 태그에 SongFragment가 나타나는 것을 확인할 수 있다. 또한 좌측으로 swipe하여 다른 Fragment로 전환할 수도 있다.
① AlbumFragment에 TabLayout에 사용될 Label을 지정하기 위해 arrayList를 만들고, TabLayout과 ViewPager를 연결해야 한다.
private val information = arrayListOf("수록곡", "상세정보", "영상")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
...
val albumAdapter = AlbumVPAdapter(this)
binding.albumContentVp.adapter = albumAdapter
TabLayoutMediator(binding.albumContentTb, binding.albumContentVp) { tab, position ->
tab.text = information[position]
}.attach()
return binding.root
}
② Indicator의 길이가 Tab을 꽉 채우는게(기본값) 아닌 글자 크기에 알맞게 채워지도록 fragment_album.xml의 TabLayout 태그에 아래의 속성을 추가한다.
app:tabIndicatorFullWidth="false"
③ 또한, Indicator의 색상을 변경하는 것도 가능하다. TabLayout 태그에 아래의 속성을 추가하자.
// 선택된 탭의 텍스트 색상 변경
app:tabSelectedTextColor="#3f3fff"
// 선택된 탭의 Indicator 색상 변경
app:tabIndicatorColor="#3f3fff"
④ 탭을 선택할 때 퍼지는 듯한 애니메이션이 나오는데, 이를 Ripple 효과라고 한다. 만약 이 효과를 제거하고 싶다면, RippleColor를 투명하게 바꿔주면 된다. TabLayout 태그에 아래의 속성을 추가하자.
app:tabRippleColor="@color/transparent"
SongFragment의 onCreateView 메서드를 아래와 같이 수정한다.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentSongBinding.inflate(inflater, container, false)
binding.songMixoffTg.setOnClickListener {
binding.songMixoffTg.visibility = View.GONE
binding.songMixonTg.visibility = View.VISIBLE
}
binding.songMixonTg.setOnClickListener {
binding.songMixoffTg.visibility = View.VISIBLE
binding.songMixonTg.visibility = View.GONE
}
return binding.root
}
코드를 실행시킨 후 SongFragment의 토글 버튼을 클릭해보자. 클릭할 때마다 토글의 ON/OFF가 반복될 것이다.