fix(widget): 배너 가상 목록 갱신을 안정화한다
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget.banner
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Outline
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -38,19 +39,17 @@ class BannerAdapter(
|
||||
this.items.addAll(items)
|
||||
val newItemCount = itemCount
|
||||
when {
|
||||
previousItemCount == 0 && newItemCount > 0 -> notifyItemRangeInserted(0, newItemCount)
|
||||
previousItemCount > 0 && newItemCount == 0 -> notifyItemRangeRemoved(0, previousItemCount)
|
||||
previousItemCount == newItemCount -> notifyItemRangeChanged(0, newItemCount)
|
||||
else -> {
|
||||
notifyItemRangeRemoved(0, previousItemCount)
|
||||
notifyItemRangeInserted(0, newItemCount)
|
||||
}
|
||||
previousItemCount == 0 && newItemCount == 1 -> notifyItemInserted(0)
|
||||
previousItemCount == 1 && newItemCount == 0 -> notifyItemRemoved(0)
|
||||
previousItemCount == 1 && newItemCount == 1 -> notifyItemChanged(0)
|
||||
else -> notifyVirtualItemsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun setItemSizePx(itemSizePx: Int) {
|
||||
val shouldNotify = this.itemSizePx != itemSizePx
|
||||
this.itemSizePx = itemSizePx
|
||||
notifyItemsChanged()
|
||||
if (shouldNotify) notifyVisibleItemsChanged()
|
||||
}
|
||||
|
||||
fun setOnClickItem(listener: ((BannerItem) -> Unit)?) {
|
||||
@@ -62,8 +61,9 @@ class BannerAdapter(
|
||||
}
|
||||
|
||||
fun setPreviewImageResource(resId: Int) {
|
||||
val shouldNotify = previewImageResId != resId
|
||||
previewImageResId = resId
|
||||
notifyItemsChanged()
|
||||
if (shouldNotify) notifyVisibleItemsChanged()
|
||||
}
|
||||
|
||||
fun toRealIndex(position: Int): Int = if (items.isEmpty()) 0 else position % items.size
|
||||
@@ -106,9 +106,17 @@ class BannerAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyItemsChanged() {
|
||||
val count = itemCount
|
||||
if (count > 0) notifyItemRangeChanged(0, count)
|
||||
private fun notifyVisibleItemsChanged() {
|
||||
when (itemCount) {
|
||||
0 -> Unit
|
||||
1 -> notifyItemChanged(0)
|
||||
else -> notifyVirtualItemsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun notifyVirtualItemsChanged() {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package kr.co.vividnext.sodalive.v2.widget.banner
|
||||
|
||||
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||
|
||||
data class BannerItem(
|
||||
val bannerId: String?,
|
||||
val imageUrl: String
|
||||
val imageUrl: String,
|
||||
val eventItem: EventItem?,
|
||||
val creatorId: Long?,
|
||||
val seriesId: Long?,
|
||||
val link: String?
|
||||
)
|
||||
|
||||
@@ -146,7 +146,13 @@ class BannerView @JvmOverloads constructor(
|
||||
if (previewImageResId != 0) adapter.setPreviewImageResource(previewImageResId)
|
||||
if (previewItemCount > 0) {
|
||||
items = List(previewItemCount) { index ->
|
||||
BannerItem(bannerId = "preview-$index", imageUrl = "")
|
||||
BannerItem(
|
||||
imageUrl = "",
|
||||
eventItem = null,
|
||||
creatorId = null,
|
||||
seriesId = null,
|
||||
link = "preview-$index"
|
||||
)
|
||||
}
|
||||
currentIndex = BannerState.from(previewItemCount, previewCurrentIndex).currentIndex
|
||||
visibility = VISIBLE
|
||||
|
||||
@@ -67,6 +67,74 @@ class BannerViewTest {
|
||||
assertEquals(items[1], clickedItem)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adapter는 carousel 설정 시 max range notify를 호출하지 않는다`() {
|
||||
val adapter = BannerAdapter()
|
||||
var insertedItemCount = 0
|
||||
var changedItemCount = 0
|
||||
var dataSetChangedCount = 0
|
||||
adapter.registerAdapterDataObserver(
|
||||
object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
insertedItemCount = itemCount
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
changedItemCount = itemCount
|
||||
}
|
||||
|
||||
override fun onChanged() {
|
||||
dataSetChangedCount += 1
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
adapter.submitItems(listOf(sampleItem("1"), sampleItem("2")))
|
||||
adapter.setItemSizePx(100)
|
||||
|
||||
assertEquals(0, insertedItemCount)
|
||||
assertEquals(0, changedItemCount)
|
||||
assertEquals(2, dataSetChangedCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `adapter는 단일 배너 설정 시 specific notify를 호출한다`() {
|
||||
val adapter = BannerAdapter()
|
||||
var insertedItemCount = 0
|
||||
var changedItemCount = 0
|
||||
var removedItemCount = 0
|
||||
var dataSetChangedCount = 0
|
||||
adapter.registerAdapterDataObserver(
|
||||
object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
insertedItemCount += itemCount
|
||||
}
|
||||
|
||||
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
|
||||
changedItemCount += itemCount
|
||||
}
|
||||
|
||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||
removedItemCount += itemCount
|
||||
}
|
||||
|
||||
override fun onChanged() {
|
||||
dataSetChangedCount += 1
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
adapter.submitItems(listOf(sampleItem("1")))
|
||||
adapter.setItemSizePx(100)
|
||||
adapter.submitItems(listOf(sampleItem("2")))
|
||||
adapter.submitItems(emptyList())
|
||||
|
||||
assertEquals(1, insertedItemCount)
|
||||
assertEquals(2, changedItemCount)
|
||||
assertEquals(1, removedItemCount)
|
||||
assertEquals(0, dataSetChangedCount)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `배너 view는 빈 목록이면 숨기고 단일 목록이면 counter를 숨긴다`() {
|
||||
val view = inflateBannerView()
|
||||
@@ -533,8 +601,11 @@ class BannerViewTest {
|
||||
}
|
||||
|
||||
private fun sampleItem(id: String) = BannerItem(
|
||||
bannerId = id,
|
||||
imageUrl = "https://example.com/banner-$id.png"
|
||||
imageUrl = "https://example.com/banner-$id.png",
|
||||
eventItem = null,
|
||||
creatorId = id.toLongOrNull(),
|
||||
seriesId = null,
|
||||
link = "https://example.com/banner-$id"
|
||||
)
|
||||
|
||||
private fun exactly(size: Int): Int = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
|
||||
|
||||
Reference in New Issue
Block a user