CachingItemContentFactory.kt
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.foundation.lazy
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* Caches the lambdas being produced by [itemContentFactory]. This allows us to perform less
* recompositions as the compose runtime can skip the whole composition if we subcompose with the
* same instance of the content lambda.
*/
internal class CachingItemContentFactory(
itemContentFactory: LazyItemScope.(Int) -> @Composable () -> Unit
) : (Int) -> @Composable () -> Unit {
/**
* The cached instance of the scope to be used for composing items.
*/
private var itemScope = InitialLazyItemsScopeImpl
/**
* Contains the cached lambdas produced by the [itemContentFactory].
*/
private val lambdasCache = mutableMapOf<Int, @Composable () -> Unit>()
/**
* Current factory for creating an item content lambdas.
*/
var itemContentFactory: LazyItemScope.(Int) -> @Composable () -> Unit = itemContentFactory
set(value) {
if (field !== value) {
lambdasCache.clear()
field = value
}
}
/**
* Updates the [itemScope] with the last [constraints] we got from the parent.
*/
internal fun updateItemScope(density: Density, constraints: Constraints) = with(density) {
val width = constraints.maxWidth.toDp()
val height = constraints.maxHeight.toDp()
if (width != itemScope.maxWidth || height != itemScope.maxHeight) {
itemScope = LazyItemScopeImpl(width, height)
lambdasCache.clear()
}
}
/**
* Return cached item content lambda or creates a new lambda and puts it in the cache.
*/
override fun invoke(index: Int) = lambdasCache.getOrPut(index) {
itemScope.itemContentFactory(index)
}
}
/**
* Pre-allocated initial value for [LazyItemScopeImpl] to not have it nullable and avoid using
* late init.
*/
private val InitialLazyItemsScopeImpl = LazyItemScopeImpl(0.dp, 0.dp)
private data class LazyItemScopeImpl(
val maxWidth: Dp,
val maxHeight: Dp
) : LazyItemScope {
override fun Modifier.fillParentMaxSize() = size(maxWidth, maxHeight)
override fun Modifier.fillParentMaxWidth() = width(maxWidth)
override fun Modifier.fillParentMaxHeight() = height(maxHeight)
}