이번 SOPT34기 합동세미나에서 디자인 파트에서 리디자인한 '카카오페이'를 구현하게 되었고, Home을 맡아서 구현하게 되었다. 그 중에서 소유하고 있는 계좌의 통합내역을 viewpager2와 dot indicator를 통해서 구현하였는데 그 과정을 적어본다.
보다시피 내역이 있고 그 밑에 dot indicator가 있는 방식이다.
하지만...! 뷰를 보면 그 다음 아이템이 살짝 보이는 구조이다. 그리고 내역이 스크롤 형식으로 되어있다.
그렇다면 리사이클러뷰랑 indicator을 연결해야하나?라는 고민도 하였지만 내가 사용한 spring dot indicator을 사용하기 위해서는 viewpager2를 필요로 한다. 그래서 리사이클러뷰랑 viewpager를 동기화 시키고 indicator을 연결해야한다.
즉 커스텀뷰라는 뜻이다...(혹시 다른 방법을 알고 계시다면 알려주세요😭)
그래서 이 상황을 디자인 선생님들께 말씀드리고 viewpager로 구현하는 걸로 허락을 받았다.
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "com.tbuonomo:dotsindicator:5.0"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp_home_total_content"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.tbuonomo.viewpagerdotsindicator.SpringDotsIndicator
android:id="@+id/vp_home_total_content_dots_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:dotsColor="@color/black"
app:dotsCornerRadius="4dp"
app:dotsSize="8dp"
app:dotsSpacing="6dp"
android:layout_marginTop="16dp"
app:dotsStrokeColor="@color/grey500"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vp_home_total_content"/>
<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"
android:background="@drawable/bg_grey200_radius_15dp">
<ImageView
android:id="@+id/iv_total_content_bank_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginVertical="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/tv_total_content_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Android.Body2Bold"
android:textColor="@color/black"
android:layout_marginStart="12dp"
app:layout_constraintTop_toTopOf="@id/iv_total_content_bank_logo"
app:layout_constraintStart_toEndOf="@id/iv_total_content_bank_logo"/>
<TextView
android:id="@+id/tv_total_content_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Android.Detail1Regular"
android:textColor="@color/grey600"
app:layout_constraintTop_toBottomOf="@id/tv_total_content_name"
app:layout_constraintStart_toStartOf="@id/tv_total_content_name"/>
<TextView
android:id="@+id/tv_total_content_cost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Android.Body2Bold"
android:textColor="@color/black"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="@id/iv_total_content_bank_logo"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/tv_total_content_send_or_deposit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Android.Detail1Regular"
android:textColor="@color/grey600"
app:layout_constraintTop_toBottomOf="@id/tv_total_content_cost"
app:layout_constraintEnd_toEndOf="@id/tv_total_content_cost"/>
</androidx.constraintlayout.widget.ConstraintLayout>
data class TotalContentItem(
val imageResId: Int,
val name: String,
val date: String,
val cost: String,
val sendOrDeposit: String
)
class HomeViewPagerViewHolder(private val binding: ItemTotalContentBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: TotalContentItem) {
with(binding) {
tvTotalContentName.text = item.name
tvTotalContentDate.text = item.date
tvTotalContentCost.text = item.cost
tvTotalContentSendOrDeposit.text = item.sendOrDeposit
ivTotalContentBankLogo.setImageResource(item.imageResId)
}
}
}
class HomeViewPagerAdapter(private val items: List<TotalContentItem>) :
RecyclerView.Adapter<HomeViewPagerAdapter.HomeViewPagerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeViewPagerViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ItemTotalContentBinding.inflate(inflater, parent, false)
binding.root.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
return HomeViewPagerViewHolder(binding)
}
override fun onBindViewHolder(holder: HomeViewPagerViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount(): Int {
return items.size
}
View의 레이아웃 파라미터를 설정하는 부분에서 'match_parent'로 해야한다.
java.lang.IllegalStateException: Pages must fill the whole ViewPager2
그걸 모르고 구현했을 때 이런 로그를 만나게 될 것이다...ㅎ
//HomeFragment.kt
private val mockData = listOf(
TotalContentItem("연진카뱅", "05.20", "-20,000", "송금", R.drawable.ic_kakaobank_logo),
TotalContentItem("문수카뱅", "05.21", "-19,900", "송금", R.drawable.ic_kakaobank_logo),
TotalContentItem("하늘카뱅", "05.22", "+19,900", "입금", R.drawable.ic_kakaobank_logo),
TotalContentItem("비상금", "05.24", "-100,000", "송금", R.drawable.ic_kakaobank_logo),
TotalContentItem("주택청약", "05.25", "-50,000", "송금", R.drawable.ic_kakaobank_logo),
TotalContentItem("생활비", "05.26", "+200,000", "입금", R.drawable.ic_kakaobank_logo)
) // 가짜 데이터 만들어주기
private fun setupViewPager() {
val adapter = HomeViewPagerAdapter(mockData)
binding.vpHomeTotalContent.adapter = adapter
binding.vpHomeTotalContentDotsIndicator.attachTo(binding.vpHomeTotalContent)
}
attachTo
를 이용해서 viewpager에 indicator을 붙여주면 된다.
Indicator에 대한 더 자세한 내용은 dotindicator GitHub 을 참고해주세요:)