아래와 같은 UI를 구현할때 어떻게 하는게 좋을까?
보통 기본적으로는 ScrollView 안에 TextView(헤더 내용), RecyclerView(이미지), TextView(하단 내용)으로 구성한다.
이렇게 하면 ScrollView의 세로 스크롤과 RecyclerView의 세로 스크롤이 겹치기 때문에 스크롤 이슈가 있을 수 있다.
또한 스크롤 뷰에
android:fillViewport="true" 속성을 넣게 되면
스크롤뷰가 content높이 계산을 하기 위해 RecyclerView가 모든 아이템을 그리게되는데 viewholder재사용을 하지 않아 RecyclerView의 이점을 얻을 수 없는 문제도 있다.
그래서 하나의 리사이클러뷰로 구조를 변경하는것이 좋다 ( ViewType 쪼개기 )
상단 TextView (헤더), ImageView(이미지), TextView(푸터) 로 3가지 타입으로 나눈다.
해당 형태의 모델로 변경
sealed class NoticeDetailContentUiModel : Identifiable {
data class Header(
val content: String
) : NoticeDetailContentUiModel() {
override val identifier: Any
get() = "Header"
}
data class Image(
val id: Long,
val src: String
) : NoticeDetailContentUiModel() {
override val identifier: Any
get() = id
}
data class Footer(
val description: String?
) : NoticeDetailContentUiModel() {
override val identifier: Any
get() = "Footer"
}
}
Adapter 에서 다음과 같이 설정
override fun getItemViewType(position: Int, item: NoticeDetailContentUiModel): ViewType {
return when (item) {
is NoticeDetailContentUiModel.Header -> ViewType.HEADER
is NoticeDetailContentUiModel.Image -> ViewType.IMAGE
is NoticeDetailContentUiModel.Footer -> ViewType.FOOTER
}
}
override fun onCreateViewHolder(
layoutInflater: LayoutInflater,
parent: ViewGroup,
viewType: ViewType
): BaseViewHolder<NoticeDetailContentUiModel> {
return when (viewType) {
ViewType.HEADER -> ItemViewHolder(
layoutInflater.inflate(
R.layout.item_notice_detail_header,
parent,
false
)
)
ViewType.IMAGE -> ItemViewHolder(
layoutInflater.inflate(
R.layout.item_notice_detail_image,
parent,
false
)
)
ViewType.FOOTER -> ItemViewHolder(
layoutInflater.inflate(
R.layout.item_notice_detail_footer,
parent,
false
)
)
}
}
enum class ViewType {
HEADER, IMAGE, FOOTER;
}
응용 버전
이미지와 같이 ViewPager + RecyclerView (GridLayoutManager)의 경우에는 어떻게 해야할까?
1. 모델
sealed class HomeUiModel : Identifiable {
data class BannerList(
val banners: List<Banner>
) : HomeUiModel() {
override val identifier: Any
get() = "banners"
data class Banner(
val id: Long,
val image: String
) : Identifiable {
override val identifier: Any
get() = id
}
}
data class Product(
val id: Long,
val image: String
) : HomeUiModel() {
override val identifier: Any
get() = id
}
}
2. UI (xml)
- BannerList
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp_banner"
android:layout_width="0dp"
android:layout_height="98dp" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tl_banner"
android:layout_width="wrap_content"
android:layout_height="30dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Banner
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent" />
- Product
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->item.onClick.invoke()}">
<ImageView
android:id="@+id/iv_product"
android:layout_width="0dp"
android:layout_height="0dp"/>
...
</androidx.constraintlayout.widget.ConstraintLayout>
- Adapter
override fun getItemViewType(position: Int, item: HomeUiModel): ViewType {
return when (item) {
is HomeUiModel.BannerList -> ViewType.BANNER_LIST
is HomeUiModel.Product -> ViewType.PRODUCT
}
}
override fun onCreateViewHolder(
layoutInflater: LayoutInflater,
parent: ViewGroup,
viewType: ViewType
): BaseViewHolder<HomeUiModel> {
return when (viewType) {
ViewType.BANNER_LIST -> BannerViewHolder(
ItemHomeBannerListBinding.inflate(
layoutInflater,
parent,
false
)
)
ViewType.PRODUCT -> ProductListViewHolder(
ItemHomeProductBinding.inflate(
layoutInflater,
parent,
false
)
)
}
}
class BannerViewHolder(val binding: ItemHomeBannerListBinding) :
BaseViewHolder<HomeUiModel>(binding.root) {
init {
with(binding) {
vpBanner.adapter = HomeBannerListAdapter()
TabLayoutMediator(tlBanner, vpBanner) { tab, _ ->
vpBanner.currentItem = tab.position
}.attach()
}
}
}
enum class ViewType {
BANNER_LIST, PRODUCT
}
* 전체 리사이클러뷰의 구조는 GridLayoutManger, spanCount="2"로 지정한다.
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
-> 뷰타입이 BANNER_LIST 인 경우엔 spanSize : 2 (늘리기)
PRODUCT 인 경우엔 1 (그대로)
(layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (outerAdapter.getItemViewTypeAsEnum(position)) {
HomeOuterAdapter.ViewType.BANNER_LIST -> 2
HomeOuterAdapter.ViewType.PRODUCT -> 1
}
}
}
'Android' 카테고리의 다른 글
시간 간격 계산하여 x분전, x시간 전으로 변형하기 (0) | 2021.12.24 |
---|---|
API Response Map 형태로 받기 ( Retrofit ) (0) | 2021.10.04 |
카카오 로그인 Trouble Shooting (0) | 2021.09.29 |
어댑터 초기화후 데이터 넣기 (0) | 2021.09.10 |
커스텀 이미지피커 [+페이징,앨범] (10, 11 대응) (5) | 2021.09.07 |