LazyDsl.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.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
/**
* Receiver scope which is used by [LazyColumn] and [LazyRow].
*/
interface LazyListScope {
/**
* Adds a list of items and their content to the scope.
*
* @param items the data list
* @param itemContent the content displayed by a single item
*/
fun <T> items(
items: List<T>,
itemContent: @Composable LazyItemScope.(item: T) -> Unit
)
/**
* Adds a single item to the scope.
*
* @param content the content of the item
*/
fun item(content: @Composable LazyItemScope.() -> Unit)
/**
* Adds a list of items to the scope where the content of an item is aware of its index.
*
* @param items the data list
* @param itemContent the content displayed by a single item
*/
fun <T> itemsIndexed(
items: List<T>,
itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
)
}
internal class IntervalHolder(
val startIndex: Int,
val content: LazyItemScope.(Int) -> (@Composable () -> Unit)
)
internal class LazyListScopeImpl : LazyListScope {
private val intervals = mutableListOf<IntervalHolder>()
var totalSize = 0
fun contentFor(index: Int, scope: LazyItemScope): @Composable () -> Unit {
val intervalIndex = findIndexOfHighestValueLesserThan(intervals, index)
val interval = intervals[intervalIndex]
val localIntervalIndex = index - interval.startIndex
return interval.content(scope, localIntervalIndex)
}
override fun <T> items(
items: List<T>,
itemContent: @Composable LazyItemScope.(item: T) -> Unit
) {
// There aren't any items to display
if (items.isEmpty()) { return }
val interval = IntervalHolder(
startIndex = totalSize,
content = { index ->
val item = items[index]
{ itemContent(item) }
}
)
totalSize += items.size
intervals.add(interval)
}
override fun item(content: @Composable LazyItemScope.() -> Unit) {
val interval = IntervalHolder(
startIndex = totalSize,
content = { { content() } }
)
totalSize += 1
intervals.add(interval)
}
override fun <T> itemsIndexed(
items: List<T>,
itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
) {
// There aren't any items to display
if (items.isEmpty()) { return }
val interval = IntervalHolder(
startIndex = totalSize,
content = { index ->
val item = items[index]
{ itemContent(index, item) }
}
)
totalSize += items.size
intervals.add(interval)
}
/**
* Finds the index of the [list] which contains the highest value of [IntervalHolder.startIndex]
* that is less than or equal to the given [value].
*/
private fun findIndexOfHighestValueLesserThan(list: List<IntervalHolder>, value: Int): Int {
var left = 0
var right = list.lastIndex
while (left < right) {
val middle = (left + right) / 2
val middleValue = list[middle].startIndex
if (middleValue == value) {
return middle
}
if (middleValue < value) {
left = middle + 1
// Verify that the left will not be bigger than our value
if (value < list[left].startIndex) {
return middle
}
} else {
right = middle - 1
}
}
return left
}
}
/**
* The horizontally scrolling list that only composes and lays out the currently visible items.
* The [content] block defines a DSL which allows you to emit items of different types. For
* example you can use [LazyListScope.item] to add a single item and [LazyListScope.items] to add
* a list of items.
*
* @sample androidx.compose.foundation.samples.LazyRowSample
*
* @param modifier the modifier to apply to this layout
* @param state the state object to be used to control or observe the list's state
* @param contentPadding a padding around the whole content. This will add padding for the
* content after it has been clipped, which is not possible via [modifier] param. Note that it is
* **not** a padding applied for each item's content
* @param verticalAlignment the vertical alignment applied to the items
* @param content a block which describes the content. Inside this block you can use methods like
* [LazyListScope.item] to add a single item or [LazyListScope.items] to add a list of items.
*/
@Composable
fun LazyRow(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: LazyListScope.() -> Unit
) {
val scope = LazyListScopeImpl()
scope.apply(content)
LazyList(
itemsCount = scope.totalSize,
modifier = modifier,
state = state,
contentPadding = contentPadding,
verticalAlignment = verticalAlignment,
isVertical = false
) {
index ->
scope.contentFor(index, this)
}
}
/**
* The vertically scrolling list that only composes and lays out the currently visible items.
* The [content] block defines a DSL which allows you to emit items of different types. For
* example you can use [LazyListScope.item] to add a single item and [LazyListScope.items] to add
* a list of items.
*
* @sample androidx.compose.foundation.samples.LazyColumnSample
*
* @param modifier the modifier to apply to this layout
* @param state the state object to be used to control or observe the list's state
* @param contentPadding a padding around the whole content. This will add padding for the
* content after it has been clipped, which is not possible via [modifier] param. Note that it is
* **not** a padding applied for each item's content
* @param horizontalAlignment the horizontal alignment applied to the items
* @param content a block which describes the content. Inside this block you can use methods like
* [LazyListScope.item] to add a single item or [LazyListScope.items] to add a list of items.
*/
@Composable
fun LazyColumn(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: LazyListScope.() -> Unit
) {
val scope = LazyListScopeImpl()
scope.apply(content)
LazyList(
itemsCount = scope.totalSize,
modifier = modifier,
state = state,
contentPadding = contentPadding,
horizontalAlignment = horizontalAlignment,
isVertical = true
) { index ->
scope.contentFor(index, this)
}
}