1616
1717package androidx.compose.foundation.lazy.layout
1818
19- import androidx.collection.mutableIntIntMapOf
2019import androidx.collection.mutableIntObjectMapOf
2120import androidx.collection.mutableIntSetOf
2221import androidx.compose.foundation.ExperimentalFoundationApi
@@ -43,7 +42,7 @@ internal abstract class CacheWindowLogic(
4342 * Cache for items sizes in the current window. Holds sizes for both visible and non-visible
4443 * items
4544 */
46- private val windowCache = mutableIntIntMapOf ()
45+ private val windowCache = mutableIntObjectMapOf< CachedItem > ()
4746 private var previousPassDelta = 0f
4847 private var previousPassItemCount = UnsetItemCount
4948 private var hasUpdatedVisibleItemsOnce = false
@@ -120,15 +119,7 @@ internal abstract class CacheWindowLogic(
120119 * changed.
121120 */
122121 if (previousPassItemCount != UnsetItemCount && previousPassItemCount != totalItemsCount) {
123- debugLog { " Total Items Changed" }
124- shouldRefillWindow = true
125- prefetchWindowStartLine = prefetchWindowStartLine.coerceAtLeast(0 )
126- val lastLineIndex = getLastLineIndex()
127- if (lastLineIndex != InvalidIndex ) {
128- prefetchWindowEndLine = prefetchWindowEndLine.coerceAtMost(lastLineIndex)
129- }
130- /* * Free up the space so the fill will happen and not re-use old data. */
131- removeOutOfBoundsItems(prefetchWindowEndLine, itemsCount - 1 )
122+ onDatasetChangedSize()
132123 }
133124
134125 itemsCount = totalItemsCount
@@ -137,8 +128,8 @@ internal abstract class CacheWindowLogic(
137128 // by [cancelOutOfBounds]. If any items changed sizes we re-trigger the window filling
138129 // update.
139130 if (hasVisibleItems) {
140- forEachVisibleItem { index, mainAxisSize ->
141- if (index != InvalidIndex ) cacheVisibleItemsInfo(index, mainAxisSize)
131+ forEachVisibleItem { index, key, mainAxisSize ->
132+ if (index != InvalidIndex ) cacheVisibleItemsInfo(index, key, mainAxisSize)
142133 }
143134 if (shouldRefillWindow) {
144135 // refill window in accordance with last pass delta
@@ -234,6 +225,25 @@ internal abstract class CacheWindowLogic(
234225 }
235226 }
236227
228+ private fun CacheWindowScope.onDatasetChangedSize () {
229+ debugLog { " Total Items Changed" }
230+ shouldRefillWindow = true
231+ prefetchWindowStartLine = prefetchWindowStartLine.coerceAtLeast(0 )
232+ val lastLineIndex = getLastLineIndex()
233+ if (lastLineIndex != InvalidIndex ) {
234+ prefetchWindowEndLine = prefetchWindowEndLine.coerceAtMost(lastLineIndex)
235+ }
236+
237+ /* *
238+ * Resets the window state. We will refill the window on the direction of the last scroll.
239+ */
240+ if (previousPassDelta <= 0f ) {
241+ removeOutOfBoundsItems(lastVisibleLineIndex, itemsCount - 1 )
242+ } else {
243+ removeOutOfBoundsItems(0 , firstVisibleLineIndex)
244+ }
245+ }
246+
237247 fun resetStrategy () {
238248 prefetchWindowStartLine = Int .MAX_VALUE
239249 prefetchWindowEndLine = Int .MIN_VALUE
@@ -283,7 +293,7 @@ internal abstract class CacheWindowLogic(
283293 // If we get the same delta in the next frame, would we cover the extra space needed
284294 // to actually need this item? If so, mark it as urgent
285295 val isUrgent: Boolean =
286- if (prefetchWindowEndLine + 1 == visibleWindowEnd + 1 ) {
296+ if (prefetchWindowEndLine + 1 == visibleWindowEnd + 1 && scrollDelta != 0.0f ) {
287297 scrollDelta.absoluteValue >= mainAxisExtraSpaceEnd
288298 } else {
289299 false
@@ -315,7 +325,9 @@ internal abstract class CacheWindowLogic(
315325 // If we get the same delta in the next frame, would we cover the extra space needed
316326 // to actually need this item? If so, mark it as urgent
317327 val isUrgent: Boolean =
318- if (prefetchWindowStartLine - 1 == visibleWindowStart - 1 ) {
328+ if (
329+ prefetchWindowStartLine - 1 == visibleWindowStart - 1 && scrollDelta != 0.0f
330+ ) {
319331 scrollDelta.absoluteValue >= mainAxisExtraSpaceStart
320332 } else {
321333 false
@@ -358,7 +370,7 @@ internal abstract class CacheWindowLogic(
358370 while (prefetchWindowStartExtraSpace > 0 && prefetchWindowStartLine > 0 ) {
359371 val item =
360372 if (windowCache.containsKey(prefetchWindowStartLine - 1 )) {
361- windowCache[prefetchWindowStartLine - 1 ]
373+ windowCache[prefetchWindowStartLine - 1 ]!! .mainAxisSize
362374 } else {
363375 break
364376 }
@@ -373,7 +385,7 @@ internal abstract class CacheWindowLogic(
373385 while (prefetchWindowEndExtraSpace > 0 && prefetchWindowEndLine < itemsCount - 1 ) {
374386 val item =
375387 if (windowCache.containsKey(prefetchWindowEndLine + 1 )) {
376- windowCache[prefetchWindowEndLine + 1 ]
388+ windowCache[prefetchWindowEndLine + 1 ]!! .mainAxisSize
377389 } else {
378390 break
379391 }
@@ -386,13 +398,16 @@ internal abstract class CacheWindowLogic(
386398
387399 private fun CacheWindowScope.getItemSizeOrPrefetch (index : Int , isUrgent : Boolean ): Int {
388400 return if (windowCache.containsKey(index)) {
389- windowCache[index]
401+ debugLog { " Item $index is Cached!" }
402+ windowCache[index]!! .mainAxisSize
390403 } else if (prefetchWindowHandles.containsKey(index)) {
391404 // item is scheduled but didn't finish yet
405+ debugLog { " Item=$index is already scheduled. isUrgent=$isUrgent " }
392406 if (isUrgent) prefetchWindowHandles[index]?.fastForEach { it.markAsUrgent() }
393407 InvalidItemSize
394408 } else {
395409 // item is not scheduled
410+ debugLog { " Scheduling Prefetching for Item=$index . isUrgent=$isUrgent " }
396411 prefetchWindowHandles[index] =
397412 schedulePrefetch(index) { prefetchedIndex, size ->
398413 onItemPrefetched(prefetchedIndex, size)
@@ -404,7 +419,7 @@ internal abstract class CacheWindowLogic(
404419
405420 /* * Grows the window with measured items and prefetched items. */
406421 private fun cachePrefetchedItem (index : Int , size : Int ) {
407- windowCache[index] = size
422+ windowCache[index] = updateOrCreateCachedItem(index, size, CachedItem . NoKey )
408423 if (index > prefetchWindowEndLine) {
409424 prefetchWindowEndLine = index
410425 prefetchWindowEndExtraSpace - = size
@@ -414,18 +429,34 @@ internal abstract class CacheWindowLogic(
414429 }
415430 }
416431
432+ private fun updateOrCreateCachedItem (index : Int , size : Int , key : Any ): CachedItem {
433+ val cachedItem = windowCache[index]
434+ return if (cachedItem != null ) {
435+ cachedItem.mainAxisSize = size
436+ cachedItem.key = key
437+ cachedItem
438+ } else {
439+ CachedItem (CachedItem .NoKey , size)
440+ }
441+ }
442+
417443 /* *
418444 * When caching visible items we need to check if the existing item changed sizes. If so, we
419445 * will set [shouldRefillWindow] which will trigger a complete window filling and cancel any out
420- * of bounds requests.
446+ * of bounds requests. The same is valid if items are replaced (have the same size by key
447+ * changed).
421448 */
422- private fun cacheVisibleItemsInfo (index : Int , size : Int ) {
423- debugLog { " cacheVisibleItemsInfo item=$index size=$size " }
424- if (windowCache.containsKey(index) && windowCache[index] != size) {
425- shouldRefillWindow = true
449+ private fun cacheVisibleItemsInfo (index : Int , key : Any , size : Int ) {
450+ debugLog { " cacheVisibleItemsInfo item=$index size=$size key=$key " }
451+ if (windowCache.containsKey(index)) {
452+ val cachedSize = windowCache[index]!! .mainAxisSize
453+ val cachedKey = windowCache[index]!! .key
454+ if (cachedSize != size || cachedKey != key) {
455+ shouldRefillWindow = true
456+ }
426457 }
427458
428- windowCache[index] = size
459+ windowCache[index] = updateOrCreateCachedItem(index, size, key)
429460 // We're caching a visible item, remove its handle since we won't need it anymore.
430461 prefetchWindowStartLine = minOf(prefetchWindowStartLine, index)
431462 prefetchWindowEndLine = maxOf(prefetchWindowEndLine, index)
@@ -439,6 +470,8 @@ internal abstract class CacheWindowLogic(
439470
440471 windowCache.forEachKey { if (it in startLine.. endLine) indicesToRemove.add(it) }
441472
473+ debugLog { " Indices to remove=$indicesToRemove " }
474+
442475 indicesToRemove.forEach {
443476 prefetchWindowHandles.remove(it)?.fastForEach { it.cancel() }
444477 windowCache.remove(it)
@@ -501,15 +534,19 @@ internal interface CacheWindowScope {
501534
502535 fun getVisibleItemLine (indexInVisibleLines : Int ): Int
503536
537+ fun getVisibleLineKey (indexInVisibleLines : Int ): Any
538+
504539 fun getLastIndexInLine (lineIndex : Int ): Int
505540
506541 fun getLastLineIndex (): Int
507542}
508543
509544internal inline fun CacheWindowScope.forEachVisibleItem (
510- action : (itemIndex: Int , mainAxisSize: Int ) -> Unit
545+ action : (itemIndex: Int , itemKey: Any , mainAxisSize: Int ) -> Unit
511546) {
512- repeat(visibleLineCount) { action(getVisibleItemLine(it), getVisibleItemSize(it)) }
547+ repeat(visibleLineCount) {
548+ action(getVisibleItemLine(it), getVisibleLineKey(it), getVisibleItemSize(it))
549+ }
513550}
514551
515552private const val InvalidItemSize = - 1
@@ -523,3 +560,7 @@ private inline fun debugLog(generateMsg: () -> String) {
523560 println (" CacheWindowLogic: ${generateMsg()} " )
524561 }
525562}
563+
564+ internal class CachedItem (var key : Any , var mainAxisSize : Int ) {
565+ companion object NoKey
566+ }
0 commit comments