1 /* 2 * Copyright (C) 2015 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.LinearSystem.FULL_DEBUG; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 22 23 import androidx.constraintlayout.core.LinearSystem; 24 import androidx.constraintlayout.core.Metrics; 25 import androidx.constraintlayout.core.SolverVariable; 26 import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure; 27 import androidx.constraintlayout.core.widgets.analyzer.DependencyGraph; 28 import androidx.constraintlayout.core.widgets.analyzer.Direct; 29 import androidx.constraintlayout.core.widgets.analyzer.Grouping; 30 31 import java.lang.ref.WeakReference; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.HashSet; 35 import java.util.List; 36 37 /** 38 * A container of ConstraintWidget that can layout its children 39 */ 40 public class ConstraintWidgetContainer extends WidgetContainer { 41 42 private static final int MAX_ITERATIONS = 8; 43 44 private static final boolean DEBUG = FULL_DEBUG; 45 private static final boolean DEBUG_LAYOUT = false; 46 static final boolean DEBUG_GRAPH = false; 47 48 BasicMeasure mBasicMeasureSolver = new BasicMeasure(this); 49 50 //////////////////////////////////////////////////////////////////////////////////////////////// 51 // Graph measures 52 //////////////////////////////////////////////////////////////////////////////////////////////// 53 54 public DependencyGraph mDependencyGraph = new DependencyGraph(this); 55 private int mPass; // number of layout passes 56 57 /** 58 * Invalidate the graph of constraints 59 */ invalidateGraph()60 public void invalidateGraph() { 61 mDependencyGraph.invalidateGraph(); 62 } 63 64 /** 65 * Invalidate the widgets measures 66 */ invalidateMeasures()67 public void invalidateMeasures() { 68 mDependencyGraph.invalidateMeasures(); 69 } 70 71 72 // @TODO: add description directMeasure(boolean optimizeWrap)73 public boolean directMeasure(boolean optimizeWrap) { 74 return mDependencyGraph.directMeasure(optimizeWrap); 75 // int paddingLeft = getX(); 76 // int paddingTop = getY(); 77 // if (mDependencyGraph.directMeasureSetup(optimizeWrap)) { 78 // mDependencyGraph.measureWidgets(); 79 // boolean allResolved = 80 // mDependencyGraph.directMeasureWithOrientation(optimizeWrap, HORIZONTAL); 81 // allResolved &= mDependencyGraph.directMeasureWithOrientation(optimizeWrap, VERTICAL); 82 // for (ConstraintWidget child : mChildren) { 83 // child.setDrawX(child.getDrawX() + paddingLeft); 84 // child.setDrawY(child.getDrawY() + paddingTop); 85 // } 86 // setX(paddingLeft); 87 // setY(paddingTop); 88 // return allResolved; 89 // } 90 // return false; 91 } 92 93 // @TODO: add description directMeasureSetup(boolean optimizeWrap)94 public boolean directMeasureSetup(boolean optimizeWrap) { 95 return mDependencyGraph.directMeasureSetup(optimizeWrap); 96 } 97 98 // @TODO: add description directMeasureWithOrientation(boolean optimizeWrap, int orientation)99 public boolean directMeasureWithOrientation(boolean optimizeWrap, int orientation) { 100 return mDependencyGraph.directMeasureWithOrientation(optimizeWrap, orientation); 101 } 102 103 // @TODO: add description defineTerminalWidgets()104 public void defineTerminalWidgets() { 105 mDependencyGraph.defineTerminalWidgets(getHorizontalDimensionBehaviour(), 106 getVerticalDimensionBehaviour()); 107 } 108 109 //////////////////////////////////////////////////////////////////////////////////////////////// 110 111 /** 112 * Measure the layout 113 */ measure(int optimizationLevel, int widthMode, int widthSize, int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight, int paddingX, int paddingY)114 public long measure(int optimizationLevel, int widthMode, int widthSize, 115 int heightMode, int heightSize, int lastMeasureWidth, 116 int lastMeasureHeight, int paddingX, int paddingY) { 117 mPaddingLeft = paddingX; 118 mPaddingTop = paddingY; 119 return mBasicMeasureSolver.solverMeasure(this, optimizationLevel, paddingX, paddingY, 120 widthMode, widthSize, heightMode, heightSize, 121 lastMeasureWidth, lastMeasureHeight); 122 } 123 124 // @TODO: add description updateHierarchy()125 public void updateHierarchy() { 126 mBasicMeasureSolver.updateHierarchy(this); 127 } 128 129 protected BasicMeasure.Measurer mMeasurer = null; 130 131 // @TODO: add description setMeasurer(BasicMeasure.Measurer measurer)132 public void setMeasurer(BasicMeasure.Measurer measurer) { 133 mMeasurer = measurer; 134 mDependencyGraph.setMeasurer(measurer); 135 } 136 getMeasurer()137 public BasicMeasure.Measurer getMeasurer() { 138 return mMeasurer; 139 } 140 141 private boolean mIsRtl = false; 142 public Metrics mMetrics; 143 144 // @TODO: add description fillMetrics(Metrics metrics)145 public void fillMetrics(Metrics metrics) { 146 mMetrics = metrics; 147 mSystem.fillMetrics(metrics); 148 } 149 150 protected LinearSystem mSystem = new LinearSystem(); 151 152 int mPaddingLeft; 153 int mPaddingTop; 154 int mPaddingRight; 155 int mPaddingBottom; 156 157 public int mHorizontalChainsSize = 0; 158 public int mVerticalChainsSize = 0; 159 160 ChainHead[] mVerticalChainsArray = new ChainHead[4]; 161 ChainHead[] mHorizontalChainsArray = new ChainHead[4]; 162 163 public boolean mGroupsWrapOptimized = false; 164 public boolean mHorizontalWrapOptimized = false; 165 public boolean mVerticalWrapOptimized = false; 166 public int mWrapFixedWidth = 0; 167 public int mWrapFixedHeight = 0; 168 169 private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD; 170 public boolean mSkipSolver = false; 171 172 private boolean mWidthMeasuredTooSmall = false; 173 private boolean mHeightMeasuredTooSmall = false; 174 175 /*-----------------------------------------------------------------------*/ 176 // Construction 177 /*-----------------------------------------------------------------------*/ 178 179 /** 180 * Default constructor 181 */ ConstraintWidgetContainer()182 public ConstraintWidgetContainer() { 183 } 184 185 /** 186 * Constructor 187 * 188 * @param x x position 189 * @param y y position 190 * @param width width of the layout 191 * @param height height of the layout 192 */ ConstraintWidgetContainer(int x, int y, int width, int height)193 public ConstraintWidgetContainer(int x, int y, int width, int height) { 194 super(x, y, width, height); 195 } 196 197 /** 198 * Constructor 199 * 200 * @param width width of the layout 201 * @param height height of the layout 202 */ ConstraintWidgetContainer(int width, int height)203 public ConstraintWidgetContainer(int width, int height) { 204 super(width, height); 205 } 206 ConstraintWidgetContainer(String debugName, int width, int height)207 public ConstraintWidgetContainer(String debugName, int width, int height) { 208 super(width, height); 209 setDebugName(debugName); 210 } 211 212 /** 213 * Resolves the system directly when possible 214 * 215 * @param value optimization level 216 */ setOptimizationLevel(int value)217 public void setOptimizationLevel(int value) { 218 mOptimizationLevel = value; 219 mSystem.USE_DEPENDENCY_ORDERING = optimizeFor(Optimizer.OPTIMIZATION_DEPENDENCY_ORDERING); 220 } 221 222 /** 223 * Returns the current optimization level 224 */ getOptimizationLevel()225 public int getOptimizationLevel() { 226 return mOptimizationLevel; 227 } 228 229 /** 230 * Returns true if the given feature should be optimized 231 */ optimizeFor(int feature)232 public boolean optimizeFor(int feature) { 233 return (mOptimizationLevel & feature) == feature; 234 } 235 236 /** 237 * Specify the xml type for the container 238 */ 239 @Override getType()240 public String getType() { 241 return "ConstraintLayout"; 242 } 243 244 @Override reset()245 public void reset() { 246 mSystem.reset(); 247 mPaddingLeft = 0; 248 mPaddingRight = 0; 249 mPaddingTop = 0; 250 mPaddingBottom = 0; 251 mSkipSolver = false; 252 super.reset(); 253 } 254 255 /** 256 * Return true if the width given is too small for the content laid out 257 */ isWidthMeasuredTooSmall()258 public boolean isWidthMeasuredTooSmall() { 259 return mWidthMeasuredTooSmall; 260 } 261 262 /** 263 * Return true if the height given is too small for the content laid out 264 */ isHeightMeasuredTooSmall()265 public boolean isHeightMeasuredTooSmall() { 266 return mHeightMeasuredTooSmall; 267 } 268 269 int mDebugSolverPassCount = 0; 270 271 private WeakReference<ConstraintAnchor> mVerticalWrapMin = null; 272 private WeakReference<ConstraintAnchor> mHorizontalWrapMin = null; 273 private WeakReference<ConstraintAnchor> mVerticalWrapMax = null; 274 private WeakReference<ConstraintAnchor> mHorizontalWrapMax = null; 275 addVerticalWrapMinVariable(ConstraintAnchor top)276 void addVerticalWrapMinVariable(ConstraintAnchor top) { 277 if (mVerticalWrapMin == null || mVerticalWrapMin.get() == null 278 || top.getFinalValue() > mVerticalWrapMin.get().getFinalValue()) { 279 mVerticalWrapMin = new WeakReference<>(top); 280 } 281 } 282 283 // @TODO: add description addHorizontalWrapMinVariable(ConstraintAnchor left)284 public void addHorizontalWrapMinVariable(ConstraintAnchor left) { 285 if (mHorizontalWrapMin == null || mHorizontalWrapMin.get() == null 286 || left.getFinalValue() > mHorizontalWrapMin.get().getFinalValue()) { 287 mHorizontalWrapMin = new WeakReference<>(left); 288 } 289 } 290 addVerticalWrapMaxVariable(ConstraintAnchor bottom)291 void addVerticalWrapMaxVariable(ConstraintAnchor bottom) { 292 if (mVerticalWrapMax == null || mVerticalWrapMax.get() == null 293 || bottom.getFinalValue() > mVerticalWrapMax.get().getFinalValue()) { 294 mVerticalWrapMax = new WeakReference<>(bottom); 295 } 296 } 297 298 // @TODO: add description addHorizontalWrapMaxVariable(ConstraintAnchor right)299 public void addHorizontalWrapMaxVariable(ConstraintAnchor right) { 300 if (mHorizontalWrapMax == null || mHorizontalWrapMax.get() == null 301 || right.getFinalValue() > mHorizontalWrapMax.get().getFinalValue()) { 302 mHorizontalWrapMax = new WeakReference<>(right); 303 } 304 } 305 addMinWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMin)306 private void addMinWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMin) { 307 SolverVariable variable = mSystem.createObjectVariable(constraintAnchor); 308 int wrapStrength = SolverVariable.STRENGTH_EQUALITY; 309 mSystem.addGreaterThan(variable, parentMin, 0, wrapStrength); 310 } 311 addMaxWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMax)312 private void addMaxWrap(ConstraintAnchor constraintAnchor, SolverVariable parentMax) { 313 SolverVariable variable = mSystem.createObjectVariable(constraintAnchor); 314 int wrapStrength = SolverVariable.STRENGTH_EQUALITY; 315 mSystem.addGreaterThan(parentMax, variable, 0, wrapStrength); 316 } 317 318 HashSet<ConstraintWidget> mWidgetsToAdd = new HashSet<>(); 319 320 /** 321 * Add this widget to the solver 322 * 323 * @param system the solver we want to add the widget to 324 */ addChildrenToSolver(LinearSystem system)325 public boolean addChildrenToSolver(LinearSystem system) { 326 if (DEBUG) { 327 System.out.println("\n#######################################"); 328 System.out.println("## ADD CHILDREN TO SOLVER (" + mDebugSolverPassCount + ") ##"); 329 System.out.println("#######################################\n"); 330 mDebugSolverPassCount++; 331 } 332 333 boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH); 334 addToSolver(system, optimize); 335 final int count = mChildren.size(); 336 337 boolean hasBarriers = false; 338 for (int i = 0; i < count; i++) { 339 ConstraintWidget widget = mChildren.get(i); 340 widget.setInBarrier(HORIZONTAL, false); 341 widget.setInBarrier(VERTICAL, false); 342 if (widget instanceof Barrier) { 343 hasBarriers = true; 344 } 345 } 346 347 if (hasBarriers) { 348 for (int i = 0; i < count; i++) { 349 ConstraintWidget widget = mChildren.get(i); 350 if (widget instanceof Barrier) { 351 ((Barrier) widget).markWidgets(); 352 } 353 } 354 } 355 356 mWidgetsToAdd.clear(); 357 for (int i = 0; i < count; i++) { 358 ConstraintWidget widget = mChildren.get(i); 359 if (widget.addFirst()) { 360 if (widget instanceof VirtualLayout) { 361 mWidgetsToAdd.add(widget); 362 } else { 363 widget.addToSolver(system, optimize); 364 } 365 } 366 } 367 368 // If we have virtual layouts, we need to add them to the solver in the correct 369 // order (in case they reference one another) 370 while (mWidgetsToAdd.size() > 0) { 371 int numLayouts = mWidgetsToAdd.size(); 372 VirtualLayout layout = null; 373 for (ConstraintWidget widget : mWidgetsToAdd) { 374 layout = (VirtualLayout) widget; 375 376 // we'll go through the virtual layouts that references others first, to give 377 // them a shot at setting their constraints. 378 if (layout.contains(mWidgetsToAdd)) { 379 layout.addToSolver(system, optimize); 380 mWidgetsToAdd.remove(layout); 381 break; 382 } 383 } 384 if (numLayouts == mWidgetsToAdd.size()) { 385 // looks we didn't find anymore dependency, let's add everything. 386 for (ConstraintWidget widget : mWidgetsToAdd) { 387 widget.addToSolver(system, optimize); 388 } 389 mWidgetsToAdd.clear(); 390 } 391 } 392 393 if (LinearSystem.USE_DEPENDENCY_ORDERING) { 394 HashSet<ConstraintWidget> widgetsToAdd = new HashSet<>(); 395 for (int i = 0; i < count; i++) { 396 ConstraintWidget widget = mChildren.get(i); 397 if (!widget.addFirst()) { 398 widgetsToAdd.add(widget); 399 } 400 } 401 int orientation = VERTICAL; 402 if (getHorizontalDimensionBehaviour() == WRAP_CONTENT) { 403 orientation = HORIZONTAL; 404 } 405 addChildrenToSolverByDependency(this, system, widgetsToAdd, orientation, false); 406 for (ConstraintWidget widget : widgetsToAdd) { 407 Optimizer.checkMatchParent(this, system, widget); 408 widget.addToSolver(system, optimize); 409 } 410 } else { 411 412 for (int i = 0; i < count; i++) { 413 ConstraintWidget widget = mChildren.get(i); 414 if (widget instanceof ConstraintWidgetContainer) { 415 DimensionBehaviour horizontalBehaviour = 416 widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL]; 417 DimensionBehaviour verticalBehaviour = 418 widget.mListDimensionBehaviors[DIMENSION_VERTICAL]; 419 if (horizontalBehaviour == WRAP_CONTENT) { 420 widget.setHorizontalDimensionBehaviour(FIXED); 421 } 422 if (verticalBehaviour == WRAP_CONTENT) { 423 widget.setVerticalDimensionBehaviour(FIXED); 424 } 425 widget.addToSolver(system, optimize); 426 if (horizontalBehaviour == WRAP_CONTENT) { 427 widget.setHorizontalDimensionBehaviour(horizontalBehaviour); 428 } 429 if (verticalBehaviour == WRAP_CONTENT) { 430 widget.setVerticalDimensionBehaviour(verticalBehaviour); 431 } 432 } else { 433 Optimizer.checkMatchParent(this, system, widget); 434 if (!widget.addFirst()) { 435 widget.addToSolver(system, optimize); 436 } 437 } 438 } 439 } 440 441 if (mHorizontalChainsSize > 0) { 442 Chain.applyChainConstraints(this, system, null, HORIZONTAL); 443 } 444 if (mVerticalChainsSize > 0) { 445 Chain.applyChainConstraints(this, system, null, VERTICAL); 446 } 447 return true; 448 } 449 450 /** 451 * Update the frame of the layout and its children from the solver 452 * 453 * @param system the solver we get the values from. 454 * @param flags the flag set associated with this solver 455 */ updateChildrenFromSolver(LinearSystem system, boolean[] flags)456 public boolean updateChildrenFromSolver(LinearSystem system, boolean[] flags) { 457 flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = false; 458 boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH); 459 updateFromSolver(system, optimize); 460 final int count = mChildren.size(); 461 boolean hasOverride = false; 462 for (int i = 0; i < count; i++) { 463 ConstraintWidget widget = mChildren.get(i); 464 widget.updateFromSolver(system, optimize); 465 if (widget.hasDimensionOverride()) { 466 hasOverride = true; 467 } 468 } 469 return hasOverride; 470 } 471 472 @Override updateFromRuns(boolean updateHorizontal, boolean updateVertical)473 public void updateFromRuns(boolean updateHorizontal, boolean updateVertical) { 474 super.updateFromRuns(updateHorizontal, updateVertical); 475 final int count = mChildren.size(); 476 for (int i = 0; i < count; i++) { 477 ConstraintWidget widget = mChildren.get(i); 478 widget.updateFromRuns(updateHorizontal, updateVertical); 479 } 480 } 481 482 /** 483 * Set the padding on this container. It will apply to the position of the children. 484 * 485 * @param left left padding 486 * @param top top padding 487 * @param right right padding 488 * @param bottom bottom padding 489 */ setPadding(int left, int top, int right, int bottom)490 public void setPadding(int left, int top, int right, int bottom) { 491 mPaddingLeft = left; 492 mPaddingTop = top; 493 mPaddingRight = right; 494 mPaddingBottom = bottom; 495 } 496 497 /** 498 * Set the rtl status. This has implications for Chains. 499 * 500 * @param isRtl true if we are in RTL. 501 */ setRtl(boolean isRtl)502 public void setRtl(boolean isRtl) { 503 mIsRtl = isRtl; 504 } 505 506 /** 507 * Returns the rtl status. 508 * 509 * @return true if in RTL, false otherwise. 510 */ isRtl()511 public boolean isRtl() { 512 return mIsRtl; 513 } 514 515 /*-----------------------------------------------------------------------*/ 516 // Overloaded methods from ConstraintWidget 517 /*-----------------------------------------------------------------------*/ 518 519 public BasicMeasure.Measure mMeasure = new BasicMeasure.Measure(); 520 521 // @TODO: add description measure(int level, ConstraintWidget widget, BasicMeasure.Measurer measurer, BasicMeasure.Measure measure, int measureStrategy)522 public static boolean measure(int level, 523 ConstraintWidget widget, 524 BasicMeasure.Measurer measurer, 525 BasicMeasure.Measure measure, 526 int measureStrategy) { 527 if (DEBUG) { 528 System.out.println(Direct.ls(level) + "(M) call to measure " + widget.getDebugName()); 529 } 530 if (measurer == null) { 531 return false; 532 } 533 if (widget.getVisibility() == GONE 534 || widget instanceof Guideline 535 || widget instanceof Barrier) { 536 if (DEBUG) { 537 System.out.println(Direct.ls(level) 538 + "(M) no measure needed for " + widget.getDebugName()); 539 } 540 measure.measuredWidth = 0; 541 measure.measuredHeight = 0; 542 return false; 543 } 544 545 measure.horizontalBehavior = widget.getHorizontalDimensionBehaviour(); 546 measure.verticalBehavior = widget.getVerticalDimensionBehaviour(); 547 measure.horizontalDimension = widget.getWidth(); 548 measure.verticalDimension = widget.getHeight(); 549 measure.measuredNeedsSolverPass = false; 550 measure.measureStrategy = measureStrategy; 551 552 boolean horizontalMatchConstraints = 553 (measure.horizontalBehavior == DimensionBehaviour.MATCH_CONSTRAINT); 554 boolean verticalMatchConstraints = 555 (measure.verticalBehavior == DimensionBehaviour.MATCH_CONSTRAINT); 556 557 boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0; 558 boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0; 559 560 if (horizontalMatchConstraints && widget.hasDanglingDimension(HORIZONTAL) 561 && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD 562 && !horizontalUseRatio) { 563 horizontalMatchConstraints = false; 564 measure.horizontalBehavior = WRAP_CONTENT; 565 if (verticalMatchConstraints 566 && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) { 567 // if match x match, size would be zero. 568 measure.horizontalBehavior = FIXED; 569 } 570 } 571 572 if (verticalMatchConstraints && widget.hasDanglingDimension(VERTICAL) 573 && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD 574 && !verticalUseRatio) { 575 verticalMatchConstraints = false; 576 measure.verticalBehavior = WRAP_CONTENT; 577 if (horizontalMatchConstraints 578 && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) { 579 // if match x match, size would be zero. 580 measure.verticalBehavior = FIXED; 581 } 582 } 583 584 if (widget.isResolvedHorizontally()) { 585 horizontalMatchConstraints = false; 586 measure.horizontalBehavior = FIXED; 587 } 588 if (widget.isResolvedVertically()) { 589 verticalMatchConstraints = false; 590 measure.verticalBehavior = FIXED; 591 } 592 593 if (horizontalUseRatio) { 594 if (widget.mResolvedMatchConstraintDefault[HORIZONTAL] 595 == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) { 596 measure.horizontalBehavior = FIXED; 597 } else if (!verticalMatchConstraints) { 598 // let's measure here 599 int measuredHeight; 600 if (measure.verticalBehavior == FIXED) { 601 measuredHeight = measure.verticalDimension; 602 } else { 603 measure.horizontalBehavior = WRAP_CONTENT; 604 measurer.measure(widget, measure); 605 measuredHeight = measure.measuredHeight; 606 } 607 measure.horizontalBehavior = FIXED; 608 // regardless of which side we are using for the ratio, getDimensionRatio() already 609 // made sure that it's expressed in WxH format, so we can simply go and multiply 610 measure.horizontalDimension = (int) (widget.getDimensionRatio() * measuredHeight); 611 if (DEBUG) { 612 System.out.println("(M) Measured once for ratio on horizontal side..."); 613 } 614 } 615 } 616 if (verticalUseRatio) { 617 if (widget.mResolvedMatchConstraintDefault[VERTICAL] 618 == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) { 619 measure.verticalBehavior = FIXED; 620 } else if (!horizontalMatchConstraints) { 621 // let's measure here 622 int measuredWidth; 623 if (measure.horizontalBehavior == FIXED) { 624 measuredWidth = measure.horizontalDimension; 625 } else { 626 measure.verticalBehavior = WRAP_CONTENT; 627 measurer.measure(widget, measure); 628 measuredWidth = measure.measuredWidth; 629 } 630 measure.verticalBehavior = FIXED; 631 if (widget.getDimensionRatioSide() == -1) { 632 // regardless of which side we are using for the ratio, 633 // getDimensionRatio() already 634 // made sure that it's expressed in WxH format, 635 // so we can simply go and divide 636 measure.verticalDimension = (int) (measuredWidth / widget.getDimensionRatio()); 637 } else { 638 // getDimensionRatio() already got reverted, so we can simply multiply 639 measure.verticalDimension = (int) (widget.getDimensionRatio() * measuredWidth); 640 } 641 if (DEBUG) { 642 System.out.println("(M) Measured once for ratio on vertical side..."); 643 } 644 } 645 } 646 647 measurer.measure(widget, measure); 648 widget.setWidth(measure.measuredWidth); 649 widget.setHeight(measure.measuredHeight); 650 widget.setHasBaseline(measure.measuredHasBaseline); 651 widget.setBaselineDistance(measure.measuredBaseline); 652 measure.measureStrategy = BasicMeasure.Measure.SELF_DIMENSIONS; 653 if (DEBUG) { 654 System.out.println("(M) Measured " + widget.getDebugName() + " with : " 655 + widget.getHorizontalDimensionBehaviour() + " x " 656 + widget.getVerticalDimensionBehaviour() + " => " 657 + widget.getWidth() + " x " + widget.getHeight()); 658 } 659 return measure.measuredNeedsSolverPass; 660 } 661 662 static int sMyCounter = 0; 663 664 /** 665 * Layout the tree of widgets 666 */ 667 @Override layout()668 public void layout() { 669 if (DEBUG) { 670 System.out.println("\n#####################################"); 671 System.out.println("## CL LAYOUT PASS ##"); 672 System.out.println("#####################################\n"); 673 mDebugSolverPassCount = 0; 674 } 675 676 mX = 0; 677 mY = 0; 678 679 mWidthMeasuredTooSmall = false; 680 mHeightMeasuredTooSmall = false; 681 final int count = mChildren.size(); 682 683 int preW = Math.max(0, getWidth()); 684 int preH = Math.max(0, getHeight()); 685 DimensionBehaviour originalVerticalDimensionBehaviour = 686 mListDimensionBehaviors[DIMENSION_VERTICAL]; 687 DimensionBehaviour originalHorizontalDimensionBehaviour = 688 mListDimensionBehaviors[DIMENSION_HORIZONTAL]; 689 690 if (DEBUG_LAYOUT) { 691 System.out.println("layout with preW: " + preW + " (" 692 + mListDimensionBehaviors[DIMENSION_HORIZONTAL] + ") preH: " + preH 693 + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL] + ")"); 694 } 695 696 if (mMetrics != null) { 697 mMetrics.layouts++; 698 } 699 700 701 boolean wrap_override = false; 702 703 if (FULL_DEBUG) { 704 System.out.println("OPTIMIZATION LEVEL " + mOptimizationLevel); 705 } 706 707 // Only try the direct optimization in the first layout pass 708 if (mPass == 0 && Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_DIRECT)) { 709 if (FULL_DEBUG) { 710 System.out.println("Direct pass " + sMyCounter++); 711 } 712 Direct.solvingPass(this, getMeasurer()); 713 if (FULL_DEBUG) { 714 System.out.println("Direct pass done."); 715 } 716 for (int i = 0; i < count; i++) { 717 ConstraintWidget child = mChildren.get(i); 718 if (FULL_DEBUG) { 719 if (child.isInHorizontalChain()) { 720 System.out.print("H"); 721 } else { 722 System.out.print(" "); 723 } 724 if (child.isInVerticalChain()) { 725 System.out.print("V"); 726 } else { 727 System.out.print(" "); 728 } 729 if (child.isResolvedHorizontally() && child.isResolvedVertically()) { 730 System.out.print("*"); 731 } else { 732 System.out.print(" "); 733 } 734 System.out.println("[" + i + "] child " + child.getDebugName() 735 + " H: " + child.isResolvedHorizontally() 736 + " V: " + child.isResolvedVertically()); 737 } 738 if (child.isMeasureRequested() 739 && !(child instanceof Guideline) 740 && !(child instanceof Barrier) 741 && !(child instanceof VirtualLayout) 742 && !child.isInVirtualLayout()) { 743 DimensionBehaviour widthBehavior = child.getDimensionBehaviour(HORIZONTAL); 744 DimensionBehaviour heightBehavior = child.getDimensionBehaviour(VERTICAL); 745 746 boolean skip = widthBehavior == DimensionBehaviour.MATCH_CONSTRAINT 747 && child.mMatchConstraintDefaultWidth != MATCH_CONSTRAINT_WRAP 748 && heightBehavior == DimensionBehaviour.MATCH_CONSTRAINT 749 && child.mMatchConstraintDefaultHeight != MATCH_CONSTRAINT_WRAP; 750 if (!skip) { 751 BasicMeasure.Measure measure = new BasicMeasure.Measure(); 752 ConstraintWidgetContainer.measure(0, child, mMeasurer, 753 measure, BasicMeasure.Measure.SELF_DIMENSIONS); 754 } 755 } 756 } 757 // let's measure children 758 if (FULL_DEBUG) { 759 System.out.println("Direct pass all done."); 760 } 761 } else { 762 if (FULL_DEBUG) { 763 System.out.println("No DIRECT PASS"); 764 } 765 } 766 767 if (count > 2 && (originalHorizontalDimensionBehaviour == WRAP_CONTENT 768 || originalVerticalDimensionBehaviour == WRAP_CONTENT) 769 && Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_GROUPING)) { 770 if (Grouping.simpleSolvingPass(this, getMeasurer())) { 771 if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) { 772 if (preW < getWidth() && preW > 0) { 773 if (DEBUG_LAYOUT) { 774 System.out.println("Override width " + getWidth() + " to " + preH); 775 } 776 setWidth(preW); 777 mWidthMeasuredTooSmall = true; 778 } else { 779 preW = getWidth(); 780 } 781 } 782 if (originalVerticalDimensionBehaviour == WRAP_CONTENT) { 783 if (preH < getHeight() && preH > 0) { 784 if (DEBUG_LAYOUT) { 785 System.out.println("Override height " + getHeight() + " to " + preH); 786 } 787 setHeight(preH); 788 mHeightMeasuredTooSmall = true; 789 } else { 790 preH = getHeight(); 791 } 792 } 793 wrap_override = true; 794 if (DEBUG_LAYOUT) { 795 System.out.println("layout post opt, preW: " + preW 796 + " (" + mListDimensionBehaviors[DIMENSION_HORIZONTAL] 797 + ") preH: " + preH + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL] 798 + "), new size " + getWidth() + " x " + getHeight()); 799 } 800 } 801 } 802 boolean useGraphOptimizer = optimizeFor(Optimizer.OPTIMIZATION_GRAPH) 803 || optimizeFor(Optimizer.OPTIMIZATION_GRAPH_WRAP); 804 805 mSystem.graphOptimizer = false; 806 mSystem.newgraphOptimizer = false; 807 808 if (mOptimizationLevel != Optimizer.OPTIMIZATION_NONE 809 && useGraphOptimizer) { 810 mSystem.newgraphOptimizer = true; 811 } 812 813 @SuppressWarnings("unused") int countSolve = 0; 814 final List<ConstraintWidget> allChildren = mChildren; 815 boolean hasWrapContent = getHorizontalDimensionBehaviour() == WRAP_CONTENT 816 || getVerticalDimensionBehaviour() == WRAP_CONTENT; 817 818 // Reset the chains before iterating on our children 819 resetChains(); 820 countSolve = 0; 821 822 // Before we solve our system, we should call layout() on any 823 // of our children that is a container. 824 for (int i = 0; i < count; i++) { 825 ConstraintWidget widget = mChildren.get(i); 826 if (widget instanceof WidgetContainer) { 827 ((WidgetContainer) widget).layout(); 828 } 829 } 830 boolean optimize = optimizeFor(Optimizer.OPTIMIZATION_GRAPH); 831 832 // Now let's solve our system as usual 833 boolean needsSolving = true; 834 while (needsSolving) { 835 countSolve++; 836 try { 837 mSystem.reset(); 838 resetChains(); 839 if (DEBUG) { 840 String debugName = getDebugName(); 841 if (debugName == null) { 842 debugName = "root"; 843 } 844 setDebugSolverName(mSystem, debugName); 845 for (int i = 0; i < count; i++) { 846 ConstraintWidget widget = mChildren.get(i); 847 if (widget.getDebugName() != null) { 848 widget.setDebugSolverName(mSystem, widget.getDebugName()); 849 } 850 } 851 } else { 852 createObjectVariables(mSystem); 853 for (int i = 0; i < count; i++) { 854 ConstraintWidget widget = mChildren.get(i); 855 widget.createObjectVariables(mSystem); 856 } 857 } 858 needsSolving = addChildrenToSolver(mSystem); 859 if (mVerticalWrapMin != null && mVerticalWrapMin.get() != null) { 860 addMinWrap(mVerticalWrapMin.get(), mSystem.createObjectVariable(mTop)); 861 mVerticalWrapMin = null; 862 } 863 if (mVerticalWrapMax != null && mVerticalWrapMax.get() != null) { 864 addMaxWrap(mVerticalWrapMax.get(), mSystem.createObjectVariable(mBottom)); 865 mVerticalWrapMax = null; 866 } 867 if (mHorizontalWrapMin != null && mHorizontalWrapMin.get() != null) { 868 addMinWrap(mHorizontalWrapMin.get(), mSystem.createObjectVariable(mLeft)); 869 mHorizontalWrapMin = null; 870 } 871 if (mHorizontalWrapMax != null && mHorizontalWrapMax.get() != null) { 872 addMaxWrap(mHorizontalWrapMax.get(), mSystem.createObjectVariable(mRight)); 873 mHorizontalWrapMax = null; 874 } 875 if (needsSolving) { 876 mSystem.minimize(); 877 } 878 } catch (Exception e) { 879 e.printStackTrace(); 880 System.out.println("EXCEPTION : " + e); 881 } 882 if (needsSolving) { 883 needsSolving = updateChildrenFromSolver(mSystem, Optimizer.sFlags); 884 } else { 885 updateFromSolver(mSystem, optimize); 886 for (int i = 0; i < count; i++) { 887 ConstraintWidget widget = mChildren.get(i); 888 widget.updateFromSolver(mSystem, optimize); 889 } 890 needsSolving = false; 891 } 892 893 if (hasWrapContent && countSolve < MAX_ITERATIONS 894 && Optimizer.sFlags[Optimizer.FLAG_RECOMPUTE_BOUNDS]) { 895 // let's get the new bounds 896 int maxX = 0; 897 int maxY = 0; 898 for (int i = 0; i < count; i++) { 899 ConstraintWidget widget = mChildren.get(i); 900 maxX = Math.max(maxX, widget.mX + widget.getWidth()); 901 maxY = Math.max(maxY, widget.mY + widget.getHeight()); 902 } 903 maxX = Math.max(mMinWidth, maxX); 904 maxY = Math.max(mMinHeight, maxY); 905 if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) { 906 if (getWidth() < maxX) { 907 if (DEBUG_LAYOUT) { 908 System.out.println( countSolve + 909 "layout override width from " + getWidth() + " vs " + maxX); 910 } 911 setWidth(maxX); 912 // force using the solver 913 mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT; 914 wrap_override = true; 915 needsSolving = true; 916 } 917 } 918 if (originalVerticalDimensionBehaviour == WRAP_CONTENT) { 919 if (getHeight() < maxY) { 920 if (DEBUG_LAYOUT) { 921 System.out.println( 922 "layout override height from " + getHeight() + " vs " + maxY); 923 } 924 setHeight(maxY); 925 // force using the solver 926 mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT; 927 wrap_override = true; 928 needsSolving = true; 929 } 930 } 931 } 932 if (true) { 933 int width = Math.max(mMinWidth, getWidth()); 934 if (width > getWidth()) { 935 if (DEBUG_LAYOUT) { 936 System.out.println( 937 "layout override 2, width from " + getWidth() + " vs " + width); 938 } 939 setWidth(width); 940 mListDimensionBehaviors[DIMENSION_HORIZONTAL] = FIXED; 941 wrap_override = true; 942 needsSolving = true; 943 } 944 int height = Math.max(mMinHeight, getHeight()); 945 if (height > getHeight()) { 946 if (DEBUG_LAYOUT) { 947 System.out.println( 948 "layout override 2, height from " + getHeight() + " vs " + height); 949 } 950 setHeight(height); 951 mListDimensionBehaviors[DIMENSION_VERTICAL] = FIXED; 952 wrap_override = true; 953 needsSolving = true; 954 } 955 956 if (!wrap_override) { 957 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT 958 && preW > 0) { 959 if (getWidth() > preW) { 960 if (DEBUG_LAYOUT) { 961 System.out.println( 962 "layout override 3, width from " + getWidth() + " vs " 963 + preW); 964 } 965 mWidthMeasuredTooSmall = true; 966 wrap_override = true; 967 mListDimensionBehaviors[DIMENSION_HORIZONTAL] = FIXED; 968 setWidth(preW); 969 needsSolving = true; 970 } 971 } 972 if (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT 973 && preH > 0) { 974 if (getHeight() > preH) { 975 if (DEBUG_LAYOUT) { 976 System.out.println( 977 "layout override 3, height from " + getHeight() + " vs " 978 + preH); 979 } 980 mHeightMeasuredTooSmall = true; 981 wrap_override = true; 982 mListDimensionBehaviors[DIMENSION_VERTICAL] = FIXED; 983 setHeight(preH); 984 needsSolving = true; 985 } 986 } 987 } 988 989 if (countSolve > MAX_ITERATIONS) { 990 needsSolving = false; 991 } 992 } 993 } 994 if (DEBUG_LAYOUT) { 995 System.out.println( 996 "Solved system in " + countSolve + " iterations (" + getWidth() + " x " 997 + getHeight() + ")"); 998 } 999 1000 mChildren = (ArrayList<ConstraintWidget>) allChildren; 1001 1002 if (wrap_override) { 1003 mListDimensionBehaviors[DIMENSION_HORIZONTAL] = originalHorizontalDimensionBehaviour; 1004 mListDimensionBehaviors[DIMENSION_VERTICAL] = originalVerticalDimensionBehaviour; 1005 } 1006 1007 resetSolverVariables(mSystem.getCache()); 1008 } 1009 1010 /** 1011 * Indicates if the container knows how to layout its content on its own 1012 * 1013 * @return true if the container does the layout, false otherwise 1014 */ handlesInternalConstraints()1015 public boolean handlesInternalConstraints() { 1016 return false; 1017 } 1018 1019 /*-----------------------------------------------------------------------*/ 1020 // Guidelines 1021 /*-----------------------------------------------------------------------*/ 1022 1023 /** 1024 * Accessor to the vertical guidelines contained in the table. 1025 * 1026 * @return array of guidelines 1027 */ getVerticalGuidelines()1028 public ArrayList<Guideline> getVerticalGuidelines() { 1029 ArrayList<Guideline> guidelines = new ArrayList<>(); 1030 for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) { 1031 final ConstraintWidget widget = mChildren.get(i); 1032 if (widget instanceof Guideline) { 1033 Guideline guideline = (Guideline) widget; 1034 if (guideline.getOrientation() == Guideline.VERTICAL) { 1035 guidelines.add(guideline); 1036 } 1037 } 1038 } 1039 return guidelines; 1040 } 1041 1042 /** 1043 * Accessor to the horizontal guidelines contained in the table. 1044 * 1045 * @return array of guidelines 1046 */ getHorizontalGuidelines()1047 public ArrayList<Guideline> getHorizontalGuidelines() { 1048 ArrayList<Guideline> guidelines = new ArrayList<>(); 1049 for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) { 1050 final ConstraintWidget widget = mChildren.get(i); 1051 if (widget instanceof Guideline) { 1052 Guideline guideline = (Guideline) widget; 1053 if (guideline.getOrientation() == Guideline.HORIZONTAL) { 1054 guidelines.add(guideline); 1055 } 1056 } 1057 } 1058 return guidelines; 1059 } 1060 getSystem()1061 public LinearSystem getSystem() { 1062 return mSystem; 1063 } 1064 1065 /*-----------------------------------------------------------------------*/ 1066 // Chains 1067 /*-----------------------------------------------------------------------*/ 1068 1069 /** 1070 * Reset the chains array. Need to be called before layout. 1071 */ resetChains()1072 private void resetChains() { 1073 mHorizontalChainsSize = 0; 1074 mVerticalChainsSize = 0; 1075 } 1076 1077 /** 1078 * Add the chain which constraintWidget is part of. Called by ConstraintWidget::addToSolver() 1079 * 1080 * @param type HORIZONTAL or VERTICAL chain 1081 */ addChain(ConstraintWidget constraintWidget, int type)1082 void addChain(ConstraintWidget constraintWidget, int type) { 1083 ConstraintWidget widget = constraintWidget; 1084 if (type == HORIZONTAL) { 1085 addHorizontalChain(widget); 1086 } else if (type == VERTICAL) { 1087 addVerticalChain(widget); 1088 } 1089 } 1090 1091 /** 1092 * Add a widget to the list of horizontal chains. The widget is the left-most widget 1093 * of the chain which doesn't have a left dual connection. 1094 * 1095 * @param widget widget starting the chain 1096 */ addHorizontalChain(ConstraintWidget widget)1097 private void addHorizontalChain(ConstraintWidget widget) { 1098 if (mHorizontalChainsSize + 1 >= mHorizontalChainsArray.length) { 1099 mHorizontalChainsArray = Arrays 1100 .copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2); 1101 } 1102 mHorizontalChainsArray[mHorizontalChainsSize] = new ChainHead(widget, HORIZONTAL, isRtl()); 1103 mHorizontalChainsSize++; 1104 } 1105 1106 /** 1107 * Add a widget to the list of vertical chains. The widget is the top-most widget 1108 * of the chain which doesn't have a top dual connection. 1109 * 1110 * @param widget widget starting the chain 1111 */ addVerticalChain(ConstraintWidget widget)1112 private void addVerticalChain(ConstraintWidget widget) { 1113 if (mVerticalChainsSize + 1 >= mVerticalChainsArray.length) { 1114 mVerticalChainsArray = Arrays 1115 .copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2); 1116 } 1117 mVerticalChainsArray[mVerticalChainsSize] = new ChainHead(widget, VERTICAL, isRtl()); 1118 mVerticalChainsSize++; 1119 } 1120 1121 /** 1122 * Keep track of the # of passes 1123 */ setPass(int pass)1124 public void setPass(int pass) { 1125 this.mPass = pass; 1126 } 1127 1128 // @TODO: add description 1129 @Override getSceneString(StringBuilder ret)1130 public void getSceneString(StringBuilder ret) { 1131 1132 ret.append(stringId + ":{\n"); 1133 ret.append(" actualWidth:" + mWidth); 1134 ret.append("\n"); 1135 ret.append(" actualHeight:" + mHeight); 1136 ret.append("\n"); 1137 1138 ArrayList<ConstraintWidget> children = getChildren(); 1139 for (ConstraintWidget child : children) { 1140 child.getSceneString(ret); 1141 ret.append(",\n"); 1142 } 1143 ret.append("}"); 1144 1145 } 1146 } 1147