1 /* 2 * Copyright (C) 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.constraintlayout.compose 18 19 import androidx.compose.runtime.Immutable 20 import androidx.compose.ui.layout.Measurable 21 import androidx.constraintlayout.core.parser.CLObject 22 import androidx.constraintlayout.core.state.ConstraintSetParser 23 import androidx.constraintlayout.core.state.ConstraintSetParser.LayoutVariables 24 import androidx.constraintlayout.core.state.Transition 25 26 @JvmDefaultWithCompatibility 27 /** 28 * Immutable description of the constraints used to layout the children of a [ConstraintLayout]. 29 * 30 * Layout items are defined with [ConstraintSetScope.createRefFor], each layout item must be defined 31 * with a unique ID from other items in the same scope: 32 * ``` 33 * val textRef = createRefFor("text") 34 * val imageRef = createRefFor("image") 35 * ``` 36 * 37 * You may also use [ConstraintSetScope.createRefsFor] to declare up to 16 items at a time using the 38 * destructuring declaration pattern: 39 * ``` 40 * val (textRef, imageRef) = createRefsFor("text", "image") 41 * ``` 42 * 43 * Individual constraints are defined with [ConstraintSetScope.constrain]. Where you can tell each 44 * layout reference how to constrain to other references including the **`parent`**: 45 * ``` 46 * constrain(textRef) { 47 * centerTo(parent) 48 * } 49 * constrain(imageRef) { 50 * centerVerticallyTo(textRef) 51 * start.linkTo(textRef.end, margin = 8.dp) 52 * } 53 * ``` 54 * 55 * Here, we constrain the *textRef* to the center of the *parent*, while the image is centered 56 * vertically to *textRef* and is horizontally placed to the right of its end anchor with a margin 57 * (keep in mind, when using `center...`, `start` or `end` the layout direction will automatically 58 * change in RTL locales). 59 * 60 * See [ConstrainScope] to learn more about how to constrain elements together. 61 * 62 * In the ConstraintLayout or MotionLayout Composables, the children must be bound using 63 * [Modifier.layoutId][androidx.compose.ui.layout.layoutId]. 64 * 65 * So, the whole snippet with ConstraintLayout would look like this: 66 * ``` 67 * val textId = "text" 68 * val imageId = "image" 69 * ConstraintLayout( 70 * constraintSet = ConstraintSet { 71 * val (textRef, imageRef) = createRefsFor(textId, imageId) 72 * constrain(textRef) { 73 * centerTo(parent) 74 * } 75 * constrain(imageRef) { 76 * centerVerticallyTo(textRef) 77 * start.linkTo(textRef.end, margin = 8.dp) 78 * } 79 * }, 80 * modifier = Modifier.fillMaxSize() 81 * ) { 82 * Text( 83 * modifier = Modifier.layoutId(textId), 84 * text = "Hello, World!" 85 * ) 86 * Image( 87 * modifier = Modifier.layoutId(imageId), 88 * imageVector = Icons.Default.Android, 89 * contentDescription = null 90 * ) 91 * } 92 * ``` 93 * 94 * ## Helpers 95 * You may also use helpers, a set of virtual (not shown on screen) components that provide special 96 * layout behaviors, you may find these in the [ConstraintSetScope] with the '`create...`' prefix, a 97 * few of these are **Guidelines**, **Chains** and **Barriers**. 98 * 99 * ### Guidelines 100 * Lines to which other [ConstrainedLayoutReference]s may be constrained to, these are defined at 101 * either a fixed or percent position from an anchor of the ConstraintLayout parent (top, bottom, 102 * start, end, absoluteLeft, absoluteRight). 103 * 104 * Example: 105 * ``` 106 * val (textRef) = createRefsFor(textId) 107 * val vG = createGuidelineFromStart(fraction = 0.3f) 108 * 109 * constrain(textRef) { 110 * centerVerticallyTo(parent) 111 * centerAround(vG) 112 * } 113 * ``` 114 * 115 * See 116 * - [ConstraintSetScope.createGuidelineFromTop] 117 * - [ConstraintSetScope.createGuidelineFromBottom] 118 * - [ConstraintSetScope.createGuidelineFromStart] 119 * - [ConstraintSetScope.createGuidelineFromEnd] 120 * - [ConstraintSetScope.createGuidelineFromAbsoluteLeft] 121 * - [ConstraintSetScope.createGuidelineFromAbsoluteRight] 122 * 123 * ### Chains 124 * Chains may be either horizontal or vertical, these, take a set of [ConstrainedLayoutReference]s 125 * and create bi-directional constraints on each of them at the same orientation of the chain in the 126 * given order, meaning that an horizontal chain will create constraints between the start and end 127 * anchors. 128 * 129 * The result, a layout that evenly distributes the space within its elements. 130 * 131 * For example, to make a layout with three text elements distributed so that the spacing between 132 * them (and around them) is equal: 133 * ``` 134 * val (textRef0, textRef1, textRef2) = createRefsFor("text0", "text1", "text2") 135 * createHorizontalChain(textRef0, textRef1, textRef2, chainStyle = ChainStyle.Spread) 136 * ``` 137 * 138 * You may set margins within elements in a chain with [ConstraintLayoutScope.withChainParams]: 139 * ``` 140 * val (textRef0, textRef1, textRef2) = createRefsFor("text0", "text1", "text2") 141 * createHorizontalChain( 142 * textRef0, 143 * textRef1.withChainParams(startMargin = 100.dp, endMargin = 100.dp), 144 * textRef2, 145 * chainStyle = ChainStyle.Spread 146 * ) 147 * ``` 148 * 149 * You can also change the way space is distributed, as chains have three different styles: 150 * - [ChainStyle.Spread] Layouts are evenly distributed after margins are accounted for (the space 151 * around and between each item is even). This is the **default** style for chains. 152 * - [ChainStyle.SpreadInside] The first and last layouts are affixed to each end of the chain, and 153 * the rest of the items are evenly distributed (after margins are accounted for). I.e.: Items are 154 * spread from the inside, distributing the space between them with no space around the first and 155 * last items. 156 * - [ChainStyle.Packed] The layouts are packed together after margins are accounted for, by 157 * default, they're packed together at the middle, you can change this behavior with the **bias** 158 * parameter of [ChainStyle.Packed]. 159 * - Alternatively, you can make every Layout in the chain to be [Dimension.fillToConstraints] and 160 * then set a particular weight to each of them to create a **weighted chain**. 161 * 162 * #### Weighted Chain 163 * Weighted chains are useful when you want the size of the elements to depend on the remaining size 164 * of the chain. As opposed to just distributing the space around and/or in-between the items. 165 * 166 * For example, to create a layout with three text elements in a row where each element takes the 167 * exact same size regardless of content, you can use a simple weighted chain where each item has 168 * the same weight: 169 * ``` 170 * val (textRef0, textRef1, textRef2) = createRefsFor("text0", "text1", "text2") 171 * createHorizontalChain( 172 * textRef0.withChainParams(weight = 1f), 173 * textRef1.withChainParams(weight = 1f), 174 * textRef2.withChainParams(weight = 1f), 175 * chainStyle = ChainStyle.Spread 176 * ) 177 * 178 * constrain(textRef0, textRef1, textRef2) { 179 * width = Dimension.fillToConstraints 180 * } 181 * ``` 182 * 183 * This way, the texts will horizontally occupy the same space even if one of them is significantly 184 * larger than the others. 185 * 186 * Also note that when using [ConstraintSetScope] you can apply the same constrains to multiple 187 * references at a time. 188 * 189 * Keep in mind that chains have a relatively high performance cost. For example, if you plan on 190 * having multiple chains one below the other, consider instead, applying just one chain and using 191 * it as a reference to constrain all other elements to the ones that match their position in that 192 * one chain. It may provide increased performance with no significant changes in the layout output. 193 * 194 * Alternatively, consider if other helpers such as [ConstraintSetScope.createGrid] can accomplish 195 * the same layout. 196 * 197 * See 198 * - [ConstraintSetScope.createHorizontalChain] 199 * - [ConstraintSetScope.createVerticalChain] 200 * - [ConstraintSetScope.withChainParams] 201 * 202 * ### Barriers 203 * Barriers take a set of [ConstrainedLayoutReference]s and creates the most further point in a 204 * given direction where other [ConstrainedLayoutReference] can constrain to. 205 * 206 * This is useful in situations where elements in a layout may have different sizes but you want to 207 * always constrain to the largest item, for example, if you have a text element on top of another 208 * and want an image to always be constrained to the end of them: 209 * ``` 210 * val (textRef0, textRef1, imageRef) = createRefsFor("text0", "text1", "image") 211 * 212 * // Creates a point at the furthest end anchor from the elements in the barrier 213 * val endTextsBarrier = createEndBarrier(textRef0, textRef1) 214 * 215 * constrain(textRef0) { 216 * centerTo(parent) 217 * } 218 * constrain(textRef1) { 219 * top.linkTo(textRef0.bottom) 220 * start.linkTo(textRef0.start) 221 * } 222 * 223 * constrain(imageRef) { 224 * top.linkTo(textRef0.top) 225 * bottom.linkTo(textRef1.bottom) 226 * 227 * // Image will always be at the end of both texts, regardless of their size 228 * start.linkTo(endTextsBarrier, margin = 8.dp) 229 * } 230 * ``` 231 * 232 * Be careful not to constrain a [ConstrainedLayoutReference] to a barrier that references it or 233 * that depends on it indirectly. This creates a cyclic dependency that results in unsupported 234 * layout behavior. 235 * 236 * See 237 * - [ConstraintSetScope.createTopBarrier] 238 * - [ConstraintSetScope.createBottomBarrier] 239 * - [ConstraintSetScope.createStartBarrier] 240 * - [ConstraintSetScope.createEndBarrier] 241 * - [ConstraintSetScope.createAbsoluteLeftBarrier] 242 * - [ConstraintSetScope.createAbsoluteRightBarrier] 243 */ 244 @Immutable 245 interface ConstraintSet { 246 /** Applies the [ConstraintSet] to a state. */ applyTonull247 fun applyTo(state: State, measurables: List<Measurable>) 248 249 fun override(name: String, value: Float) = this 250 251 fun applyTo(transition: Transition, type: Int) { 252 // nothing here, used in MotionLayout 253 } 254 isDirtynull255 fun isDirty(measurables: List<Measurable>): Boolean = true 256 } 257 258 @JvmDefaultWithCompatibility 259 @Immutable 260 internal interface DerivedConstraintSet : ConstraintSet { 261 /** 262 * [ConstraintSet] that this instance will derive its constraints from. 263 * 264 * This means that the constraints from [extendFrom] will be applied before the constraints of 265 * this [DerivedConstraintSet] instance. 266 */ 267 val extendFrom: ConstraintSet? 268 269 override fun applyTo(state: State, measurables: List<Measurable>) { 270 extendFrom?.applyTo(state, measurables) 271 applyToState(state) 272 } 273 274 /** 275 * Inheritors should implement this function so that derived constraints are applied properly. 276 */ 277 fun applyToState(state: State) 278 } 279 280 /** 281 * [ConstraintSet] defined solely on the given [clObject]. Only meant to be used to extract a copy 282 * of the underlying ConstraintSet of ConstraintLayout with the inline Modifier DSL. 283 * 284 * You likely don't mean to use this. 285 */ 286 @Immutable 287 @PublishedApi 288 internal class RawConstraintSet(private val clObject: CLObject) : ConstraintSet { 289 private val layoutVariables = LayoutVariables() 290 applyTonull291 override fun applyTo(state: State, measurables: List<Measurable>) { 292 ConstraintSetParser.populateState(clObject, state, layoutVariables) 293 } 294 equalsnull295 override fun equals(other: Any?): Boolean { 296 if (this === other) return true 297 if (javaClass != other?.javaClass) return false 298 299 other as RawConstraintSet 300 301 if (clObject != other.clObject) return false 302 303 return true 304 } 305 hashCodenull306 override fun hashCode(): Int { 307 return clObject.hashCode() 308 } 309 } 310