fix(widget): 배너 가상 목록 갱신을 안정화한다
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.widget.banner
|
package kr.co.vividnext.sodalive.v2.widget.banner
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -38,19 +39,17 @@ class BannerAdapter(
|
|||||||
this.items.addAll(items)
|
this.items.addAll(items)
|
||||||
val newItemCount = itemCount
|
val newItemCount = itemCount
|
||||||
when {
|
when {
|
||||||
previousItemCount == 0 && newItemCount > 0 -> notifyItemRangeInserted(0, newItemCount)
|
previousItemCount == 0 && newItemCount == 1 -> notifyItemInserted(0)
|
||||||
previousItemCount > 0 && newItemCount == 0 -> notifyItemRangeRemoved(0, previousItemCount)
|
previousItemCount == 1 && newItemCount == 0 -> notifyItemRemoved(0)
|
||||||
previousItemCount == newItemCount -> notifyItemRangeChanged(0, newItemCount)
|
previousItemCount == 1 && newItemCount == 1 -> notifyItemChanged(0)
|
||||||
else -> {
|
else -> notifyVirtualItemsChanged()
|
||||||
notifyItemRangeRemoved(0, previousItemCount)
|
|
||||||
notifyItemRangeInserted(0, newItemCount)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setItemSizePx(itemSizePx: Int) {
|
fun setItemSizePx(itemSizePx: Int) {
|
||||||
|
val shouldNotify = this.itemSizePx != itemSizePx
|
||||||
this.itemSizePx = itemSizePx
|
this.itemSizePx = itemSizePx
|
||||||
notifyItemsChanged()
|
if (shouldNotify) notifyVisibleItemsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOnClickItem(listener: ((BannerItem) -> Unit)?) {
|
fun setOnClickItem(listener: ((BannerItem) -> Unit)?) {
|
||||||
@@ -62,8 +61,9 @@ class BannerAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setPreviewImageResource(resId: Int) {
|
fun setPreviewImageResource(resId: Int) {
|
||||||
|
val shouldNotify = previewImageResId != resId
|
||||||
previewImageResId = resId
|
previewImageResId = resId
|
||||||
notifyItemsChanged()
|
if (shouldNotify) notifyVisibleItemsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toRealIndex(position: Int): Int = if (items.isEmpty()) 0 else position % items.size
|
fun toRealIndex(position: Int): Int = if (items.isEmpty()) 0 else position % items.size
|
||||||
@@ -106,9 +106,17 @@ class BannerAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyItemsChanged() {
|
private fun notifyVisibleItemsChanged() {
|
||||||
val count = itemCount
|
when (itemCount) {
|
||||||
if (count > 0) notifyItemRangeChanged(0, count)
|
0 -> Unit
|
||||||
|
1 -> notifyItemChanged(0)
|
||||||
|
else -> notifyVirtualItemsChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun notifyVirtualItemsChanged() {
|
||||||
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.widget.banner
|
package kr.co.vividnext.sodalive.v2.widget.banner
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.settings.event.EventItem
|
||||||
|
|
||||||
data class BannerItem(
|
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 (previewImageResId != 0) adapter.setPreviewImageResource(previewImageResId)
|
||||||
if (previewItemCount > 0) {
|
if (previewItemCount > 0) {
|
||||||
items = List(previewItemCount) { index ->
|
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
|
currentIndex = BannerState.from(previewItemCount, previewCurrentIndex).currentIndex
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
|
|||||||
@@ -67,6 +67,74 @@ class BannerViewTest {
|
|||||||
assertEquals(items[1], clickedItem)
|
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
|
@Test
|
||||||
fun `배너 view는 빈 목록이면 숨기고 단일 목록이면 counter를 숨긴다`() {
|
fun `배너 view는 빈 목록이면 숨기고 단일 목록이면 counter를 숨긴다`() {
|
||||||
val view = inflateBannerView()
|
val view = inflateBannerView()
|
||||||
@@ -533,8 +601,11 @@ class BannerViewTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sampleItem(id: String) = BannerItem(
|
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)
|
private fun exactly(size: Int): Int = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
|
||||||
|
|||||||
Reference in New Issue
Block a user