1 /* <lambda>null2 * Copyright 2024 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.ui.layout 18 19 import androidx.compose.ui.node.LayoutModifierNode 20 import androidx.compose.ui.node.NodeMeasuringIntrinsics 21 import androidx.compose.ui.unit.Constraints 22 import androidx.compose.ui.unit.IntSize 23 24 /** 25 * [ApproachLayoutModifierNode] is designed to support gradually approaching the destination layout 26 * calculated in the lookahead pass. This can be particularly helpful when the destination layout is 27 * anticipated to change drastically and would consequently result in visual disruptions. 28 * 29 * In order to create a smooth approach, an interpolation (often through animations) can be used in 30 * [approachMeasure] to interpolate the measurement or placement from a previously recorded size 31 * and/or position to the destination/target size and/or position. The destination size is available 32 * in [ApproachMeasureScope] as [ApproachMeasureScope.lookaheadSize]. And the target position can 33 * also be acquired in [ApproachMeasureScope] during placement by using 34 * [LookaheadScope.localLookaheadPositionOf] with the layout's 35 * [Placeable.PlacementScope.coordinates]. The sample code below illustrates how that can be 36 * achieved. 37 * 38 * During the lookahead pass, [measure] will be invoked. By default [measure] simply passes the 39 * incoming constraints to its child, and returns the child measure result to parent without any 40 * modification. The default behavior for [measure] is simply a pass through of constraints and 41 * measure results without modification. This can be overridden as needed. [approachMeasure] will be 42 * invoked during the approach pass after lookahead. 43 * 44 * [isMeasurementApproachInProgress] signals whether the measurement is in progress of approaching 45 * destination size. It will be queried after the destination has been determined by the lookahead 46 * pass, before [approachMeasure] is invoked. The lookahead size is provided to 47 * [isMeasurementApproachInProgress] for convenience in deciding whether the destination size has 48 * been reached. 49 * 50 * [isPlacementApproachInProgress] indicates whether the position is actively approaching 51 * destination defined by the lookahead, hence it's a signal to the system for whether additional 52 * approach placements are necessary. [isPlacementApproachInProgress] will be invoked after the 53 * destination position has been determined by lookahead pass, and before the placement phase in 54 * [approachMeasure]. 55 * 56 * **IMPORTANT**: When both [isMeasurementApproachInProgress] and [isPlacementApproachInProgress] 57 * become false, the approach is considered complete. Approach pass will subsequently snap the 58 * measurement and placement to lookahead measurement and placement. Once approach is complete, 59 * [approachMeasure] may never be invoked until either [isMeasurementApproachInProgress] or 60 * [isPlacementApproachInProgress] becomes true again. Therefore it is important to ensure 61 * [approachMeasure] and [measure] result in the same measurement and placement when the approach is 62 * complete. Otherwise, there may be visual discontinuity when we snap the measurement and placement 63 * to lookahead. 64 * 65 * It is important to be accurate in [isPlacementApproachInProgress] and 66 * [isMeasurementApproachInProgress]. A prolonged indication of incomplete approach will prevent the 67 * system from potentially skipping approach pass when possible. 68 * 69 * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample 70 */ 71 interface ApproachLayoutModifierNode : LayoutModifierNode { 72 /** 73 * [isMeasurementApproachInProgress] signals whether the measurement is currently approaching 74 * destination size. It will be queried after the destination has been determined by the 75 * lookahead pass, before [approachMeasure] is invoked. The lookahead size is provided to 76 * [isMeasurementApproachInProgress] for convenience in deciding whether the destination size 77 * has been reached. 78 * 79 * Note: It is important to be accurate in [isPlacementApproachInProgress] and 80 * [isMeasurementApproachInProgress]. A prolonged indication of incomplete approach will prevent 81 * the system from potentially skipping approach pass when possible. 82 */ 83 fun isMeasurementApproachInProgress(lookaheadSize: IntSize): Boolean 84 85 /** 86 * [isPlacementApproachInProgress] indicates whether the position is approaching destination 87 * defined by the lookahead, hence it's a signal to the system for whether additional approach 88 * placements are necessary. [isPlacementApproachInProgress] will be invoked after the 89 * destination position has been determined by lookahead pass, and before the placement phase in 90 * [approachMeasure]. 91 * 92 * Note: It is important to be accurate in [isPlacementApproachInProgress] and 93 * [isMeasurementApproachInProgress]. A prolonged indication of incomplete approach will prevent 94 * the system from potentially skipping approach pass when possible. 95 * 96 * By default, [isPlacementApproachInProgress] returns false. 97 */ 98 fun Placeable.PlacementScope.isPlacementApproachInProgress( 99 lookaheadCoordinates: LayoutCoordinates 100 ): Boolean { 101 return false 102 } 103 104 override fun MeasureScope.measure( 105 measurable: Measurable, 106 constraints: Constraints 107 ): MeasureResult = measurable.measure(constraints).run { layout(width, height) { place(0, 0) } } 108 109 /** 110 * [approachMeasure] defines how the measurement and placement of the layout approach the 111 * destination size and position. In order to achieve a smooth approach from the current size 112 * and position to the destination, an interpolation (often through animations) can be used in 113 * [approachMeasure] to interpolate the measurement or placement from a previously recorded size 114 * and position to the destination/target size and position. The destination size is available 115 * in [ApproachMeasureScope] as [ApproachMeasureScope.lookaheadSize]. And the target position 116 * can also be acquired in [ApproachMeasureScope] during placement by using 117 * [LookaheadScope.localLookaheadPositionOf] with the layout's 118 * [Placeable.PlacementScope.coordinates]. Please see sample code below for how that can be 119 * achieved. 120 * 121 * Note: [approachMeasure] is only guaranteed to be invoked when either 122 * [isMeasurementApproachInProgress] or [isMeasurementApproachInProgress] is true. Otherwise, 123 * the system will consider the approach complete (i.e. destination reached) and may skip the 124 * approach pass when possible. 125 * 126 * @sample androidx.compose.ui.samples.LookaheadLayoutCoordinatesSample 127 */ 128 fun ApproachMeasureScope.approachMeasure( 129 measurable: Measurable, 130 constraints: Constraints 131 ): MeasureResult 132 133 /** The function used to calculate minIntrinsicWidth for the approach pass changes. */ 134 fun ApproachIntrinsicMeasureScope.minApproachIntrinsicWidth( 135 measurable: IntrinsicMeasurable, 136 height: Int 137 ): Int = 138 if (node.coordinator!!.lookaheadDelegate!!.hasMeasureResult) { 139 // Only invoke approachMeasure when the node has been measured in lookahead, otherwise 140 // skip the approach measure as it might access lookahead size/constraints, neither of 141 // which would be set until lookahead measure. In that case, fall back to returning 142 // child layout's intrinsic size. 143 NodeMeasuringIntrinsics.minWidth( 144 NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints -> 145 approachMeasure(intrinsicMeasurable, constraints) 146 }, 147 this, 148 measurable, 149 height 150 ) 151 } else { 152 measurable.minIntrinsicWidth(height) 153 } 154 155 /** The function used to calculate minIntrinsicHeight for the approach pass changes. */ 156 fun ApproachIntrinsicMeasureScope.minApproachIntrinsicHeight( 157 measurable: IntrinsicMeasurable, 158 width: Int 159 ): Int = 160 if (node.coordinator!!.lookaheadDelegate!!.hasMeasureResult) { 161 // Only invoke approachMeasure when the node has been measured in lookahead, otherwise 162 // skip the approach measure as it might access lookahead size/constraints, neither of 163 // which would be set until lookahead measure. In that case, fall back to returning 164 // child layout's intrinsic size. 165 NodeMeasuringIntrinsics.minHeight( 166 NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints -> 167 approachMeasure(intrinsicMeasurable, constraints) 168 }, 169 this, 170 measurable, 171 width 172 ) 173 } else { 174 measurable.minIntrinsicHeight(width) 175 } 176 177 /** The function used to calculate maxIntrinsicWidth for the approach pass changes. */ 178 fun ApproachIntrinsicMeasureScope.maxApproachIntrinsicWidth( 179 measurable: IntrinsicMeasurable, 180 height: Int 181 ): Int = 182 if (node.coordinator!!.lookaheadDelegate!!.hasMeasureResult) { 183 // Only invoke approachMeasure when the node has been measured in lookahead, otherwise 184 // skip the approach measure as it might access lookahead size/constraints, neither of 185 // which would be set until lookahead measure. In that case, fall back to returning 186 // child layout's intrinsic size. 187 NodeMeasuringIntrinsics.maxWidth( 188 NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints -> 189 approachMeasure(intrinsicMeasurable, constraints) 190 }, 191 this, 192 measurable, 193 height 194 ) 195 } else { 196 measurable.maxIntrinsicWidth(height) 197 } 198 199 /** The function used to calculate maxIntrinsicHeight for the approach pass changes. */ 200 fun ApproachIntrinsicMeasureScope.maxApproachIntrinsicHeight( 201 measurable: IntrinsicMeasurable, 202 width: Int 203 ): Int = 204 if (node.coordinator!!.lookaheadDelegate!!.hasMeasureResult) { 205 // Only invoke approachMeasure when the node has been measured in lookahead, otherwise 206 // skip the approach measure as it might access lookahead size/constraints, neither of 207 // which would be set until lookahead measure. In that case, fall back to returning 208 // child layout's intrinsic size. 209 NodeMeasuringIntrinsics.maxHeight( 210 NodeMeasuringIntrinsics.ApproachMeasureBlock { intrinsicMeasurable, constraints -> 211 approachMeasure(intrinsicMeasurable, constraints) 212 }, 213 this, 214 measurable, 215 width 216 ) 217 } else { 218 measurable.maxIntrinsicHeight(width) 219 } 220 } 221