1 /*
2 * Copyright 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.compose.foundation.lazy.layout
18
19 import androidx.annotation.FloatRange
20 import androidx.compose.foundation.ExperimentalFoundationApi
21 import androidx.compose.runtime.Stable
22 import androidx.compose.ui.unit.Density
23 import androidx.compose.ui.unit.Dp
24 import androidx.compose.ui.unit.dp
25 import kotlin.math.roundToInt
26
27 /**
28 * Represents an out of viewport area of a Lazy Layout where items should be cached. Items will be
29 * prepared in the Cache Window area in advance to improve scroll performance.
30 */
31 @ExperimentalFoundationApi
32 @Stable
33 interface LazyLayoutCacheWindow {
34
35 /**
36 * Calculates the prefetch window area in pixels for prefetching on the scroll direction, "ahead
37 * window". The prefetch window strategy will prepare items in the ahead area in advance s they
38 * are ready to be used when they become visible.
39 *
40 * @param viewport The size of the viewport in this Lazy Layout in pixels.
41 */
Densitynull42 fun Density.calculateAheadWindow(viewport: Int): Int = 0
43
44 /**
45 * Calculates the window area in pixels for keeping items in the scroll counter direction,
46 * "behind window". Items in the behind window will not be disposed and can be accessed more
47 * quickly if they become visible again.
48 *
49 * @param viewport The size of the viewport in this Lazy Layout in pixels.
50 */
51 fun Density.calculateBehindWindow(viewport: Int): Int = 0
52 }
53
54 /**
55 * A Dp based [LazyLayoutCacheWindow].
56 *
57 * @param ahead The size of the ahead window to be used as per
58 * [LazyLayoutCacheWindow.calculateAheadWindow].
59 * @param behind The size of the behind window to be used as per
60 * [LazyLayoutCacheWindow.calculateBehindWindow].
61 */
62 @ExperimentalFoundationApi
63 fun LazyLayoutCacheWindow(ahead: Dp = 0.dp, behind: Dp = 0.dp): LazyLayoutCacheWindow {
64 return DpLazyLayoutCacheWindow(ahead, behind)
65 }
66
67 @OptIn(ExperimentalFoundationApi::class)
68 private class DpLazyLayoutCacheWindow(val ahead: Dp, val behind: Dp) : LazyLayoutCacheWindow {
calculateAheadWindownull69 override fun Density.calculateAheadWindow(viewport: Int): Int = ahead.roundToPx()
70
71 override fun Density.calculateBehindWindow(viewport: Int): Int = behind.roundToPx()
72
73 override fun hashCode(): Int {
74 return 31 * ahead.hashCode() + behind.hashCode()
75 }
76
equalsnull77 override fun equals(other: Any?): Boolean {
78 return if (other is DpLazyLayoutCacheWindow) {
79 other.ahead == this.ahead && other.behind == this.behind
80 } else {
81 false
82 }
83 }
84 }
85
86 /**
87 * Creates a [LazyLayoutCacheWindow] based off a fraction of the viewport.
88 *
89 * @param aheadFraction The fraction of the viewport to be used for the ahead window.
90 * @param behindFraction The fraction of the viewport to be used for the behind window.
91 */
92 @ExperimentalFoundationApi
LazyLayoutCacheWindownull93 fun LazyLayoutCacheWindow(
94 @FloatRange(from = 0.0) aheadFraction: Float = 0.0f,
95 @FloatRange(from = 0.0) behindFraction: Float = 0.0f
96 ): LazyLayoutCacheWindow = FractionLazyLayoutCacheWindow(aheadFraction, behindFraction)
97
98 @OptIn(ExperimentalFoundationApi::class)
99 private class FractionLazyLayoutCacheWindow(val aheadFraction: Float, val behindFraction: Float) :
100 LazyLayoutCacheWindow {
101 override fun Density.calculateAheadWindow(viewport: Int): Int =
102 (viewport * aheadFraction).roundToInt()
103
104 override fun Density.calculateBehindWindow(viewport: Int): Int =
105 (viewport * behindFraction).roundToInt()
106
107 override fun hashCode(): Int {
108 return 31 * aheadFraction.hashCode() + behindFraction.hashCode()
109 }
110
111 override fun equals(other: Any?): Boolean {
112 return if (other is FractionLazyLayoutCacheWindow) {
113 other.aheadFraction == this.aheadFraction && other.behindFraction == this.behindFraction
114 } else {
115 false
116 }
117 }
118 }
119