1 /*
<lambda>null2 * Copyright 2021 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.collection.mutableIntObjectMapOf
20 import androidx.compose.foundation.ExperimentalFoundationApi
21 import androidx.compose.foundation.internal.checkPrecondition
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.geometry.Size
24 import androidx.compose.ui.geometry.isSpecified
25 import androidx.compose.ui.layout.MeasureResult
26 import androidx.compose.ui.layout.MeasureScope
27 import androidx.compose.ui.layout.Placeable
28 import androidx.compose.ui.layout.SubcomposeMeasureScope
29 import androidx.compose.ui.unit.Constraints
30 import androidx.compose.ui.unit.Dp
31 import androidx.compose.ui.unit.DpSize
32 import androidx.compose.ui.unit.TextUnit
33 import androidx.compose.ui.unit.TextUnitType
34 import androidx.compose.ui.unit.dp
35 import androidx.compose.ui.unit.isSpecified
36 import androidx.compose.ui.unit.sp
37
38 /**
39 * The receiver scope of a [LazyLayout]'s measure lambda. The return value of the measure lambda is
40 * [MeasureResult], which should be returned by [layout].
41 *
42 * Main difference from the regular flow of writing any custom layout is that you have a new
43 * function [measure] which accepts item index and constraints, composes the item based and then
44 * measures all the layouts emitted in the item content block.
45 *
46 * Note: this interface is a part of [LazyLayout] harness that allows for building custom lazy
47 * layouts. LazyLayout and all corresponding APIs are still under development and are subject to
48 * change.
49 */
50 @Stable
51 @ExperimentalFoundationApi
52 sealed interface LazyLayoutMeasureScope : MeasureScope {
53 /**
54 * Subcompose and measure the item of lazy layout.
55 *
56 * @param index the item index. Should be no larger that [LazyLayoutItemProvider.itemCount].
57 * @param constraints [Constraints] to measure the children emitted into an item content
58 * composable specified via [LazyLayoutItemProvider.Item].
59 * @return List of [Placeable]s. Note that if you emitted multiple children into the item
60 * composable you will receive multiple placeables, each of them will be measured with the
61 * passed [constraints].
62 */
63 fun measure(index: Int, constraints: Constraints): List<Placeable>
64
65 // Below overrides added to work around https://youtrack.jetbrains.com/issue/KT-51672
66 // Must be kept in sync until resolved.
67
68 @Stable
69 override fun TextUnit.toDp(): Dp {
70 checkPrecondition(type == TextUnitType.Sp) { "Only Sp can convert to Px" }
71 return Dp(value * fontScale)
72 }
73
74 @Stable override fun Int.toDp(): Dp = (this / density).dp
75
76 @Stable override fun Float.toDp(): Dp = (this / density).dp
77
78 @Stable override fun Float.toSp(): TextUnit = (this / (fontScale * density)).sp
79
80 @Stable override fun Int.toSp(): TextUnit = (this / (fontScale * density)).sp
81
82 @Stable override fun Dp.toSp(): TextUnit = (value / fontScale).sp
83
84 @Stable
85 override fun DpSize.toSize(): Size =
86 if (isSpecified) {
87 Size(width.toPx(), height.toPx())
88 } else {
89 Size.Unspecified
90 }
91
92 @Stable
93 override fun Size.toDpSize(): DpSize =
94 if (isSpecified) {
95 DpSize(width.toDp(), height.toDp())
96 } else {
97 DpSize.Unspecified
98 }
99 }
100
101 @OptIn(ExperimentalFoundationApi::class)
102 internal class LazyLayoutMeasureScopeImpl
103 internal constructor(
104 private val itemContentFactory: LazyLayoutItemContentFactory,
105 private val subcomposeMeasureScope: SubcomposeMeasureScope
<lambda>null106 ) : LazyLayoutMeasureScope, MeasureScope by subcomposeMeasureScope {
107
108 private val itemProvider = itemContentFactory.itemProvider()
109
110 /**
111 * A cache of the previously composed items. It allows us to support [get] re-executions with
112 * the same index during the same measure pass.
113 */
114 private val placeablesCache = mutableIntObjectMapOf<List<Placeable>>()
115
116 override fun measure(index: Int, constraints: Constraints): List<Placeable> {
117 val cachedPlaceable = placeablesCache[index]
118 return if (cachedPlaceable != null) {
119 cachedPlaceable
120 } else {
121 val key = itemProvider.getKey(index)
122 val contentType = itemProvider.getContentType(index)
123 val itemContent = itemContentFactory.getContent(index, key, contentType)
124 val measurables = subcomposeMeasureScope.subcompose(key, itemContent)
125 List(measurables.size) { i -> measurables[i].measure(constraints) }
126 .also { placeablesCache[index] = it }
127 }
128 }
129
130 /** Below overrides added to work around https://youtrack.jetbrains.com/issue/KT-51672 */
131 override fun TextUnit.toDp(): Dp = with(subcomposeMeasureScope) { toDp() }
132
133 override fun Int.toDp(): Dp = with(subcomposeMeasureScope) { toDp() }
134
135 override fun Float.toDp(): Dp = with(subcomposeMeasureScope) { toDp() }
136
137 override fun Float.toSp(): TextUnit = with(subcomposeMeasureScope) { toSp() }
138
139 override fun Int.toSp(): TextUnit = with(subcomposeMeasureScope) { toSp() }
140
141 override fun Dp.toSp(): TextUnit = with(subcomposeMeasureScope) { toSp() }
142
143 override fun DpSize.toSize(): Size = with(subcomposeMeasureScope) { toSize() }
144
145 override fun Size.toDpSize(): DpSize = with(subcomposeMeasureScope) { toDpSize() }
146 }
147