1 /* 2 * Copyright (C) 2017 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.core.widgets; 18 19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD; 22 23 import androidx.constraintlayout.core.ArrayRow; 24 import androidx.constraintlayout.core.LinearSystem; 25 import androidx.constraintlayout.core.SolverVariable; 26 import androidx.constraintlayout.core.widgets.analyzer.Direct; 27 28 import java.util.ArrayList; 29 30 /** 31 * Chain management and constraints creation 32 */ 33 public class Chain { 34 35 private static final boolean DEBUG = false; 36 public static final boolean USE_CHAIN_OPTIMIZATION = false; 37 38 /** 39 * Apply specific rules for dealing with chains of widgets. 40 * Chains are defined as a list of widget linked together with bi-directional connections 41 * 42 * @param constraintWidgetContainer root container 43 * @param system the linear system we add the equations to 44 * @param widgets if this is null or contains any chainheads' widgets, 45 * constrain all chains / the corresponding chains 46 * @param orientation HORIZONTAL or VERTICAL 47 */ applyChainConstraints( ConstraintWidgetContainer constraintWidgetContainer, LinearSystem system, ArrayList<ConstraintWidget> widgets, int orientation)48 public static void applyChainConstraints( 49 ConstraintWidgetContainer constraintWidgetContainer, 50 LinearSystem system, 51 ArrayList<ConstraintWidget> widgets, 52 int orientation) { 53 // what to do: 54 // Don't skip things. Either the element is GONE or not. 55 int offset = 0; 56 int chainsSize = 0; 57 ChainHead[] chainsArray = null; 58 if (orientation == ConstraintWidget.HORIZONTAL) { 59 offset = 0; 60 chainsSize = constraintWidgetContainer.mHorizontalChainsSize; 61 chainsArray = constraintWidgetContainer.mHorizontalChainsArray; 62 } else { 63 offset = 2; 64 chainsSize = constraintWidgetContainer.mVerticalChainsSize; 65 chainsArray = constraintWidgetContainer.mVerticalChainsArray; 66 } 67 68 for (int i = 0; i < chainsSize; i++) { 69 ChainHead first = chainsArray[i]; 70 // we have to make sure we define the ChainHead here, 71 // otherwise the values we use may not be correctly initialized 72 // (as we initialize them in the ConstraintWidget.addToSolver()) 73 first.define(); 74 if (widgets == null || widgets.contains(first.mFirst)) { 75 applyChainConstraints(constraintWidgetContainer, 76 system, orientation, offset, first); 77 } 78 } 79 } 80 81 /** 82 * Apply specific rules for dealing with chains of widgets. 83 * Chains are defined as a list of widget linked together with bi-directional connections 84 * 85 * @param container the root container 86 * @param system the linear system we add the equations to 87 * @param orientation HORIZONTAL or VERTICAL 88 * @param offset 0 or 2 to accommodate for HORIZONTAL / VERTICAL 89 * @param chainHead a chain represented by its main elements 90 */ applyChainConstraints(ConstraintWidgetContainer container, LinearSystem system, int orientation, int offset, ChainHead chainHead)91 static void applyChainConstraints(ConstraintWidgetContainer container, LinearSystem system, 92 int orientation, int offset, ChainHead chainHead) { 93 ConstraintWidget first = chainHead.mFirst; 94 ConstraintWidget last = chainHead.mLast; 95 ConstraintWidget firstVisibleWidget = chainHead.mFirstVisibleWidget; 96 ConstraintWidget lastVisibleWidget = chainHead.mLastVisibleWidget; 97 ConstraintWidget head = chainHead.mHead; 98 99 ConstraintWidget widget = first; 100 ConstraintWidget next = null; 101 boolean done = false; 102 103 float totalWeights = chainHead.mTotalWeight; 104 @SuppressWarnings("unused") ConstraintWidget firstMatchConstraintsWidget = 105 chainHead.mFirstMatchConstraintWidget; 106 @SuppressWarnings("unused") ConstraintWidget previousMatchConstraintsWidget = 107 chainHead.mLastMatchConstraintWidget; 108 109 boolean isWrapContent = container.mListDimensionBehaviors[orientation] 110 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 111 boolean isChainSpread = false; 112 boolean isChainSpreadInside = false; 113 boolean isChainPacked = false; 114 115 if (orientation == ConstraintWidget.HORIZONTAL) { 116 isChainSpread = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD; 117 isChainSpreadInside = 118 head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE; 119 isChainPacked = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_PACKED; 120 } else { 121 isChainSpread = head.mVerticalChainStyle == ConstraintWidget.CHAIN_SPREAD; 122 isChainSpreadInside = head.mVerticalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE; 123 isChainPacked = head.mVerticalChainStyle == ConstraintWidget.CHAIN_PACKED; 124 } 125 126 if (USE_CHAIN_OPTIMIZATION && !isWrapContent 127 && Direct.solveChain(container, system, orientation, offset, chainHead, 128 isChainSpread, isChainSpreadInside, isChainPacked)) { 129 if (LinearSystem.FULL_DEBUG) { 130 System.out.println("### CHAIN FULLY SOLVED! ###"); 131 } 132 return; // done with the chain! 133 } else if (LinearSystem.FULL_DEBUG) { 134 System.out.println("### CHAIN WASN'T SOLVED DIRECTLY... ###"); 135 } 136 137 // This traversal will: 138 // - set up some basic ordering constraints 139 // - build a linked list of matched constraints widgets 140 while (!done) { 141 ConstraintAnchor begin = widget.mListAnchors[offset]; 142 143 int strength = SolverVariable.STRENGTH_HIGHEST; 144 if (isChainPacked) { 145 strength = SolverVariable.STRENGTH_LOW; 146 } 147 int margin = begin.getMargin(); 148 boolean isSpreadOnly = widget.mListDimensionBehaviors[orientation] 149 == DimensionBehaviour.MATCH_CONSTRAINT 150 && widget.mResolvedMatchConstraintDefault[orientation] 151 == MATCH_CONSTRAINT_SPREAD; 152 153 if (begin.mTarget != null && widget != first) { 154 margin += begin.mTarget.getMargin(); 155 } 156 157 if (isChainPacked && widget != first && widget != firstVisibleWidget) { 158 strength = SolverVariable.STRENGTH_FIXED; 159 } 160 161 if (begin.mTarget != null) { 162 if (widget == firstVisibleWidget) { 163 system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable, 164 margin, SolverVariable.STRENGTH_BARRIER); 165 } else { 166 system.addGreaterThan(begin.mSolverVariable, begin.mTarget.mSolverVariable, 167 margin, SolverVariable.STRENGTH_FIXED); 168 } 169 if (isSpreadOnly && !isChainPacked) { 170 strength = SolverVariable.STRENGTH_EQUALITY; 171 } 172 if (widget == firstVisibleWidget && isChainPacked 173 && widget.isInBarrier(orientation)) { 174 strength = SolverVariable.STRENGTH_EQUALITY; 175 } 176 system.addEquality(begin.mSolverVariable, begin.mTarget.mSolverVariable, margin, 177 strength); 178 } 179 180 if (isWrapContent) { 181 if (widget.getVisibility() != ConstraintWidget.GONE 182 && widget.mListDimensionBehaviors[orientation] 183 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { 184 system.addGreaterThan(widget.mListAnchors[offset + 1].mSolverVariable, 185 widget.mListAnchors[offset].mSolverVariable, 0, 186 SolverVariable.STRENGTH_EQUALITY); 187 } 188 system.addGreaterThan(widget.mListAnchors[offset].mSolverVariable, 189 container.mListAnchors[offset].mSolverVariable, 190 0, SolverVariable.STRENGTH_FIXED); 191 } 192 193 // go to the next widget 194 ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget; 195 if (nextAnchor != null) { 196 next = nextAnchor.mOwner; 197 if (next.mListAnchors[offset].mTarget == null 198 || next.mListAnchors[offset].mTarget.mOwner != widget) { 199 next = null; 200 } 201 } else { 202 next = null; 203 } 204 if (next != null) { 205 widget = next; 206 } else { 207 done = true; 208 } 209 } 210 211 // Make sure we have constraints for the last anchors / targets 212 if (lastVisibleWidget != null && last.mListAnchors[offset + 1].mTarget != null) { 213 ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1]; 214 boolean isSpreadOnly = lastVisibleWidget.mListDimensionBehaviors[orientation] 215 == DimensionBehaviour.MATCH_CONSTRAINT 216 && lastVisibleWidget.mResolvedMatchConstraintDefault[orientation] 217 == MATCH_CONSTRAINT_SPREAD; 218 if (isSpreadOnly && !isChainPacked && end.mTarget.mOwner == container) { 219 system.addEquality(end.mSolverVariable, end.mTarget.mSolverVariable, 220 -end.getMargin(), SolverVariable.STRENGTH_EQUALITY); 221 } else if (isChainPacked && end.mTarget.mOwner == container) { 222 system.addEquality(end.mSolverVariable, end.mTarget.mSolverVariable, 223 -end.getMargin(), SolverVariable.STRENGTH_HIGHEST); 224 } 225 system.addLowerThan(end.mSolverVariable, 226 last.mListAnchors[offset + 1].mTarget.mSolverVariable, -end.getMargin(), 227 SolverVariable.STRENGTH_BARRIER); 228 } 229 230 // ... and make sure the root end is constrained in wrap content. 231 if (isWrapContent) { 232 system.addGreaterThan(container.mListAnchors[offset + 1].mSolverVariable, 233 last.mListAnchors[offset + 1].mSolverVariable, 234 last.mListAnchors[offset + 1].getMargin(), SolverVariable.STRENGTH_FIXED); 235 } 236 237 // Now, let's apply the centering / spreading for matched constraints widgets 238 ArrayList<ConstraintWidget> listMatchConstraints = 239 chainHead.mWeightedMatchConstraintsWidgets; 240 if (listMatchConstraints != null) { 241 final int count = listMatchConstraints.size(); 242 if (count > 1) { 243 ConstraintWidget lastMatch = null; 244 float lastWeight = 0; 245 246 if (chainHead.mHasUndefinedWeights && !chainHead.mHasComplexMatchWeights) { 247 totalWeights = chainHead.mWidgetsMatchCount; 248 } 249 250 for (int i = 0; i < count; i++) { 251 ConstraintWidget match = listMatchConstraints.get(i); 252 float currentWeight = match.mWeight[orientation]; 253 254 if (currentWeight < 0) { 255 if (chainHead.mHasComplexMatchWeights) { 256 system.addEquality(match.mListAnchors[offset + 1].mSolverVariable, 257 match.mListAnchors[offset].mSolverVariable, 258 0, SolverVariable.STRENGTH_HIGHEST); 259 continue; 260 } 261 currentWeight = 1; 262 } 263 if (currentWeight == 0) { 264 system.addEquality(match.mListAnchors[offset + 1].mSolverVariable, 265 match.mListAnchors[offset].mSolverVariable, 266 0, SolverVariable.STRENGTH_FIXED); 267 continue; 268 } 269 270 if (lastMatch != null) { 271 SolverVariable begin = lastMatch.mListAnchors[offset].mSolverVariable; 272 SolverVariable end = lastMatch.mListAnchors[offset + 1].mSolverVariable; 273 SolverVariable nextBegin = match.mListAnchors[offset].mSolverVariable; 274 SolverVariable nextEnd = match.mListAnchors[offset + 1].mSolverVariable; 275 ArrayRow row = system.createRow(); 276 row.createRowEqualMatchDimensions(lastWeight, totalWeights, currentWeight, 277 begin, end, nextBegin, nextEnd); 278 system.addConstraint(row); 279 } 280 281 lastMatch = match; 282 lastWeight = currentWeight; 283 } 284 } 285 } 286 287 if (DEBUG) { 288 widget = firstVisibleWidget; 289 while (widget != null) { 290 next = widget.mNextChainWidget[orientation]; 291 widget.mListAnchors[offset].mSolverVariable 292 .setName("" + widget.getDebugName() + ".left"); 293 widget.mListAnchors[offset + 1].mSolverVariable 294 .setName("" + widget.getDebugName() + ".right"); 295 widget = next; 296 } 297 } 298 299 // Finally, let's apply the specific rules dealing with the different chain types 300 301 if (firstVisibleWidget != null 302 && (firstVisibleWidget == lastVisibleWidget || isChainPacked)) { 303 ConstraintAnchor begin = first.mListAnchors[offset]; 304 ConstraintAnchor end = last.mListAnchors[offset + 1]; 305 SolverVariable beginTarget = begin.mTarget != null 306 ? begin.mTarget.mSolverVariable : null; 307 SolverVariable endTarget = end.mTarget != null ? end.mTarget.mSolverVariable : null; 308 begin = firstVisibleWidget.mListAnchors[offset]; 309 if (lastVisibleWidget != null) { 310 end = lastVisibleWidget.mListAnchors[offset + 1]; 311 } 312 if (beginTarget != null && endTarget != null) { 313 float bias = 0.5f; 314 if (orientation == ConstraintWidget.HORIZONTAL) { 315 bias = head.mHorizontalBiasPercent; 316 } else { 317 bias = head.mVerticalBiasPercent; 318 } 319 int beginMargin = begin.getMargin(); 320 int endMargin = end.getMargin(); 321 system.addCentering(begin.mSolverVariable, beginTarget, 322 beginMargin, bias, endTarget, end.mSolverVariable, 323 endMargin, SolverVariable.STRENGTH_CENTERING); 324 } 325 } else if (isChainSpread && firstVisibleWidget != null) { 326 // for chain spread, we need to add equal dimensions in between *visible* widgets 327 widget = firstVisibleWidget; 328 ConstraintWidget previousVisibleWidget = firstVisibleWidget; 329 boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0 330 && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount); 331 while (widget != null) { 332 next = widget.mNextChainWidget[orientation]; 333 while (next != null && next.getVisibility() == GONE) { 334 next = next.mNextChainWidget[orientation]; 335 } 336 if (next != null || widget == lastVisibleWidget) { 337 ConstraintAnchor beginAnchor = widget.mListAnchors[offset]; 338 SolverVariable begin = beginAnchor.mSolverVariable; 339 SolverVariable beginTarget = beginAnchor.mTarget != null 340 ? beginAnchor.mTarget.mSolverVariable : null; 341 if (previousVisibleWidget != widget) { 342 beginTarget = 343 previousVisibleWidget.mListAnchors[offset + 1].mSolverVariable; 344 } else if (widget == firstVisibleWidget) { 345 beginTarget = first.mListAnchors[offset].mTarget != null 346 ? first.mListAnchors[offset].mTarget.mSolverVariable : null; 347 } 348 349 ConstraintAnchor beginNextAnchor = null; 350 SolverVariable beginNext = null; 351 @SuppressWarnings("unused") SolverVariable beginNextTarget = null; 352 int beginMargin = beginAnchor.getMargin(); 353 int nextMargin = widget.mListAnchors[offset + 1].getMargin(); 354 355 if (next != null) { 356 beginNextAnchor = next.mListAnchors[offset]; 357 beginNext = beginNextAnchor.mSolverVariable; 358 } else { 359 beginNextAnchor = last.mListAnchors[offset + 1].mTarget; 360 if (beginNextAnchor != null) { 361 beginNext = beginNextAnchor.mSolverVariable; 362 } 363 } 364 beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable; 365 366 if (beginNextAnchor != null) { 367 nextMargin += beginNextAnchor.getMargin(); 368 } 369 beginMargin += previousVisibleWidget.mListAnchors[offset + 1].getMargin(); 370 if (begin != null && beginTarget != null 371 && beginNext != null && beginNextTarget != null) { 372 int margin1 = beginMargin; 373 if (widget == firstVisibleWidget) { 374 margin1 = firstVisibleWidget.mListAnchors[offset].getMargin(); 375 } 376 int margin2 = nextMargin; 377 if (widget == lastVisibleWidget) { 378 margin2 = lastVisibleWidget.mListAnchors[offset + 1].getMargin(); 379 } 380 int strength = SolverVariable.STRENGTH_EQUALITY; 381 if (applyFixedEquality) { 382 strength = SolverVariable.STRENGTH_FIXED; 383 } 384 system.addCentering(begin, beginTarget, margin1, 0.5f, 385 beginNext, beginNextTarget, margin2, 386 strength); 387 } 388 } 389 if (widget.getVisibility() != GONE) { 390 previousVisibleWidget = widget; 391 } 392 widget = next; 393 } 394 } else if (isChainSpreadInside && firstVisibleWidget != null) { 395 // for chain spread inside, we need to add equal dimensions in between *visible* widgets 396 widget = firstVisibleWidget; 397 ConstraintWidget previousVisibleWidget = firstVisibleWidget; 398 boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0 399 && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount); 400 while (widget != null) { 401 next = widget.mNextChainWidget[orientation]; 402 while (next != null && next.getVisibility() == GONE) { 403 next = next.mNextChainWidget[orientation]; 404 } 405 if (widget != firstVisibleWidget && widget != lastVisibleWidget && next != null) { 406 if (next == lastVisibleWidget) { 407 next = null; 408 } 409 ConstraintAnchor beginAnchor = widget.mListAnchors[offset]; 410 SolverVariable begin = beginAnchor.mSolverVariable; 411 @SuppressWarnings("unused") SolverVariable beginTarget = 412 beginAnchor.mTarget != null 413 ? beginAnchor.mTarget.mSolverVariable : null; 414 beginTarget = previousVisibleWidget.mListAnchors[offset + 1].mSolverVariable; 415 ConstraintAnchor beginNextAnchor = null; 416 SolverVariable beginNext = null; 417 SolverVariable beginNextTarget = null; 418 int beginMargin = beginAnchor.getMargin(); 419 int nextMargin = widget.mListAnchors[offset + 1].getMargin(); 420 421 if (next != null) { 422 beginNextAnchor = next.mListAnchors[offset]; 423 beginNext = beginNextAnchor.mSolverVariable; 424 beginNextTarget = beginNextAnchor.mTarget != null 425 ? beginNextAnchor.mTarget.mSolverVariable : null; 426 } else { 427 beginNextAnchor = lastVisibleWidget.mListAnchors[offset]; 428 if (beginNextAnchor != null) { 429 beginNext = beginNextAnchor.mSolverVariable; 430 } 431 beginNextTarget = widget.mListAnchors[offset + 1].mSolverVariable; 432 } 433 434 if (beginNextAnchor != null) { 435 nextMargin += beginNextAnchor.getMargin(); 436 } 437 beginMargin += previousVisibleWidget.mListAnchors[offset + 1].getMargin(); 438 int strength = SolverVariable.STRENGTH_HIGHEST; 439 if (applyFixedEquality) { 440 strength = SolverVariable.STRENGTH_FIXED; 441 } 442 if (begin != null && beginTarget != null 443 && beginNext != null && beginNextTarget != null) { 444 system.addCentering(begin, beginTarget, beginMargin, 0.5f, 445 beginNext, beginNextTarget, nextMargin, 446 strength); 447 } 448 } 449 if (widget.getVisibility() != GONE) { 450 previousVisibleWidget = widget; 451 } 452 widget = next; 453 } 454 ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset]; 455 ConstraintAnchor beginTarget = first.mListAnchors[offset].mTarget; 456 ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1]; 457 ConstraintAnchor endTarget = last.mListAnchors[offset + 1].mTarget; 458 int endPointsStrength = SolverVariable.STRENGTH_EQUALITY; 459 if (beginTarget != null) { 460 if (firstVisibleWidget != lastVisibleWidget) { 461 system.addEquality(begin.mSolverVariable, beginTarget.mSolverVariable, 462 begin.getMargin(), endPointsStrength); 463 } else if (endTarget != null) { 464 system.addCentering(begin.mSolverVariable, beginTarget.mSolverVariable, 465 begin.getMargin(), 0.5f, end.mSolverVariable, endTarget.mSolverVariable, 466 end.getMargin(), endPointsStrength); 467 } 468 } 469 if (endTarget != null && (firstVisibleWidget != lastVisibleWidget)) { 470 system.addEquality(end.mSolverVariable, 471 endTarget.mSolverVariable, -end.getMargin(), endPointsStrength); 472 } 473 474 } 475 476 // final centering, necessary if the chain is larger than the available space... 477 if ((isChainSpread || isChainSpreadInside) && firstVisibleWidget 478 != null && firstVisibleWidget != lastVisibleWidget) { 479 ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset]; 480 if (lastVisibleWidget == null) { 481 lastVisibleWidget = firstVisibleWidget; 482 } 483 ConstraintAnchor end = lastVisibleWidget.mListAnchors[offset + 1]; 484 SolverVariable beginTarget = 485 begin.mTarget != null ? begin.mTarget.mSolverVariable : null; 486 SolverVariable endTarget = end.mTarget != null ? end.mTarget.mSolverVariable : null; 487 if (last != lastVisibleWidget) { 488 ConstraintAnchor realEnd = last.mListAnchors[offset + 1]; 489 endTarget = realEnd.mTarget != null ? realEnd.mTarget.mSolverVariable : null; 490 } 491 if (firstVisibleWidget == lastVisibleWidget) { 492 begin = firstVisibleWidget.mListAnchors[offset]; 493 end = firstVisibleWidget.mListAnchors[offset + 1]; 494 } 495 if (beginTarget != null && endTarget != null) { 496 float bias = 0.5f; 497 int beginMargin = begin.getMargin(); 498 int endMargin = lastVisibleWidget.mListAnchors[offset + 1].getMargin(); 499 system.addCentering(begin.mSolverVariable, beginTarget, beginMargin, 500 bias, endTarget, end.mSolverVariable, endMargin, 501 SolverVariable.STRENGTH_EQUALITY); 502 } 503 } 504 } 505 } 506