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 package androidx.constraintlayout.core.widgets; 17 18 import static androidx.constraintlayout.core.LinearSystem.DEBUG; 19 import static androidx.constraintlayout.core.LinearSystem.FULL_DEBUG; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 22 23 import androidx.constraintlayout.core.Cache; 24 import androidx.constraintlayout.core.LinearSystem; 25 import androidx.constraintlayout.core.SolverVariable; 26 import androidx.constraintlayout.core.state.WidgetFrame; 27 import androidx.constraintlayout.core.widgets.analyzer.ChainRun; 28 import androidx.constraintlayout.core.widgets.analyzer.HorizontalWidgetRun; 29 import androidx.constraintlayout.core.widgets.analyzer.VerticalWidgetRun; 30 import androidx.constraintlayout.core.widgets.analyzer.WidgetRun; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 37 /** 38 * Implements a constraint Widget model supporting constraints relations between other widgets. 39 * <p> 40 * The widget has various anchors (i.e. Left, Top, Right, Bottom, representing their respective 41 * sides, as well as Baseline, Center_X and Center_Y). Connecting anchors from one widget to another 42 * represents a constraint relation between the two anchors; the {@link LinearSystem} will then 43 * be able to use this model to try to minimize the distances between connected anchors. 44 * </p> 45 * <p> 46 * If opposite anchors are connected (e.g. Left and Right anchors), if they have the same strength, 47 * the widget will be equally pulled toward their respective target anchor positions; if the widget 48 * has a fixed size, this means that the widget will be centered between the two target anchors. If 49 * the widget's size is allowed to adjust, the size of the widget will change to be as large as 50 * necessary so that the widget's anchors and the target anchors' distances are zero. 51 * </p> 52 * Constraints are set by connecting a widget's anchor to another via the 53 * {@link #connect} function. 54 */ 55 public class ConstraintWidget { 56 private static final boolean AUTOTAG_CENTER = false; 57 private static final boolean DO_NOT_USE = false; 58 protected static final int SOLVER = 1; 59 protected static final int DIRECT = 2; 60 61 // apply an intrinsic size when wrap content for spread dimensions 62 private static final boolean USE_WRAP_DIMENSION_FOR_SPREAD = false; 63 64 //////////////////////////////////////////////////////////////////////////////////////////////// 65 // Graph measurements 66 //////////////////////////////////////////////////////////////////////////////////////////////// 67 68 public boolean measured = false; 69 public WidgetRun[] run = new WidgetRun[2]; 70 public ChainRun horizontalChainRun; 71 public ChainRun verticalChainRun; 72 73 public HorizontalWidgetRun mHorizontalRun = null; 74 public VerticalWidgetRun mVerticalRun = null; 75 76 public boolean[] isTerminalWidget = {true, true}; 77 boolean mResolvedHasRatio = false; 78 private boolean mMeasureRequested = true; 79 private boolean mOptimizeWrapO = false; 80 private boolean mOptimizeWrapOnResolved = true; 81 82 private int mWidthOverride = -1; 83 private int mHeightOverride = -1; 84 85 public WidgetFrame frame = new WidgetFrame(this); 86 87 public String stringId; 88 89 // @TODO: add description getRun(int orientation)90 public WidgetRun getRun(int orientation) { 91 if (orientation == HORIZONTAL) { 92 return mHorizontalRun; 93 } else if (orientation == VERTICAL) { 94 return mVerticalRun; 95 } 96 return null; 97 } 98 99 private boolean mResolvedHorizontal = false; 100 private boolean mResolvedVertical = false; 101 102 private boolean mHorizontalSolvingPass = false; 103 private boolean mVerticalSolvingPass = false; 104 105 // @TODO: add description setFinalFrame(int left, int top, int right, int bottom, int baseline, int orientation)106 public void setFinalFrame(int left, 107 int top, 108 int right, 109 int bottom, 110 int baseline, 111 int orientation) { 112 setFrame(left, top, right, bottom); 113 setBaselineDistance(baseline); 114 if (orientation == HORIZONTAL) { 115 mResolvedHorizontal = true; 116 mResolvedVertical = false; 117 } else if (orientation == VERTICAL) { 118 mResolvedHorizontal = false; 119 mResolvedVertical = true; 120 } else if (orientation == BOTH) { 121 mResolvedHorizontal = true; 122 mResolvedVertical = true; 123 } else { 124 mResolvedHorizontal = false; 125 mResolvedVertical = false; 126 } 127 } 128 129 // @TODO: add description setFinalLeft(int x1)130 public void setFinalLeft(int x1) { 131 mLeft.setFinalValue(x1); 132 mX = x1; 133 } 134 135 // @TODO: add description setFinalTop(int y1)136 public void setFinalTop(int y1) { 137 mTop.setFinalValue(y1); 138 mY = y1; 139 } 140 141 // @TODO: add description resetSolvingPassFlag()142 public void resetSolvingPassFlag() { 143 mHorizontalSolvingPass = false; 144 mVerticalSolvingPass = false; 145 } 146 isHorizontalSolvingPassDone()147 public boolean isHorizontalSolvingPassDone() { 148 return mHorizontalSolvingPass; 149 } 150 isVerticalSolvingPassDone()151 public boolean isVerticalSolvingPassDone() { 152 return mVerticalSolvingPass; 153 } 154 155 // @TODO: add description markHorizontalSolvingPassDone()156 public void markHorizontalSolvingPassDone() { 157 mHorizontalSolvingPass = true; 158 } 159 160 // @TODO: add description markVerticalSolvingPassDone()161 public void markVerticalSolvingPassDone() { 162 mVerticalSolvingPass = true; 163 } 164 165 // @TODO: add description setFinalHorizontal(int x1, int x2)166 public void setFinalHorizontal(int x1, int x2) { 167 if (mResolvedHorizontal) { 168 return; 169 } 170 mLeft.setFinalValue(x1); 171 mRight.setFinalValue(x2); 172 mX = x1; 173 mWidth = x2 - x1; 174 mResolvedHorizontal = true; 175 if (LinearSystem.FULL_DEBUG) { 176 System.out.println("*** SET FINAL HORIZONTAL FOR " + getDebugName() 177 + " : " + x1 + " -> " + x2 + " (width: " + mWidth + ")"); 178 } 179 } 180 181 // @TODO: add description setFinalVertical(int y1, int y2)182 public void setFinalVertical(int y1, int y2) { 183 if (mResolvedVertical) { 184 return; 185 } 186 mTop.setFinalValue(y1); 187 mBottom.setFinalValue(y2); 188 mY = y1; 189 mHeight = y2 - y1; 190 if (mHasBaseline) { 191 mBaseline.setFinalValue(y1 + mBaselineDistance); 192 } 193 mResolvedVertical = true; 194 if (LinearSystem.FULL_DEBUG) { 195 System.out.println("*** SET FINAL VERTICAL FOR " + getDebugName() 196 + " : " + y1 + " -> " + y2 + " (height: " + mHeight + ")"); 197 } 198 } 199 200 // @TODO: add description setFinalBaseline(int baselineValue)201 public void setFinalBaseline(int baselineValue) { 202 if (!mHasBaseline) { 203 return; 204 } 205 int y1 = baselineValue - mBaselineDistance; 206 int y2 = y1 + mHeight; 207 mY = y1; 208 mTop.setFinalValue(y1); 209 mBottom.setFinalValue(y2); 210 mBaseline.setFinalValue(baselineValue); 211 mResolvedVertical = true; 212 } 213 isResolvedHorizontally()214 public boolean isResolvedHorizontally() { 215 return mResolvedHorizontal || (mLeft.hasFinalValue() && mRight.hasFinalValue()); 216 } 217 isResolvedVertically()218 public boolean isResolvedVertically() { 219 return mResolvedVertical || (mTop.hasFinalValue() && mBottom.hasFinalValue()); 220 } 221 222 // @TODO: add description resetFinalResolution()223 public void resetFinalResolution() { 224 mResolvedHorizontal = false; 225 mResolvedVertical = false; 226 mHorizontalSolvingPass = false; 227 mVerticalSolvingPass = false; 228 for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) { 229 final ConstraintAnchor anchor = mAnchors.get(i); 230 anchor.resetFinalResolution(); 231 } 232 } 233 234 // @TODO: add description ensureMeasureRequested()235 public void ensureMeasureRequested() { 236 mMeasureRequested = true; 237 } 238 239 // @TODO: add description hasDependencies()240 public boolean hasDependencies() { 241 for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) { 242 final ConstraintAnchor anchor = mAnchors.get(i); 243 if (anchor.hasDependents()) { 244 return true; 245 } 246 } 247 return false; 248 } 249 250 // @TODO: add description hasDanglingDimension(int orientation)251 public boolean hasDanglingDimension(int orientation) { 252 if (orientation == HORIZONTAL) { 253 int horizontalTargets = 254 (mLeft.mTarget != null ? 1 : 0) + (mRight.mTarget != null ? 1 : 0); 255 return horizontalTargets < 2; 256 } else { 257 int verticalTargets = (mTop.mTarget != null ? 1 : 0) 258 + (mBottom.mTarget != null ? 1 : 0) + (mBaseline.mTarget != null ? 1 : 0); 259 return verticalTargets < 2; 260 } 261 } 262 263 // @TODO: add description hasResolvedTargets(int orientation, int size)264 public boolean hasResolvedTargets(int orientation, int size) { 265 if (orientation == HORIZONTAL) { 266 if (mLeft.mTarget != null && mLeft.mTarget.hasFinalValue() 267 && mRight.mTarget != null && mRight.mTarget.hasFinalValue()) { 268 return ((mRight.mTarget.getFinalValue() - mRight.getMargin()) 269 - (mLeft.mTarget.getFinalValue() + mLeft.getMargin())) >= size; 270 } 271 } else { 272 if (mTop.mTarget != null && mTop.mTarget.hasFinalValue() 273 && mBottom.mTarget != null && mBottom.mTarget.hasFinalValue()) { 274 return ((mBottom.mTarget.getFinalValue() - mBottom.getMargin()) 275 - (mTop.mTarget.getFinalValue() + mTop.getMargin())) >= size; 276 } 277 } 278 return false; 279 } 280 281 //////////////////////////////////////////////////////////////////////////////////////////////// 282 283 public static final int MATCH_CONSTRAINT_SPREAD = 0; 284 public static final int MATCH_CONSTRAINT_WRAP = 1; 285 public static final int MATCH_CONSTRAINT_PERCENT = 2; 286 public static final int MATCH_CONSTRAINT_RATIO = 3; 287 public static final int MATCH_CONSTRAINT_RATIO_RESOLVED = 4; 288 289 public static final int UNKNOWN = -1; 290 public static final int HORIZONTAL = 0; 291 public static final int VERTICAL = 1; 292 public static final int BOTH = 2; 293 294 public static final int VISIBLE = 0; 295 public static final int INVISIBLE = 4; 296 public static final int GONE = 8; 297 298 // Values of the chain styles 299 public static final int CHAIN_SPREAD = 0; 300 public static final int CHAIN_SPREAD_INSIDE = 1; 301 public static final int CHAIN_PACKED = 2; 302 303 // Values of the wrap behavior in parent 304 public static final int WRAP_BEHAVIOR_INCLUDED = 0; // default 305 public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = 1; 306 public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = 2; 307 public static final int WRAP_BEHAVIOR_SKIPPED = 3; 308 309 // Support for direct resolution 310 public int mHorizontalResolution = UNKNOWN; 311 public int mVerticalResolution = UNKNOWN; 312 313 private static final int WRAP = -2; 314 315 private int mWrapBehaviorInParent = WRAP_BEHAVIOR_INCLUDED; 316 317 public int mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD; 318 public int mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD; 319 public int[] mResolvedMatchConstraintDefault = new int[2]; 320 321 public int mMatchConstraintMinWidth = 0; 322 public int mMatchConstraintMaxWidth = 0; 323 public float mMatchConstraintPercentWidth = 1; 324 public int mMatchConstraintMinHeight = 0; 325 public int mMatchConstraintMaxHeight = 0; 326 public float mMatchConstraintPercentHeight = 1; 327 public boolean mIsWidthWrapContent; 328 public boolean mIsHeightWrapContent; 329 330 int mResolvedDimensionRatioSide = UNKNOWN; 331 float mResolvedDimensionRatio = 1.0f; 332 333 private int[] mMaxDimension = {Integer.MAX_VALUE, Integer.MAX_VALUE}; 334 public float mCircleConstraintAngle = Float.NaN; 335 private boolean mHasBaseline = false; 336 private boolean mInPlaceholder; 337 338 private boolean mInVirtualLayout = false; 339 isInVirtualLayout()340 public boolean isInVirtualLayout() { 341 return mInVirtualLayout; 342 } 343 setInVirtualLayout(boolean inVirtualLayout)344 public void setInVirtualLayout(boolean inVirtualLayout) { 345 mInVirtualLayout = inVirtualLayout; 346 } 347 getMaxHeight()348 public int getMaxHeight() { 349 return mMaxDimension[VERTICAL]; 350 } 351 getMaxWidth()352 public int getMaxWidth() { 353 return mMaxDimension[HORIZONTAL]; 354 } 355 setMaxWidth(int maxWidth)356 public void setMaxWidth(int maxWidth) { 357 mMaxDimension[HORIZONTAL] = maxWidth; 358 } 359 setMaxHeight(int maxHeight)360 public void setMaxHeight(int maxHeight) { 361 mMaxDimension[VERTICAL] = maxHeight; 362 } 363 isSpreadWidth()364 public boolean isSpreadWidth() { 365 return mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD 366 && mDimensionRatio == 0 367 && mMatchConstraintMinWidth == 0 368 && mMatchConstraintMaxWidth == 0 369 && mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT; 370 } 371 isSpreadHeight()372 public boolean isSpreadHeight() { 373 return mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD 374 && mDimensionRatio == 0 375 && mMatchConstraintMinHeight == 0 376 && mMatchConstraintMaxHeight == 0 377 && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT; 378 } 379 setHasBaseline(boolean hasBaseline)380 public void setHasBaseline(boolean hasBaseline) { 381 this.mHasBaseline = hasBaseline; 382 } 383 getHasBaseline()384 public boolean getHasBaseline() { 385 return mHasBaseline; 386 } 387 isInPlaceholder()388 public boolean isInPlaceholder() { 389 return mInPlaceholder; 390 } 391 setInPlaceholder(boolean inPlaceholder)392 public void setInPlaceholder(boolean inPlaceholder) { 393 this.mInPlaceholder = inPlaceholder; 394 } 395 setInBarrier(int orientation, boolean value)396 protected void setInBarrier(int orientation, boolean value) { 397 mIsInBarrier[orientation] = value; 398 } 399 400 // @TODO: add description isInBarrier(int orientation)401 public boolean isInBarrier(int orientation) { 402 return mIsInBarrier[orientation]; 403 } 404 setMeasureRequested(boolean measureRequested)405 public void setMeasureRequested(boolean measureRequested) { 406 mMeasureRequested = measureRequested; 407 } 408 isMeasureRequested()409 public boolean isMeasureRequested() { 410 return mMeasureRequested && mVisibility != GONE; 411 } 412 413 // @TODO: add description setWrapBehaviorInParent(int behavior)414 public void setWrapBehaviorInParent(int behavior) { 415 if (behavior >= 0 && behavior <= WRAP_BEHAVIOR_SKIPPED) { 416 mWrapBehaviorInParent = behavior; 417 } 418 } 419 getWrapBehaviorInParent()420 public int getWrapBehaviorInParent() { 421 return mWrapBehaviorInParent; 422 } 423 424 /** 425 * Keep a cache of the last measure cache as we can bypass remeasures during the onMeasure... 426 * the View's measure cache will only be reset in onLayout, so too late for us. 427 */ 428 private int mLastHorizontalMeasureSpec = 0; 429 private int mLastVerticalMeasureSpec = 0; 430 getLastHorizontalMeasureSpec()431 public int getLastHorizontalMeasureSpec() { 432 return mLastHorizontalMeasureSpec; 433 } 434 getLastVerticalMeasureSpec()435 public int getLastVerticalMeasureSpec() { 436 return mLastVerticalMeasureSpec; 437 } 438 439 // @TODO: add description setLastMeasureSpec(int horizontal, int vertical)440 public void setLastMeasureSpec(int horizontal, int vertical) { 441 mLastHorizontalMeasureSpec = horizontal; 442 mLastVerticalMeasureSpec = vertical; 443 setMeasureRequested(false); 444 } 445 446 /** 447 * Define how the widget will resize 448 */ 449 public enum DimensionBehaviour { 450 FIXED, WRAP_CONTENT, MATCH_CONSTRAINT, MATCH_PARENT 451 } 452 453 // The anchors available on the widget 454 // note: all anchors should be added to the mAnchors array (see addAnchors()) 455 public ConstraintAnchor mLeft = new ConstraintAnchor(this, ConstraintAnchor.Type.LEFT); 456 public ConstraintAnchor mTop = new ConstraintAnchor(this, ConstraintAnchor.Type.TOP); 457 public ConstraintAnchor mRight = new ConstraintAnchor(this, ConstraintAnchor.Type.RIGHT); 458 public ConstraintAnchor mBottom = new ConstraintAnchor(this, ConstraintAnchor.Type.BOTTOM); 459 public ConstraintAnchor mBaseline = new ConstraintAnchor(this, ConstraintAnchor.Type.BASELINE); 460 ConstraintAnchor mCenterX = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_X); 461 ConstraintAnchor mCenterY = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_Y); 462 public ConstraintAnchor mCenter = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER); 463 464 public static final int ANCHOR_LEFT = 0; 465 public static final int ANCHOR_RIGHT = 1; 466 public static final int ANCHOR_TOP = 2; 467 public static final int ANCHOR_BOTTOM = 3; 468 public static final int ANCHOR_BASELINE = 4; 469 470 public ConstraintAnchor[] mListAnchors = {mLeft, mRight, mTop, mBottom, mBaseline, mCenter}; 471 protected ArrayList<ConstraintAnchor> mAnchors = new ArrayList<>(); 472 473 private boolean[] mIsInBarrier = new boolean[2]; 474 475 // The horizontal and vertical behaviour for the widgets' dimensions 476 static final int DIMENSION_HORIZONTAL = 0; 477 static final int DIMENSION_VERTICAL = 1; 478 public DimensionBehaviour[] mListDimensionBehaviors = 479 {DimensionBehaviour.FIXED, DimensionBehaviour.FIXED}; 480 481 // Parent of this widget 482 public ConstraintWidget mParent = null; 483 484 // Dimensions of the widget 485 int mWidth = 0; 486 int mHeight = 0; 487 public float mDimensionRatio = 0; 488 protected int mDimensionRatioSide = UNKNOWN; 489 490 // Origin of the widget 491 protected int mX = 0; 492 protected int mY = 0; 493 int mRelX = 0; 494 int mRelY = 0; 495 496 // Root offset 497 protected int mOffsetX = 0; 498 protected int mOffsetY = 0; 499 500 // Baseline distance relative to the top of the widget 501 int mBaselineDistance = 0; 502 503 // Minimum sizes for the widget 504 protected int mMinWidth; 505 protected int mMinHeight; 506 507 // Percentages used for biasing one connection over another when dual connections 508 // of the same strength exist 509 public static float DEFAULT_BIAS = 0.5f; 510 float mHorizontalBiasPercent = DEFAULT_BIAS; 511 float mVerticalBiasPercent = DEFAULT_BIAS; 512 513 // The companion widget (typically, the real widget we represent) 514 private Object mCompanionWidget; 515 516 // This is used to possibly "skip" a position while inside a container. For example, 517 // a container like Table can use this to implement empty cells 518 // (the item positioned after the empty cell will have a skip value of 1) 519 private int mContainerItemSkip = 0; 520 521 // Contains the visibility status of the widget (VISIBLE, INVISIBLE, or GONE) 522 private int mVisibility = VISIBLE; 523 // Contains if this widget is animated. Currently only affects gone behaviour 524 private boolean mAnimated = false; 525 private String mDebugName = null; 526 private String mType = null; 527 528 int mDistToTop; 529 int mDistToLeft; 530 int mDistToRight; 531 int mDistToBottom; 532 boolean mLeftHasCentered; 533 boolean mRightHasCentered; 534 boolean mTopHasCentered; 535 boolean mBottomHasCentered; 536 boolean mHorizontalWrapVisited; 537 boolean mVerticalWrapVisited; 538 boolean mGroupsToSolver = false; 539 540 // Chain support 541 int mHorizontalChainStyle = CHAIN_SPREAD; 542 int mVerticalChainStyle = CHAIN_SPREAD; 543 boolean mHorizontalChainFixedPosition; 544 boolean mVerticalChainFixedPosition; 545 546 public float[] mWeight = {UNKNOWN, UNKNOWN}; 547 548 protected ConstraintWidget[] mListNextMatchConstraintsWidget = {null, null}; 549 protected ConstraintWidget[] mNextChainWidget = {null, null}; 550 551 ConstraintWidget mHorizontalNextWidget = null; 552 ConstraintWidget mVerticalNextWidget = null; 553 554 // TODO: see if we can make this simpler 555 556 // @TODO: add description reset()557 public void reset() { 558 mLeft.reset(); 559 mTop.reset(); 560 mRight.reset(); 561 mBottom.reset(); 562 mBaseline.reset(); 563 mCenterX.reset(); 564 mCenterY.reset(); 565 mCenter.reset(); 566 mParent = null; 567 mCircleConstraintAngle = Float.NaN; 568 mWidth = 0; 569 mHeight = 0; 570 mDimensionRatio = 0; 571 mDimensionRatioSide = UNKNOWN; 572 mX = 0; 573 mY = 0; 574 mOffsetX = 0; 575 mOffsetY = 0; 576 mBaselineDistance = 0; 577 mMinWidth = 0; 578 mMinHeight = 0; 579 mHorizontalBiasPercent = DEFAULT_BIAS; 580 mVerticalBiasPercent = DEFAULT_BIAS; 581 mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED; 582 mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED; 583 mCompanionWidget = null; 584 mContainerItemSkip = 0; 585 mVisibility = VISIBLE; 586 mType = null; 587 mHorizontalWrapVisited = false; 588 mVerticalWrapVisited = false; 589 mHorizontalChainStyle = CHAIN_SPREAD; 590 mVerticalChainStyle = CHAIN_SPREAD; 591 mHorizontalChainFixedPosition = false; 592 mVerticalChainFixedPosition = false; 593 mWeight[DIMENSION_HORIZONTAL] = UNKNOWN; 594 mWeight[DIMENSION_VERTICAL] = UNKNOWN; 595 mHorizontalResolution = UNKNOWN; 596 mVerticalResolution = UNKNOWN; 597 mMaxDimension[HORIZONTAL] = Integer.MAX_VALUE; 598 mMaxDimension[VERTICAL] = Integer.MAX_VALUE; 599 mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD; 600 mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD; 601 mMatchConstraintPercentWidth = 1; 602 mMatchConstraintPercentHeight = 1; 603 mMatchConstraintMaxWidth = Integer.MAX_VALUE; 604 mMatchConstraintMaxHeight = Integer.MAX_VALUE; 605 mMatchConstraintMinWidth = 0; 606 mMatchConstraintMinHeight = 0; 607 mResolvedHasRatio = false; 608 mResolvedDimensionRatioSide = UNKNOWN; 609 mResolvedDimensionRatio = 1f; 610 mGroupsToSolver = false; 611 isTerminalWidget[HORIZONTAL] = true; 612 isTerminalWidget[VERTICAL] = true; 613 mInVirtualLayout = false; 614 mIsInBarrier[HORIZONTAL] = false; 615 mIsInBarrier[VERTICAL] = false; 616 mMeasureRequested = true; 617 mResolvedMatchConstraintDefault[HORIZONTAL] = 0; 618 mResolvedMatchConstraintDefault[VERTICAL] = 0; 619 mWidthOverride = -1; 620 mHeightOverride = -1; 621 } 622 623 ///////////////////////////////////SERIALIZE/////////////////////////////////////////////// 624 serializeAnchor(StringBuilder ret, String side, ConstraintAnchor a)625 private void serializeAnchor(StringBuilder ret, String side, ConstraintAnchor a) { 626 if (a.mTarget == null) { 627 return; 628 } 629 ret.append(side); 630 ret.append(" : [ '"); 631 ret.append(a.mTarget); 632 ret.append("',"); 633 ret.append(a.mMargin); 634 ret.append(","); 635 ret.append(a.mGoneMargin); 636 ret.append(","); 637 ret.append(" ] ,\n"); 638 } 639 serializeCircle(StringBuilder ret, ConstraintAnchor a, float angle)640 private void serializeCircle(StringBuilder ret, ConstraintAnchor a, float angle) { 641 if (a.mTarget == null || Float.isNaN(angle)) { 642 return; 643 } 644 645 ret.append("circle : [ '"); 646 ret.append(a.mTarget); 647 ret.append("',"); 648 ret.append(a.mMargin); 649 ret.append(","); 650 ret.append(angle); 651 ret.append(","); 652 ret.append(" ] ,\n"); 653 } 654 serializeAttribute(StringBuilder ret, String type, float value, float def)655 private void serializeAttribute(StringBuilder ret, String type, float value, float def) { 656 if (value == def) { 657 return; 658 } 659 ret.append(type); 660 ret.append(" : "); 661 ret.append(value); 662 ret.append(",\n"); 663 } 664 serializeAttribute(StringBuilder ret, String type, int value, int def)665 private void serializeAttribute(StringBuilder ret, String type, int value, int def) { 666 if (value == def) { 667 return; 668 } 669 ret.append(type); 670 ret.append(" : "); 671 ret.append(value); 672 ret.append(",\n"); 673 } 674 serializeAttribute(StringBuilder ret, String type, String value, String def)675 private void serializeAttribute(StringBuilder ret, String type, String value, String def) { 676 if (def.equals(value)) { 677 return; 678 } 679 ret.append(type); 680 ret.append(" : "); 681 ret.append(value); 682 ret.append(",\n"); 683 } 684 serializeDimensionRatio(StringBuilder ret, String type, float value, int whichSide)685 private void serializeDimensionRatio(StringBuilder ret, 686 String type, 687 float value, 688 int whichSide) { 689 if (value == 0) { 690 return; 691 } 692 ret.append(type); 693 ret.append(" : ["); 694 ret.append(value); 695 ret.append(","); 696 ret.append(whichSide); 697 ret.append(""); 698 ret.append("],\n"); 699 } 700 serializeSize(StringBuilder ret, String type, int size, int min, int max, int override, int matchConstraintMin, int matchConstraintDefault, float matchConstraintPercent, float weight)701 private void serializeSize(StringBuilder ret, String type, int size, 702 int min, int max, int override, 703 int matchConstraintMin, int matchConstraintDefault, 704 float matchConstraintPercent, 705 float weight) { 706 ret.append(type); 707 ret.append(" : {\n"); 708 serializeAttribute(ret, "size", size, Integer.MIN_VALUE); 709 serializeAttribute(ret, "min", min, 0); 710 serializeAttribute(ret, "max", max, Integer.MAX_VALUE); 711 serializeAttribute(ret, "matchMin", matchConstraintMin, 0); 712 serializeAttribute(ret, "matchDef", matchConstraintDefault, MATCH_CONSTRAINT_SPREAD); 713 serializeAttribute(ret, "matchPercent", matchConstraintDefault, 1); 714 serializeAttribute(ret, "matchConstraintPercent", matchConstraintPercent, 1); 715 serializeAttribute(ret, "weight", weight, 1); 716 serializeAttribute(ret, "override", override, 1); 717 718 ret.append("},\n"); 719 } 720 721 /** 722 * Serialize the anchors for JSON5 output 723 * @param ret StringBuilder to be populated 724 * @return the same string builder to alow chaining 725 */ serialize(StringBuilder ret)726 public StringBuilder serialize(StringBuilder ret) { 727 ret.append("{\n"); 728 serializeAnchor(ret, "left", mLeft); 729 serializeAnchor(ret, "top", mTop); 730 serializeAnchor(ret, "right", mRight); 731 serializeAnchor(ret, "bottom", mBottom); 732 serializeAnchor(ret, "baseline", mBaseline); 733 serializeAnchor(ret, "centerX", mCenterX); 734 serializeAnchor(ret, "centerY", mCenterY); 735 serializeCircle(ret, mCenter, mCircleConstraintAngle); 736 737 serializeSize(ret, "width", 738 mWidth, 739 mMinWidth, 740 mMaxDimension[HORIZONTAL], 741 mWidthOverride, 742 mMatchConstraintMinWidth, 743 mMatchConstraintDefaultWidth, 744 mMatchConstraintPercentWidth, 745 mWeight[DIMENSION_HORIZONTAL] 746 ); 747 748 serializeSize(ret, "height", 749 mHeight, 750 mMinHeight, 751 mMaxDimension[VERTICAL], 752 mHeightOverride, 753 mMatchConstraintMinHeight, 754 mMatchConstraintDefaultHeight, 755 mMatchConstraintPercentHeight, 756 mWeight[DIMENSION_VERTICAL]); 757 758 serializeDimensionRatio(ret, "dimensionRatio", mDimensionRatio, mDimensionRatioSide); 759 serializeAttribute(ret, "horizontalBias", mHorizontalBiasPercent, DEFAULT_BIAS); 760 serializeAttribute(ret, "verticalBias", mVerticalBiasPercent, DEFAULT_BIAS); 761 ret.append("}\n"); 762 763 return ret; 764 } 765 ///////////////////////////////////END SERIALIZE/////////////////////////////////////////// 766 767 public int horizontalGroup = -1; 768 public int verticalGroup = -1; 769 770 // @TODO: add description oppositeDimensionDependsOn(int orientation)771 public boolean oppositeDimensionDependsOn(int orientation) { 772 int oppositeOrientation = (orientation == HORIZONTAL) ? VERTICAL : HORIZONTAL; 773 DimensionBehaviour dimensionBehaviour = mListDimensionBehaviors[orientation]; 774 DimensionBehaviour oppositeDimensionBehaviour = 775 mListDimensionBehaviors[oppositeOrientation]; 776 return dimensionBehaviour == MATCH_CONSTRAINT 777 && oppositeDimensionBehaviour == MATCH_CONSTRAINT; 778 //&& mDimensionRatio != 0; 779 } 780 781 // @TODO: add description oppositeDimensionsTied()782 public boolean oppositeDimensionsTied() { 783 return /* isInHorizontalChain() || isInVerticalChain() || */ 784 (mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT 785 && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT); 786 } 787 788 // @TODO: add description hasDimensionOverride()789 public boolean hasDimensionOverride() { 790 return mWidthOverride != -1 || mHeightOverride != -1; 791 } 792 793 /*-----------------------------------------------------------------------*/ 794 // Creation 795 /*-----------------------------------------------------------------------*/ 796 797 /** 798 * Default constructor 799 */ ConstraintWidget()800 public ConstraintWidget() { 801 addAnchors(); 802 } 803 ConstraintWidget(String debugName)804 public ConstraintWidget(String debugName) { 805 addAnchors(); 806 setDebugName(debugName); 807 } 808 809 /** 810 * Constructor 811 * 812 * @param x x position 813 * @param y y position 814 * @param width width of the layout 815 * @param height height of the layout 816 */ ConstraintWidget(int x, int y, int width, int height)817 public ConstraintWidget(int x, int y, int width, int height) { 818 mX = x; 819 mY = y; 820 mWidth = width; 821 mHeight = height; 822 addAnchors(); 823 } 824 ConstraintWidget(String debugName, int x, int y, int width, int height)825 public ConstraintWidget(String debugName, int x, int y, int width, int height) { 826 this(x, y, width, height); 827 setDebugName(debugName); 828 } 829 830 /** 831 * Constructor 832 * 833 * @param width width of the layout 834 * @param height height of the layout 835 */ ConstraintWidget(int width, int height)836 public ConstraintWidget(int width, int height) { 837 this(0, 0, width, height); 838 } 839 840 // @TODO: add description ensureWidgetRuns()841 public void ensureWidgetRuns() { 842 if (mHorizontalRun == null) { 843 mHorizontalRun = new HorizontalWidgetRun(this); 844 } 845 if (mVerticalRun == null) { 846 mVerticalRun = new VerticalWidgetRun(this); 847 } 848 } 849 ConstraintWidget(String debugName, int width, int height)850 public ConstraintWidget(String debugName, int width, int height) { 851 this(width, height); 852 setDebugName(debugName); 853 } 854 855 /** 856 * Reset the solver variables of the anchors 857 */ resetSolverVariables(Cache cache)858 public void resetSolverVariables(Cache cache) { 859 mLeft.resetSolverVariable(cache); 860 mTop.resetSolverVariable(cache); 861 mRight.resetSolverVariable(cache); 862 mBottom.resetSolverVariable(cache); 863 mBaseline.resetSolverVariable(cache); 864 mCenter.resetSolverVariable(cache); 865 mCenterX.resetSolverVariable(cache); 866 mCenterY.resetSolverVariable(cache); 867 } 868 869 /** 870 * Add all the anchors to the mAnchors array 871 */ addAnchors()872 private void addAnchors() { 873 mAnchors.add(mLeft); 874 mAnchors.add(mTop); 875 mAnchors.add(mRight); 876 mAnchors.add(mBottom); 877 mAnchors.add(mCenterX); 878 mAnchors.add(mCenterY); 879 mAnchors.add(mCenter); 880 mAnchors.add(mBaseline); 881 } 882 883 /** 884 * Returns true if the widget is the root widget 885 * 886 * @return true if root widget, false otherwise 887 */ isRoot()888 public boolean isRoot() { 889 return mParent == null; 890 } 891 892 /** 893 * Returns the parent of this widget if there is one 894 * 895 * @return parent 896 */ getParent()897 public ConstraintWidget getParent() { 898 return mParent; 899 } 900 901 /** 902 * Set the parent of this widget 903 * 904 * @param widget parent 905 */ setParent(ConstraintWidget widget)906 public void setParent(ConstraintWidget widget) { 907 mParent = widget; 908 } 909 910 /** 911 * Keep track of wrap_content for width 912 */ setWidthWrapContent(boolean widthWrapContent)913 public void setWidthWrapContent(boolean widthWrapContent) { 914 this.mIsWidthWrapContent = widthWrapContent; 915 } 916 917 /** 918 * Returns true if width is set to wrap_content 919 */ isWidthWrapContent()920 public boolean isWidthWrapContent() { 921 return mIsWidthWrapContent; 922 } 923 924 /** 925 * Keep track of wrap_content for height 926 */ setHeightWrapContent(boolean heightWrapContent)927 public void setHeightWrapContent(boolean heightWrapContent) { 928 this.mIsHeightWrapContent = heightWrapContent; 929 } 930 931 /** 932 * Returns true if height is set to wrap_content 933 */ isHeightWrapContent()934 public boolean isHeightWrapContent() { 935 return mIsHeightWrapContent; 936 } 937 938 /** 939 * Set a circular constraint 940 * 941 * @param target the target widget we will use as the center of the circle 942 * @param angle the angle (from 0 to 360) 943 * @param radius the radius used 944 */ connectCircularConstraint(ConstraintWidget target, float angle, int radius)945 public void connectCircularConstraint(ConstraintWidget target, float angle, int radius) { 946 immediateConnect(ConstraintAnchor.Type.CENTER, target, ConstraintAnchor.Type.CENTER, 947 radius, 0); 948 mCircleConstraintAngle = angle; 949 } 950 951 /** 952 * Returns the type string if set 953 * 954 * @return type (null if not set) 955 */ getType()956 public String getType() { 957 return mType; 958 } 959 960 /** 961 * Set the type of the widget (as a String) 962 * 963 * @param type type of the widget 964 */ setType(String type)965 public void setType(String type) { 966 mType = type; 967 } 968 969 /** 970 * Set the visibility for this widget 971 * 972 * @param visibility either VISIBLE, INVISIBLE, or GONE 973 */ setVisibility(int visibility)974 public void setVisibility(int visibility) { 975 mVisibility = visibility; 976 } 977 978 /** 979 * Returns the current visibility value for this widget 980 * 981 * @return the visibility (VISIBLE, INVISIBLE, or GONE) 982 */ getVisibility()983 public int getVisibility() { 984 return mVisibility; 985 } 986 987 /** 988 * Set if this widget is animated. Currently only affects gone behaviour 989 * 990 * @param animated if true the widget must be positioned correctly when not visible 991 */ setAnimated(boolean animated)992 public void setAnimated(boolean animated) { 993 mAnimated = animated; 994 } 995 996 /** 997 * Returns if this widget is animated. Currently only affects gone behaviour 998 * 999 * @return true if ConstraintWidget is used in Animation 1000 */ isAnimated()1001 public boolean isAnimated() { 1002 return mAnimated; 1003 } 1004 1005 /** 1006 * Returns the name of this widget (used for debug purposes) 1007 * 1008 * @return the debug name 1009 */ getDebugName()1010 public String getDebugName() { 1011 return mDebugName; 1012 } 1013 1014 /** 1015 * Set the debug name of this widget 1016 */ setDebugName(String name)1017 public void setDebugName(String name) { 1018 mDebugName = name; 1019 } 1020 1021 /** 1022 * Utility debug function. Sets the names of the anchors in the solver given 1023 * a widget's name. The given name is used as a prefix, resulting in anchors' names 1024 * of the form: 1025 * <p/> 1026 * <ul> 1027 * <li>{name}.left</li> 1028 * <li>{name}.top</li> 1029 * <li>{name}.right</li> 1030 * <li>{name}.bottom</li> 1031 * <li>{name}.baseline</li> 1032 * </ul> 1033 * 1034 * @param system solver used 1035 * @param name name of the widget 1036 */ setDebugSolverName(LinearSystem system, String name)1037 public void setDebugSolverName(LinearSystem system, String name) { 1038 mDebugName = name; 1039 SolverVariable left = system.createObjectVariable(mLeft); 1040 SolverVariable top = system.createObjectVariable(mTop); 1041 SolverVariable right = system.createObjectVariable(mRight); 1042 SolverVariable bottom = system.createObjectVariable(mBottom); 1043 left.setName(name + ".left"); 1044 top.setName(name + ".top"); 1045 right.setName(name + ".right"); 1046 bottom.setName(name + ".bottom"); 1047 SolverVariable baseline = system.createObjectVariable(mBaseline); 1048 baseline.setName(name + ".baseline"); 1049 } 1050 1051 /** 1052 * Create all the system variables for this widget 1053 * 1054 * 1055 */ createObjectVariables(LinearSystem system)1056 public void createObjectVariables(LinearSystem system) { 1057 system.createObjectVariable(mLeft); 1058 system.createObjectVariable(mTop); 1059 system.createObjectVariable(mRight); 1060 system.createObjectVariable(mBottom); 1061 if (mBaselineDistance > 0) { 1062 system.createObjectVariable(mBaseline); 1063 } 1064 } 1065 1066 /** 1067 * Returns a string representation of the ConstraintWidget 1068 * 1069 * @return string representation of the widget 1070 */ 1071 @Override toString()1072 public String toString() { 1073 return (mType != null ? "type: " + mType + " " : "") 1074 + (mDebugName != null ? "id: " + mDebugName + " " : "") 1075 + "(" + mX + ", " + mY + ") - (" + mWidth + " x " + mHeight + ")"; 1076 } 1077 1078 /*-----------------------------------------------------------------------*/ 1079 // Position 1080 /*-----------------------------------------------------------------------*/ 1081 // The widget position is expressed in two ways: 1082 // - relative to its direct parent container (getX(), getY()) 1083 // - relative to the root container (getDrawX(), getDrawY()) 1084 // Additionally, getDrawX()/getDrawY() are used when animating the 1085 // widget position on screen 1086 /*-----------------------------------------------------------------------*/ 1087 1088 /** 1089 * Return the x position of the widget, relative to its container 1090 * 1091 * @return x position 1092 */ getX()1093 public int getX() { 1094 if (mParent != null && mParent instanceof ConstraintWidgetContainer) { 1095 return ((ConstraintWidgetContainer) mParent).mPaddingLeft + mX; 1096 } 1097 return mX; 1098 } 1099 1100 /** 1101 * Return the y position of the widget, relative to its container 1102 * 1103 * @return y position 1104 */ getY()1105 public int getY() { 1106 if (mParent != null && mParent instanceof ConstraintWidgetContainer) { 1107 return ((ConstraintWidgetContainer) mParent).mPaddingTop + mY; 1108 } 1109 return mY; 1110 } 1111 1112 /** 1113 * Return the width of the widget 1114 * 1115 * @return width width 1116 */ getWidth()1117 public int getWidth() { 1118 if (mVisibility == ConstraintWidget.GONE) { 1119 return 0; 1120 } 1121 return mWidth; 1122 } 1123 1124 // @TODO: add description getOptimizerWrapWidth()1125 public int getOptimizerWrapWidth() { 1126 int w = mWidth; 1127 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT) { 1128 if (mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) { 1129 w = Math.max(mMatchConstraintMinWidth, w); 1130 } else if (mMatchConstraintMinWidth > 0) { 1131 w = mMatchConstraintMinWidth; 1132 mWidth = w; 1133 } else { 1134 w = 0; 1135 } 1136 if (mMatchConstraintMaxWidth > 0 && mMatchConstraintMaxWidth < w) { 1137 w = mMatchConstraintMaxWidth; 1138 } 1139 } 1140 return w; 1141 } 1142 1143 // @TODO: add description getOptimizerWrapHeight()1144 public int getOptimizerWrapHeight() { 1145 int h = mHeight; 1146 if (mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT) { 1147 if (mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) { 1148 h = Math.max(mMatchConstraintMinHeight, h); 1149 } else if (mMatchConstraintMinHeight > 0) { 1150 h = mMatchConstraintMinHeight; 1151 mHeight = h; 1152 } else { 1153 h = 0; 1154 } 1155 if (mMatchConstraintMaxHeight > 0 && mMatchConstraintMaxHeight < h) { 1156 h = mMatchConstraintMaxHeight; 1157 } 1158 } 1159 return h; 1160 } 1161 1162 /** 1163 * Return the height of the widget 1164 * 1165 * @return height height 1166 */ getHeight()1167 public int getHeight() { 1168 if (mVisibility == ConstraintWidget.GONE) { 1169 return 0; 1170 } 1171 return mHeight; 1172 } 1173 1174 /** 1175 * Get a dimension of the widget in a particular orientation. 1176 * 1177 * @return The dimension of the specified orientation. 1178 */ getLength(int orientation)1179 public int getLength(int orientation) { 1180 if (orientation == HORIZONTAL) { 1181 return getWidth(); 1182 } else if (orientation == VERTICAL) { 1183 return getHeight(); 1184 } else { 1185 return 0; 1186 } 1187 } 1188 1189 /** 1190 * Return the x position of the widget, relative to the root 1191 * (without animation) 1192 * 1193 * @return x position 1194 */ getRootX()1195 protected int getRootX() { 1196 return mX + mOffsetX; 1197 } 1198 1199 /** 1200 * Return the y position of the widget, relative to the root 1201 * (without animation) 1202 */ getRootY()1203 protected int getRootY() { 1204 return mY + mOffsetY; 1205 } 1206 1207 /** 1208 * Return the minimum width of the widget 1209 * 1210 * @return minimum width 1211 */ getMinWidth()1212 public int getMinWidth() { 1213 return mMinWidth; 1214 } 1215 1216 /** 1217 * Return the minimum height of the widget 1218 * 1219 * @return minimum height 1220 */ getMinHeight()1221 public int getMinHeight() { 1222 return mMinHeight; 1223 } 1224 1225 /** 1226 * Return the left position of the widget (similar to {@link #getX()}) 1227 * 1228 * @return left position of the widget 1229 */ getLeft()1230 public int getLeft() { 1231 return getX(); 1232 } 1233 1234 /** 1235 * Return the top position of the widget (similar to {@link #getY()}) 1236 * 1237 * @return top position of the widget 1238 */ getTop()1239 public int getTop() { 1240 return getY(); 1241 } 1242 1243 /** 1244 * Return the right position of the widget 1245 * 1246 * @return right position of the widget 1247 */ getRight()1248 public int getRight() { 1249 return getX() + mWidth; 1250 } 1251 1252 /** 1253 * Return the bottom position of the widget 1254 * 1255 * @return bottom position of the widget 1256 */ getBottom()1257 public int getBottom() { 1258 return getY() + mHeight; 1259 } 1260 1261 /** 1262 * Returns all the horizontal margin of the widget. 1263 */ getHorizontalMargin()1264 public int getHorizontalMargin() { 1265 int margin = 0; 1266 if (mLeft != null) { 1267 margin += mLeft.mMargin; 1268 } 1269 if (mRight != null) { 1270 margin += mRight.mMargin; 1271 } 1272 return margin; 1273 } 1274 1275 /** 1276 * Returns all the vertical margin of the widget 1277 */ getVerticalMargin()1278 public int getVerticalMargin() { 1279 int margin = 0; 1280 if (mLeft != null) { 1281 margin += mTop.mMargin; 1282 } 1283 if (mRight != null) { 1284 margin += mBottom.mMargin; 1285 } 1286 return margin; 1287 } 1288 1289 /** 1290 * Return the horizontal percentage bias that is used when two opposite connections 1291 * exist of the same strength. 1292 * 1293 * @return horizontal percentage bias 1294 */ getHorizontalBiasPercent()1295 public float getHorizontalBiasPercent() { 1296 return mHorizontalBiasPercent; 1297 } 1298 1299 /** 1300 * Return the vertical percentage bias that is used when two opposite connections 1301 * exist of the same strength. 1302 * 1303 * @return vertical percentage bias 1304 */ getVerticalBiasPercent()1305 public float getVerticalBiasPercent() { 1306 return mVerticalBiasPercent; 1307 } 1308 1309 /** 1310 * Return the percentage bias that is used when two opposite connections exist of the same 1311 * strength in a particular orientation. 1312 * 1313 * @param orientation Orientation {@link #HORIZONTAL}/{@link #VERTICAL}. 1314 * @return Respective percentage bias. 1315 */ getBiasPercent(int orientation)1316 public float getBiasPercent(int orientation) { 1317 if (orientation == HORIZONTAL) { 1318 return mHorizontalBiasPercent; 1319 } else if (orientation == VERTICAL) { 1320 return mVerticalBiasPercent; 1321 } else { 1322 return UNKNOWN; 1323 } 1324 } 1325 1326 /** 1327 * Return true if this widget has a baseline 1328 * 1329 * @return true if the widget has a baseline, false otherwise 1330 */ hasBaseline()1331 public boolean hasBaseline() { 1332 return mHasBaseline; 1333 } 1334 1335 /** 1336 * Return the baseline distance relative to the top of the widget 1337 * 1338 * @return baseline 1339 */ getBaselineDistance()1340 public int getBaselineDistance() { 1341 return mBaselineDistance; 1342 } 1343 1344 /** 1345 * Return the companion widget. Typically, this would be the real 1346 * widget we represent with this instance of ConstraintWidget. 1347 * 1348 * @return the companion widget, if set. 1349 */ getCompanionWidget()1350 public Object getCompanionWidget() { 1351 return mCompanionWidget; 1352 } 1353 1354 /** 1355 * Return the array of anchors of this widget 1356 * 1357 * @return array of anchors 1358 */ getAnchors()1359 public ArrayList<ConstraintAnchor> getAnchors() { 1360 return mAnchors; 1361 } 1362 1363 /** 1364 * Set the x position of the widget, relative to its container 1365 * 1366 * @param x x position 1367 */ setX(int x)1368 public void setX(int x) { 1369 mX = x; 1370 } 1371 1372 /** 1373 * Set the y position of the widget, relative to its container 1374 * 1375 * @param y y position 1376 */ setY(int y)1377 public void setY(int y) { 1378 mY = y; 1379 } 1380 1381 /** 1382 * Set both the origin in (x, y) of the widget, relative to its container 1383 * 1384 * @param x x position 1385 * @param y y position 1386 */ setOrigin(int x, int y)1387 public void setOrigin(int x, int y) { 1388 mX = x; 1389 mY = y; 1390 } 1391 1392 /** 1393 * Set the offset of this widget relative to the root widget 1394 * 1395 * @param x horizontal offset 1396 * @param y vertical offset 1397 */ setOffset(int x, int y)1398 public void setOffset(int x, int y) { 1399 mOffsetX = x; 1400 mOffsetY = y; 1401 } 1402 1403 /** 1404 * Set the margin to be used when connected to a widget with a visibility of GONE 1405 * 1406 * @param type the anchor to set the margin on 1407 * @param goneMargin the margin value to use 1408 */ setGoneMargin(ConstraintAnchor.Type type, int goneMargin)1409 public void setGoneMargin(ConstraintAnchor.Type type, int goneMargin) { 1410 switch (type) { 1411 case LEFT: { 1412 mLeft.mGoneMargin = goneMargin; 1413 } 1414 break; 1415 case TOP: { 1416 mTop.mGoneMargin = goneMargin; 1417 } 1418 break; 1419 case RIGHT: { 1420 mRight.mGoneMargin = goneMargin; 1421 } 1422 break; 1423 case BOTTOM: { 1424 mBottom.mGoneMargin = goneMargin; 1425 } 1426 break; 1427 case BASELINE: { 1428 mBaseline.mGoneMargin = goneMargin; 1429 } 1430 break; 1431 case CENTER: 1432 case CENTER_X: 1433 case CENTER_Y: 1434 case NONE: 1435 break; 1436 } 1437 } 1438 1439 /** 1440 * Set the width of the widget 1441 * 1442 * @param w width 1443 */ setWidth(int w)1444 public void setWidth(int w) { 1445 mWidth = w; 1446 if (mWidth < mMinWidth) { 1447 mWidth = mMinWidth; 1448 } 1449 } 1450 1451 /** 1452 * Set the height of the widget 1453 * 1454 * @param h height 1455 */ setHeight(int h)1456 public void setHeight(int h) { 1457 mHeight = h; 1458 if (mHeight < mMinHeight) { 1459 mHeight = mMinHeight; 1460 } 1461 } 1462 1463 /** 1464 * Set the dimension of a widget in a particular orientation. 1465 * 1466 * @param length Size of the dimension. 1467 * @param orientation HORIZONTAL or VERTICAL 1468 */ setLength(int length, int orientation)1469 public void setLength(int length, int orientation) { 1470 if (orientation == HORIZONTAL) { 1471 setWidth(length); 1472 } else if (orientation == VERTICAL) { 1473 setHeight(length); 1474 } 1475 } 1476 1477 /** 1478 * Set the horizontal style when MATCH_CONSTRAINT is set 1479 * 1480 * @param horizontalMatchStyle MATCH_CONSTRAINT_SPREAD or MATCH_CONSTRAINT_WRAP 1481 * @param min minimum value 1482 * @param max maximum value 1483 * @param percent Percent width 1484 */ setHorizontalMatchStyle(int horizontalMatchStyle, int min, int max, float percent)1485 public void setHorizontalMatchStyle(int horizontalMatchStyle, int min, int max, float percent) { 1486 mMatchConstraintDefaultWidth = horizontalMatchStyle; 1487 mMatchConstraintMinWidth = min; 1488 mMatchConstraintMaxWidth = (max == Integer.MAX_VALUE) ? 0 : max; 1489 mMatchConstraintPercentWidth = percent; 1490 if (percent > 0 && percent < 1 && mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) { 1491 mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_PERCENT; 1492 } 1493 } 1494 1495 /** 1496 * Set the vertical style when MATCH_CONSTRAINT is set 1497 * 1498 * @param verticalMatchStyle MATCH_CONSTRAINT_SPREAD or MATCH_CONSTRAINT_WRAP 1499 * @param min minimum value 1500 * @param max maximum value 1501 * @param percent Percent height 1502 */ setVerticalMatchStyle(int verticalMatchStyle, int min, int max, float percent)1503 public void setVerticalMatchStyle(int verticalMatchStyle, int min, int max, float percent) { 1504 mMatchConstraintDefaultHeight = verticalMatchStyle; 1505 mMatchConstraintMinHeight = min; 1506 mMatchConstraintMaxHeight = (max == Integer.MAX_VALUE) ? 0 : max; 1507 mMatchConstraintPercentHeight = percent; 1508 if (percent > 0 && percent < 1 1509 && mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) { 1510 mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_PERCENT; 1511 } 1512 } 1513 1514 /** 1515 * Set the ratio of the widget 1516 * 1517 * @param ratio given string of format [H|V],[float|x:y] or [float|x:y] 1518 */ setDimensionRatio(String ratio)1519 public void setDimensionRatio(String ratio) { 1520 if (ratio == null || ratio.length() == 0) { 1521 mDimensionRatio = 0; 1522 return; 1523 } 1524 int dimensionRatioSide = UNKNOWN; 1525 float dimensionRatio = 0; 1526 int len = ratio.length(); 1527 int commaIndex = ratio.indexOf(','); 1528 if (commaIndex > 0 && commaIndex < len - 1) { 1529 String dimension = ratio.substring(0, commaIndex); 1530 if (dimension.equalsIgnoreCase("W")) { 1531 dimensionRatioSide = HORIZONTAL; 1532 } else if (dimension.equalsIgnoreCase("H")) { 1533 dimensionRatioSide = VERTICAL; 1534 } 1535 commaIndex++; 1536 } else { 1537 commaIndex = 0; 1538 } 1539 int colonIndex = ratio.indexOf(':'); 1540 1541 if (colonIndex >= 0 && colonIndex < len - 1) { 1542 String nominator = ratio.substring(commaIndex, colonIndex); 1543 String denominator = ratio.substring(colonIndex + 1); 1544 if (nominator.length() > 0 && denominator.length() > 0) { 1545 try { 1546 float nominatorValue = Float.parseFloat(nominator); 1547 float denominatorValue = Float.parseFloat(denominator); 1548 if (nominatorValue > 0 && denominatorValue > 0) { 1549 if (dimensionRatioSide == VERTICAL) { 1550 dimensionRatio = Math.abs(denominatorValue / nominatorValue); 1551 } else { 1552 dimensionRatio = Math.abs(nominatorValue / denominatorValue); 1553 } 1554 } 1555 } catch (NumberFormatException e) { 1556 // Ignore 1557 } 1558 } 1559 } else { 1560 String r = ratio.substring(commaIndex); 1561 if (r.length() > 0) { 1562 try { 1563 dimensionRatio = Float.parseFloat(r); 1564 } catch (NumberFormatException e) { 1565 // Ignore 1566 } 1567 } 1568 } 1569 1570 if (dimensionRatio > 0) { 1571 mDimensionRatio = dimensionRatio; 1572 mDimensionRatioSide = dimensionRatioSide; 1573 } 1574 } 1575 1576 /** 1577 * Set the ratio of the widget 1578 * The ratio will be applied if at least one of the dimension 1579 * (width or height) is set to a behaviour 1580 * of DimensionBehaviour.MATCH_CONSTRAINT 1581 * -- the dimension's value will be set to the other dimension * ratio. 1582 * 1583 * @param ratio A float value that describes W/H or H/W depending 1584 * on the provided dimensionRatioSide 1585 * @param dimensionRatioSide The side the ratio should be calculated on, 1586 * HORIZONTAL, VERTICAL, or UNKNOWN 1587 */ setDimensionRatio(float ratio, int dimensionRatioSide)1588 public void setDimensionRatio(float ratio, int dimensionRatioSide) { 1589 mDimensionRatio = ratio; 1590 mDimensionRatioSide = dimensionRatioSide; 1591 } 1592 1593 /** 1594 * Return the current ratio of this widget 1595 * 1596 * @return the dimension ratio (HORIZONTAL, VERTICAL, or UNKNOWN) 1597 */ getDimensionRatio()1598 public float getDimensionRatio() { 1599 return mDimensionRatio; 1600 } 1601 1602 /** 1603 * Return the current side on which ratio will be applied 1604 * 1605 * @return HORIZONTAL, VERTICAL, or UNKNOWN 1606 */ getDimensionRatioSide()1607 public int getDimensionRatioSide() { 1608 return mDimensionRatioSide; 1609 } 1610 1611 /** 1612 * Set the horizontal bias percent to apply when we have two opposite constraints of 1613 * equal strength 1614 * 1615 * @param horizontalBiasPercent the percentage used 1616 */ setHorizontalBiasPercent(float horizontalBiasPercent)1617 public void setHorizontalBiasPercent(float horizontalBiasPercent) { 1618 mHorizontalBiasPercent = horizontalBiasPercent; 1619 } 1620 1621 /** 1622 * Set the vertical bias percent to apply when we have two opposite constraints of 1623 * equal strength 1624 * 1625 * @param verticalBiasPercent the percentage used 1626 */ setVerticalBiasPercent(float verticalBiasPercent)1627 public void setVerticalBiasPercent(float verticalBiasPercent) { 1628 mVerticalBiasPercent = verticalBiasPercent; 1629 } 1630 1631 /** 1632 * Set the minimum width of the widget 1633 * 1634 * @param w minimum width 1635 */ setMinWidth(int w)1636 public void setMinWidth(int w) { 1637 if (w < 0) { 1638 mMinWidth = 0; 1639 } else { 1640 mMinWidth = w; 1641 } 1642 } 1643 1644 /** 1645 * Set the minimum height of the widget 1646 * 1647 * @param h minimum height 1648 */ setMinHeight(int h)1649 public void setMinHeight(int h) { 1650 if (h < 0) { 1651 mMinHeight = 0; 1652 } else { 1653 mMinHeight = h; 1654 } 1655 } 1656 1657 /** 1658 * Set both width and height of the widget 1659 * 1660 * @param w width 1661 * @param h height 1662 */ setDimension(int w, int h)1663 public void setDimension(int w, int h) { 1664 mWidth = w; 1665 if (mWidth < mMinWidth) { 1666 mWidth = mMinWidth; 1667 } 1668 mHeight = h; 1669 if (mHeight < mMinHeight) { 1670 mHeight = mMinHeight; 1671 } 1672 } 1673 1674 /** 1675 * Set the position+dimension of the widget given left/top/right/bottom 1676 * 1677 * @param left left side position of the widget 1678 * @param top top side position of the widget 1679 * @param right right side position of the widget 1680 * @param bottom bottom side position of the widget 1681 */ setFrame(int left, int top, int right, int bottom)1682 public void setFrame(int left, int top, int right, int bottom) { 1683 int w = right - left; 1684 int h = bottom - top; 1685 1686 mX = left; 1687 mY = top; 1688 1689 if (mVisibility == ConstraintWidget.GONE) { 1690 mWidth = 0; 1691 mHeight = 0; 1692 return; 1693 } 1694 1695 // correct dimensional instability caused by rounding errors 1696 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] 1697 == DimensionBehaviour.FIXED && w < mWidth) { 1698 w = mWidth; 1699 } 1700 if (mListDimensionBehaviors[DIMENSION_VERTICAL] 1701 == DimensionBehaviour.FIXED && h < mHeight) { 1702 h = mHeight; 1703 } 1704 1705 mWidth = w; 1706 mHeight = h; 1707 1708 if (mHeight < mMinHeight) { 1709 mHeight = mMinHeight; 1710 } 1711 if (mWidth < mMinWidth) { 1712 mWidth = mMinWidth; 1713 } 1714 if (mMatchConstraintMaxWidth > 0 1715 && mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT) { 1716 mWidth = Math.min(mWidth, mMatchConstraintMaxWidth); 1717 } 1718 if (mMatchConstraintMaxHeight > 0 1719 && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT) { 1720 mHeight = Math.min(mHeight, mMatchConstraintMaxHeight); 1721 } 1722 if (w != mWidth) { 1723 mWidthOverride = mWidth; 1724 } 1725 if (h != mHeight) { 1726 mHeightOverride = mHeight; 1727 } 1728 1729 if (LinearSystem.FULL_DEBUG) { 1730 System.out.println("update from solver " + mDebugName 1731 + " " + mX + ":" + mY + " - " + mWidth + " x " + mHeight); 1732 } 1733 } 1734 1735 /** 1736 * Set the position+dimension of the widget based on starting/ending positions on one dimension. 1737 * 1738 * @param start Left/Top side position of the widget. 1739 * @param end Right/Bottom side position of the widget. 1740 * @param orientation Orientation being set (HORIZONTAL/VERTICAL). 1741 */ setFrame(int start, int end, int orientation)1742 public void setFrame(int start, int end, int orientation) { 1743 if (orientation == HORIZONTAL) { 1744 setHorizontalDimension(start, end); 1745 } else if (orientation == VERTICAL) { 1746 setVerticalDimension(start, end); 1747 } 1748 } 1749 1750 /** 1751 * Set the positions for the horizontal dimension only 1752 * 1753 * @param left left side position of the widget 1754 * @param right right side position of the widget 1755 */ setHorizontalDimension(int left, int right)1756 public void setHorizontalDimension(int left, int right) { 1757 mX = left; 1758 mWidth = right - left; 1759 if (mWidth < mMinWidth) { 1760 mWidth = mMinWidth; 1761 } 1762 } 1763 1764 /** 1765 * Set the positions for the vertical dimension only 1766 * 1767 * @param top top side position of the widget 1768 * @param bottom bottom side position of the widget 1769 */ setVerticalDimension(int top, int bottom)1770 public void setVerticalDimension(int top, int bottom) { 1771 mY = top; 1772 mHeight = bottom - top; 1773 if (mHeight < mMinHeight) { 1774 mHeight = mMinHeight; 1775 } 1776 } 1777 1778 /** 1779 * Get the left/top position of the widget relative to 1780 * the outer side of the container (right/bottom). 1781 * 1782 * @param orientation Orientation by which to find the relative positioning of the widget. 1783 * @return The relative position of the widget. 1784 */ getRelativePositioning(int orientation)1785 int getRelativePositioning(int orientation) { 1786 if (orientation == HORIZONTAL) { 1787 return mRelX; 1788 } else if (orientation == VERTICAL) { 1789 return mRelY; 1790 } else { 1791 return 0; 1792 } 1793 } 1794 1795 /** 1796 * Set the left/top position of the widget relative to 1797 * the outer side of the container (right/bottom). 1798 * 1799 * @param offset Offset of the relative position. 1800 * @param orientation Orientation of the offset being set. 1801 */ setRelativePositioning(int offset, int orientation)1802 void setRelativePositioning(int offset, int orientation) { 1803 if (orientation == HORIZONTAL) { 1804 mRelX = offset; 1805 } else if (orientation == VERTICAL) { 1806 mRelY = offset; 1807 } 1808 } 1809 1810 /** 1811 * Set the baseline distance relative to the top of the widget 1812 * 1813 * @param baseline the distance of the baseline relative to the widget's top 1814 */ setBaselineDistance(int baseline)1815 public void setBaselineDistance(int baseline) { 1816 mBaselineDistance = baseline; 1817 mHasBaseline = baseline > 0; 1818 } 1819 1820 /** 1821 * Set the companion widget. Typically, this would be the real widget we 1822 * represent with this instance of ConstraintWidget. 1823 */ setCompanionWidget(Object companion)1824 public void setCompanionWidget(Object companion) { 1825 mCompanionWidget = companion; 1826 } 1827 1828 /** 1829 * Set the skip value for this widget. This can be used when a widget is in a container, 1830 * so that container can position the widget as if it was positioned further in the list 1831 * of widgets. For example, with Table, this is used to skip empty cells 1832 * (the widget after an empty cell will have a skip value of one) 1833 */ setContainerItemSkip(int skip)1834 public void setContainerItemSkip(int skip) { 1835 if (skip >= 0) { 1836 mContainerItemSkip = skip; 1837 } else { 1838 mContainerItemSkip = 0; 1839 } 1840 } 1841 1842 /** 1843 * Accessor for the skip value 1844 * 1845 * @return skip value 1846 */ getContainerItemSkip()1847 public int getContainerItemSkip() { 1848 return mContainerItemSkip; 1849 } 1850 1851 /** 1852 * Set the horizontal weight (only used in chains) 1853 * 1854 * @param horizontalWeight Floating point value weight 1855 */ setHorizontalWeight(float horizontalWeight)1856 public void setHorizontalWeight(float horizontalWeight) { 1857 mWeight[DIMENSION_HORIZONTAL] = horizontalWeight; 1858 } 1859 1860 /** 1861 * Set the vertical weight (only used in chains) 1862 * 1863 * @param verticalWeight Floating point value weight 1864 */ setVerticalWeight(float verticalWeight)1865 public void setVerticalWeight(float verticalWeight) { 1866 mWeight[DIMENSION_VERTICAL] = verticalWeight; 1867 } 1868 1869 /** 1870 * Set the chain starting from this widget to be packed. 1871 * The horizontal bias will control how elements of the chain are positioned. 1872 * 1873 * @param horizontalChainStyle (CHAIN_SPREAD, CHAIN_SPREAD_INSIDE, CHAIN_PACKED) 1874 */ setHorizontalChainStyle(int horizontalChainStyle)1875 public void setHorizontalChainStyle(int horizontalChainStyle) { 1876 mHorizontalChainStyle = horizontalChainStyle; 1877 } 1878 1879 /** 1880 * get the chain starting from this widget to be packed. 1881 * The horizontal bias will control how elements of the chain are positioned. 1882 * 1883 * @return Horizontal Chain Style 1884 */ getHorizontalChainStyle()1885 public int getHorizontalChainStyle() { 1886 return mHorizontalChainStyle; 1887 } 1888 1889 /** 1890 * Set the chain starting from this widget to be packed. 1891 * The vertical bias will control how elements of the chain are positioned. 1892 * 1893 * @param verticalChainStyle (CHAIN_SPREAD, CHAIN_SPREAD_INSIDE, CHAIN_PACKED) 1894 */ setVerticalChainStyle(int verticalChainStyle)1895 public void setVerticalChainStyle(int verticalChainStyle) { 1896 mVerticalChainStyle = verticalChainStyle; 1897 } 1898 1899 /** 1900 * Set the chain starting from this widget to be packed. 1901 * The vertical bias will control how elements of the chain are positioned. 1902 */ getVerticalChainStyle()1903 public int getVerticalChainStyle() { 1904 return mVerticalChainStyle; 1905 } 1906 1907 /** 1908 * Returns true if this widget should be used in a barrier 1909 */ allowedInBarrier()1910 public boolean allowedInBarrier() { 1911 return mVisibility != GONE; 1912 } 1913 1914 /*-----------------------------------------------------------------------*/ 1915 // Connections 1916 /*-----------------------------------------------------------------------*/ 1917 1918 /** 1919 * Immediate connection to an anchor without any checks. 1920 * 1921 * @param startType The type of anchor on this widget 1922 * @param target The target widget 1923 * @param endType The type of anchor on the target widget 1924 * @param margin How much margin we want to keep as 1925 * a minimum distance between the two anchors 1926 * @param goneMargin How much margin we want to keep if the target is set to {@code View.GONE} 1927 */ immediateConnect(ConstraintAnchor.Type startType, ConstraintWidget target, ConstraintAnchor.Type endType, int margin, int goneMargin)1928 public void immediateConnect(ConstraintAnchor.Type startType, ConstraintWidget target, 1929 ConstraintAnchor.Type endType, int margin, int goneMargin) { 1930 ConstraintAnchor startAnchor = getAnchor(startType); 1931 ConstraintAnchor endAnchor = target.getAnchor(endType); 1932 startAnchor.connect(endAnchor, margin, goneMargin, true); 1933 } 1934 1935 /** 1936 * Connect the given anchors together (the from anchor should be owned by this widget) 1937 * 1938 * @param from the anchor we are connecting from (of this widget) 1939 * @param to the anchor we are connecting to 1940 * @param margin how much margin we want to have 1941 */ connect(ConstraintAnchor from, ConstraintAnchor to, int margin)1942 public void connect(ConstraintAnchor from, ConstraintAnchor to, int margin) { 1943 if (from.getOwner() == this) { 1944 connect(from.getType(), to.getOwner(), to.getType(), margin); 1945 } 1946 } 1947 1948 /** 1949 * Connect a given anchor of this widget to another anchor of a target widget 1950 * 1951 * @param constraintFrom which anchor of this widget to connect from 1952 * @param target the target widget 1953 * @param constraintTo the target anchor on the target widget 1954 */ connect(ConstraintAnchor.Type constraintFrom, ConstraintWidget target, ConstraintAnchor.Type constraintTo)1955 public void connect(ConstraintAnchor.Type constraintFrom, 1956 ConstraintWidget target, 1957 ConstraintAnchor.Type constraintTo) { 1958 if (DEBUG) { 1959 System.out.println(this.getDebugName() + " connect " 1960 + constraintFrom + " to " + target + " " + constraintTo); 1961 } 1962 connect(constraintFrom, target, constraintTo, 0); 1963 } 1964 1965 /** 1966 * Connect a given anchor of this widget to another anchor of a target widget 1967 * 1968 * @param constraintFrom which anchor of this widget to connect from 1969 * @param target the target widget 1970 * @param constraintTo the target anchor on the target widget 1971 * @param margin how much margin we want to keep as 1972 * a minimum distance between the two anchors 1973 */ connect(ConstraintAnchor.Type constraintFrom, ConstraintWidget target, ConstraintAnchor.Type constraintTo, int margin)1974 public void connect(ConstraintAnchor.Type constraintFrom, 1975 ConstraintWidget target, 1976 ConstraintAnchor.Type constraintTo, int margin) { 1977 if (constraintFrom == ConstraintAnchor.Type.CENTER) { 1978 // If we have center, we connect instead to the corresponding 1979 // left/right or top/bottom pairs 1980 if (constraintTo == ConstraintAnchor.Type.CENTER) { 1981 ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); 1982 ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); 1983 ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); 1984 ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); 1985 boolean centerX = false; 1986 boolean centerY = false; 1987 if ((left != null && left.isConnected()) 1988 || (right != null && right.isConnected())) { 1989 // don't apply center here 1990 } else { 1991 connect(ConstraintAnchor.Type.LEFT, target, 1992 ConstraintAnchor.Type.LEFT, 0); 1993 connect(ConstraintAnchor.Type.RIGHT, target, 1994 ConstraintAnchor.Type.RIGHT, 0); 1995 centerX = true; 1996 } 1997 if ((top != null && top.isConnected()) 1998 || (bottom != null && bottom.isConnected())) { 1999 // don't apply center here 2000 } else { 2001 connect(ConstraintAnchor.Type.TOP, target, 2002 ConstraintAnchor.Type.TOP, 0); 2003 connect(ConstraintAnchor.Type.BOTTOM, target, 2004 ConstraintAnchor.Type.BOTTOM, 0); 2005 centerY = true; 2006 } 2007 if (centerX && centerY) { 2008 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); 2009 center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER), 0); 2010 } else if (centerX) { 2011 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER_X); 2012 center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER_X), 0); 2013 } else if (centerY) { 2014 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER_Y); 2015 center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER_Y), 0); 2016 } 2017 } else if ((constraintTo == ConstraintAnchor.Type.LEFT) 2018 || (constraintTo == ConstraintAnchor.Type.RIGHT)) { 2019 connect(ConstraintAnchor.Type.LEFT, target, 2020 constraintTo, 0); 2021 connect(ConstraintAnchor.Type.RIGHT, target, 2022 constraintTo, 0); 2023 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); 2024 center.connect(target.getAnchor(constraintTo), 0); 2025 } else if ((constraintTo == ConstraintAnchor.Type.TOP) 2026 || (constraintTo == ConstraintAnchor.Type.BOTTOM)) { 2027 connect(ConstraintAnchor.Type.TOP, target, 2028 constraintTo, 0); 2029 connect(ConstraintAnchor.Type.BOTTOM, target, 2030 constraintTo, 0); 2031 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); 2032 center.connect(target.getAnchor(constraintTo), 0); 2033 } 2034 } else if (constraintFrom == ConstraintAnchor.Type.CENTER_X 2035 && (constraintTo == ConstraintAnchor.Type.LEFT 2036 || constraintTo == ConstraintAnchor.Type.RIGHT)) { 2037 ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); 2038 ConstraintAnchor targetAnchor = target.getAnchor(constraintTo); 2039 ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); 2040 left.connect(targetAnchor, 0); 2041 right.connect(targetAnchor, 0); 2042 ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); 2043 centerX.connect(targetAnchor, 0); 2044 } else if (constraintFrom == ConstraintAnchor.Type.CENTER_Y 2045 && (constraintTo == ConstraintAnchor.Type.TOP 2046 || constraintTo == ConstraintAnchor.Type.BOTTOM)) { 2047 ConstraintAnchor targetAnchor = target.getAnchor(constraintTo); 2048 ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); 2049 top.connect(targetAnchor, 0); 2050 ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); 2051 bottom.connect(targetAnchor, 0); 2052 ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); 2053 centerY.connect(targetAnchor, 0); 2054 } else if (constraintFrom == ConstraintAnchor.Type.CENTER_X 2055 && constraintTo == ConstraintAnchor.Type.CENTER_X) { 2056 // Center X connection will connect left & right 2057 ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); 2058 ConstraintAnchor leftTarget = target.getAnchor(ConstraintAnchor.Type.LEFT); 2059 left.connect(leftTarget, 0); 2060 ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); 2061 ConstraintAnchor rightTarget = target.getAnchor(ConstraintAnchor.Type.RIGHT); 2062 right.connect(rightTarget, 0); 2063 ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); 2064 centerX.connect(target.getAnchor(constraintTo), 0); 2065 } else if (constraintFrom == ConstraintAnchor.Type.CENTER_Y 2066 && constraintTo == ConstraintAnchor.Type.CENTER_Y) { 2067 // Center Y connection will connect top & bottom. 2068 ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); 2069 ConstraintAnchor topTarget = target.getAnchor(ConstraintAnchor.Type.TOP); 2070 top.connect(topTarget, 0); 2071 ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); 2072 ConstraintAnchor bottomTarget = target.getAnchor(ConstraintAnchor.Type.BOTTOM); 2073 bottom.connect(bottomTarget, 0); 2074 ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); 2075 centerY.connect(target.getAnchor(constraintTo), 0); 2076 } else { 2077 ConstraintAnchor fromAnchor = getAnchor(constraintFrom); 2078 ConstraintAnchor toAnchor = target.getAnchor(constraintTo); 2079 if (fromAnchor.isValidConnection(toAnchor)) { 2080 // make sure that the baseline takes precedence over top/bottom 2081 // and reversely, reset the baseline if we are connecting top/bottom 2082 if (constraintFrom == ConstraintAnchor.Type.BASELINE) { 2083 ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); 2084 ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); 2085 if (top != null) { 2086 top.reset(); 2087 } 2088 if (bottom != null) { 2089 bottom.reset(); 2090 } 2091 } else if ((constraintFrom == ConstraintAnchor.Type.TOP) 2092 || (constraintFrom == ConstraintAnchor.Type.BOTTOM)) { 2093 ConstraintAnchor baseline = getAnchor(ConstraintAnchor.Type.BASELINE); 2094 if (baseline != null) { 2095 baseline.reset(); 2096 } 2097 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); 2098 if (center.getTarget() != toAnchor) { 2099 center.reset(); 2100 } 2101 ConstraintAnchor opposite = getAnchor(constraintFrom).getOpposite(); 2102 ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); 2103 if (centerY.isConnected()) { 2104 opposite.reset(); 2105 centerY.reset(); 2106 } else { 2107 if (AUTOTAG_CENTER) { 2108 // let's see if we need to mark center_y as connected 2109 if (opposite.isConnected() && opposite.getTarget().getOwner() 2110 == toAnchor.getOwner()) { 2111 ConstraintAnchor targetCenterY = toAnchor.getOwner().getAnchor( 2112 ConstraintAnchor.Type.CENTER_Y); 2113 centerY.connect(targetCenterY, 0); 2114 } 2115 } 2116 } 2117 } else if ((constraintFrom == ConstraintAnchor.Type.LEFT) 2118 || (constraintFrom == ConstraintAnchor.Type.RIGHT)) { 2119 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); 2120 if (center.getTarget() != toAnchor) { 2121 center.reset(); 2122 } 2123 ConstraintAnchor opposite = getAnchor(constraintFrom).getOpposite(); 2124 ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); 2125 if (centerX.isConnected()) { 2126 opposite.reset(); 2127 centerX.reset(); 2128 } else { 2129 if (AUTOTAG_CENTER) { 2130 // let's see if we need to mark center_x as connected 2131 if (opposite.isConnected() && opposite.getTarget().getOwner() 2132 == toAnchor.getOwner()) { 2133 ConstraintAnchor targetCenterX = toAnchor.getOwner().getAnchor( 2134 ConstraintAnchor.Type.CENTER_X); 2135 centerX.connect(targetCenterX, 0); 2136 } 2137 } 2138 } 2139 2140 } 2141 fromAnchor.connect(toAnchor, margin); 2142 } 2143 } 2144 } 2145 2146 /** 2147 * Reset all the constraints set on this widget 2148 */ resetAllConstraints()2149 public void resetAllConstraints() { 2150 resetAnchors(); 2151 setVerticalBiasPercent(DEFAULT_BIAS); 2152 setHorizontalBiasPercent(DEFAULT_BIAS); 2153 } 2154 2155 /** 2156 * Reset the given anchor 2157 * 2158 * @param anchor the anchor we want to reset 2159 */ resetAnchor(ConstraintAnchor anchor)2160 public void resetAnchor(ConstraintAnchor anchor) { 2161 if (getParent() != null) { 2162 if (getParent() instanceof ConstraintWidgetContainer) { 2163 ConstraintWidgetContainer parent = (ConstraintWidgetContainer) getParent(); 2164 if (parent.handlesInternalConstraints()) { 2165 return; 2166 } 2167 } 2168 } 2169 ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); 2170 ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); 2171 ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); 2172 ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); 2173 ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); 2174 ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); 2175 ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); 2176 2177 if (anchor == center) { 2178 if (left.isConnected() && right.isConnected() 2179 && left.getTarget() == right.getTarget()) { 2180 left.reset(); 2181 right.reset(); 2182 } 2183 if (top.isConnected() && bottom.isConnected() 2184 && top.getTarget() == bottom.getTarget()) { 2185 top.reset(); 2186 bottom.reset(); 2187 } 2188 mHorizontalBiasPercent = 0.5f; 2189 mVerticalBiasPercent = 0.5f; 2190 } else if (anchor == centerX) { 2191 if (left.isConnected() && right.isConnected() 2192 && left.getTarget().getOwner() == right.getTarget().getOwner()) { 2193 left.reset(); 2194 right.reset(); 2195 } 2196 mHorizontalBiasPercent = 0.5f; 2197 } else if (anchor == centerY) { 2198 if (top.isConnected() && bottom.isConnected() 2199 && top.getTarget().getOwner() == bottom.getTarget().getOwner()) { 2200 top.reset(); 2201 bottom.reset(); 2202 } 2203 mVerticalBiasPercent = 0.5f; 2204 } else if (anchor == left || anchor == right) { 2205 if (left.isConnected() && left.getTarget() == right.getTarget()) { 2206 center.reset(); 2207 } 2208 } else if (anchor == top || anchor == bottom) { 2209 if (top.isConnected() && top.getTarget() == bottom.getTarget()) { 2210 center.reset(); 2211 } 2212 } 2213 anchor.reset(); 2214 } 2215 2216 /** 2217 * Reset all connections 2218 */ resetAnchors()2219 public void resetAnchors() { 2220 ConstraintWidget parent = getParent(); 2221 if (parent != null && parent instanceof ConstraintWidgetContainer) { 2222 ConstraintWidgetContainer parentContainer = (ConstraintWidgetContainer) getParent(); 2223 if (parentContainer.handlesInternalConstraints()) { 2224 return; 2225 } 2226 } 2227 for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) { 2228 final ConstraintAnchor anchor = mAnchors.get(i); 2229 anchor.reset(); 2230 } 2231 } 2232 2233 /** 2234 * Given a type of anchor, returns the corresponding anchor. 2235 * 2236 * @param anchorType type of the anchor (LEFT, TOP, RIGHT, BOTTOM, BASELINE, CENTER_X, CENTER_Y) 2237 * @return the matching anchor 2238 */ getAnchor(ConstraintAnchor.Type anchorType)2239 public ConstraintAnchor getAnchor(ConstraintAnchor.Type anchorType) { 2240 switch (anchorType) { 2241 case LEFT: { 2242 return mLeft; 2243 } 2244 case TOP: { 2245 return mTop; 2246 } 2247 case RIGHT: { 2248 return mRight; 2249 } 2250 case BOTTOM: { 2251 return mBottom; 2252 } 2253 case BASELINE: { 2254 return mBaseline; 2255 } 2256 case CENTER_X: { 2257 return mCenterX; 2258 } 2259 case CENTER_Y: { 2260 return mCenterY; 2261 } 2262 case CENTER: { 2263 return mCenter; 2264 } 2265 case NONE: 2266 return null; 2267 } 2268 throw new AssertionError(anchorType.name()); 2269 } 2270 2271 /** 2272 * Accessor for the horizontal dimension behaviour 2273 * 2274 * @return dimension behaviour 2275 */ getHorizontalDimensionBehaviour()2276 public DimensionBehaviour getHorizontalDimensionBehaviour() { 2277 return mListDimensionBehaviors[DIMENSION_HORIZONTAL]; 2278 } 2279 2280 /** 2281 * Accessor for the vertical dimension behaviour 2282 * 2283 * @return dimension behaviour 2284 */ getVerticalDimensionBehaviour()2285 public DimensionBehaviour getVerticalDimensionBehaviour() { 2286 return mListDimensionBehaviors[DIMENSION_VERTICAL]; 2287 } 2288 2289 /** 2290 * Get the widget's {@link DimensionBehaviour} in an specific orientation. 2291 * 2292 * @return The {@link DimensionBehaviour} of the widget. 2293 */ getDimensionBehaviour(int orientation)2294 public DimensionBehaviour getDimensionBehaviour(int orientation) { 2295 if (orientation == HORIZONTAL) { 2296 return getHorizontalDimensionBehaviour(); 2297 } else if (orientation == VERTICAL) { 2298 return getVerticalDimensionBehaviour(); 2299 } else { 2300 return null; 2301 } 2302 } 2303 2304 /** 2305 * Set the widget's behaviour for the horizontal dimension 2306 * 2307 * @param behaviour the horizontal dimension's behaviour 2308 */ setHorizontalDimensionBehaviour(DimensionBehaviour behaviour)2309 public void setHorizontalDimensionBehaviour(DimensionBehaviour behaviour) { 2310 mListDimensionBehaviors[DIMENSION_HORIZONTAL] = behaviour; 2311 } 2312 2313 /** 2314 * Set the widget's behaviour for the vertical dimension 2315 * 2316 * @param behaviour the vertical dimension's behaviour 2317 */ setVerticalDimensionBehaviour(DimensionBehaviour behaviour)2318 public void setVerticalDimensionBehaviour(DimensionBehaviour behaviour) { 2319 mListDimensionBehaviors[DIMENSION_VERTICAL] = behaviour; 2320 } 2321 2322 /** 2323 * Test if you are in a Horizontal chain 2324 * 2325 * @return true if in a horizontal chain 2326 */ isInHorizontalChain()2327 public boolean isInHorizontalChain() { 2328 if ((mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft) 2329 || (mRight.mTarget != null && mRight.mTarget.mTarget == mRight)) { 2330 return true; 2331 } 2332 return false; 2333 } 2334 2335 /** 2336 * Return the previous chain member if one exists 2337 * 2338 * @param orientation HORIZONTAL or VERTICAL 2339 * @return the previous chain member or null if we are the first chain element 2340 */ getPreviousChainMember(int orientation)2341 public ConstraintWidget getPreviousChainMember(int orientation) { 2342 if (orientation == HORIZONTAL) { 2343 if (mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft) { 2344 return mLeft.mTarget.mOwner; 2345 } 2346 } else if (orientation == VERTICAL) { 2347 if (mTop.mTarget != null && mTop.mTarget.mTarget == mTop) { 2348 return mTop.mTarget.mOwner; 2349 } 2350 } 2351 return null; 2352 } 2353 2354 /** 2355 * Return the next chain member if one exists 2356 * 2357 * @param orientation HORIZONTAL or VERTICAL 2358 * @return the next chain member or null if we are the last chain element 2359 */ getNextChainMember(int orientation)2360 public ConstraintWidget getNextChainMember(int orientation) { 2361 if (orientation == HORIZONTAL) { 2362 if (mRight.mTarget != null && mRight.mTarget.mTarget == mRight) { 2363 return mRight.mTarget.mOwner; 2364 } 2365 } else if (orientation == VERTICAL) { 2366 if (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom) { 2367 return mBottom.mTarget.mOwner; 2368 } 2369 } 2370 return null; 2371 } 2372 2373 /** 2374 * if in a horizontal chain return the left most widget in the chain. 2375 * 2376 * @return left most widget in chain or null 2377 */ getHorizontalChainControlWidget()2378 public ConstraintWidget getHorizontalChainControlWidget() { 2379 ConstraintWidget found = null; 2380 if (isInHorizontalChain()) { 2381 ConstraintWidget tmp = this; 2382 2383 while (found == null && tmp != null) { 2384 ConstraintAnchor anchor = tmp.getAnchor(ConstraintAnchor.Type.LEFT); 2385 ConstraintAnchor targetOwner = (anchor == null) ? null : anchor.getTarget(); 2386 ConstraintWidget target = (targetOwner == null) ? null : targetOwner.getOwner(); 2387 if (target == getParent()) { 2388 found = tmp; 2389 break; 2390 } 2391 ConstraintAnchor targetAnchor = (target == null) 2392 ? null : target.getAnchor(ConstraintAnchor.Type.RIGHT).getTarget(); 2393 if (targetAnchor != null && targetAnchor.getOwner() != tmp) { 2394 found = tmp; 2395 } else { 2396 tmp = target; 2397 } 2398 } 2399 } 2400 return found; 2401 } 2402 2403 2404 /** 2405 * Test if you are in a vertical chain 2406 * 2407 * @return true if in a vertical chain 2408 */ isInVerticalChain()2409 public boolean isInVerticalChain() { 2410 if ((mTop.mTarget != null && mTop.mTarget.mTarget == mTop) 2411 || (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom)) { 2412 return true; 2413 } 2414 return false; 2415 } 2416 2417 /** 2418 * if in a vertical chain return the top most widget in the chain. 2419 * 2420 * @return top most widget in chain or null 2421 */ getVerticalChainControlWidget()2422 public ConstraintWidget getVerticalChainControlWidget() { 2423 ConstraintWidget found = null; 2424 if (isInVerticalChain()) { 2425 ConstraintWidget tmp = this; 2426 while (found == null && tmp != null) { 2427 ConstraintAnchor anchor = tmp.getAnchor(ConstraintAnchor.Type.TOP); 2428 ConstraintAnchor targetOwner = (anchor == null) ? null : anchor.getTarget(); 2429 ConstraintWidget target = (targetOwner == null) ? null : targetOwner.getOwner(); 2430 if (target == getParent()) { 2431 found = tmp; 2432 break; 2433 } 2434 ConstraintAnchor targetAnchor = (target == null) 2435 ? null : target.getAnchor(ConstraintAnchor.Type.BOTTOM).getTarget(); 2436 if (targetAnchor != null && targetAnchor.getOwner() != tmp) { 2437 found = tmp; 2438 } else { 2439 tmp = target; 2440 } 2441 } 2442 2443 } 2444 return found; 2445 } 2446 2447 /** 2448 * Determine if the widget is the first element of a chain in a given orientation. 2449 * 2450 * @param orientation Either {@link #HORIZONTAL} or {@link #VERTICAL} 2451 * @return if the widget is the head of a chain 2452 */ isChainHead(int orientation)2453 private boolean isChainHead(int orientation) { 2454 int offset = orientation * 2; 2455 return (mListAnchors[offset].mTarget != null 2456 && mListAnchors[offset].mTarget.mTarget != mListAnchors[offset]) 2457 && (mListAnchors[offset + 1].mTarget != null 2458 && mListAnchors[offset + 1].mTarget.mTarget == mListAnchors[offset + 1]); 2459 } 2460 2461 2462 /*-----------------------------------------------------------------------*/ 2463 // Constraints 2464 /*-----------------------------------------------------------------------*/ 2465 2466 /** 2467 * Add this widget to the solver 2468 * 2469 * @param system the solver we want to add the widget to 2470 * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on 2471 */ addToSolver(LinearSystem system, boolean optimize)2472 public void addToSolver(LinearSystem system, boolean optimize) { 2473 if (LinearSystem.FULL_DEBUG) { 2474 System.out.println("\n----------------------------------------------"); 2475 System.out.println("-- adding " + getDebugName() + " to the solver"); 2476 if (isInVirtualLayout()) { 2477 System.out.println("-- note: is in virtual layout"); 2478 } 2479 System.out.println("----------------------------------------------\n"); 2480 } 2481 2482 SolverVariable left = system.createObjectVariable(mLeft); 2483 SolverVariable right = system.createObjectVariable(mRight); 2484 SolverVariable top = system.createObjectVariable(mTop); 2485 SolverVariable bottom = system.createObjectVariable(mBottom); 2486 SolverVariable baseline = system.createObjectVariable(mBaseline); 2487 2488 boolean horizontalParentWrapContent = false; 2489 boolean verticalParentWrapContent = false; 2490 if (mParent != null) { 2491 horizontalParentWrapContent = mParent != null 2492 ? mParent.mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT : false; 2493 verticalParentWrapContent = mParent != null 2494 ? mParent.mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT : false; 2495 2496 switch (mWrapBehaviorInParent) { 2497 case WRAP_BEHAVIOR_SKIPPED: { 2498 horizontalParentWrapContent = false; 2499 verticalParentWrapContent = false; 2500 } 2501 break; 2502 case WRAP_BEHAVIOR_HORIZONTAL_ONLY: { 2503 verticalParentWrapContent = false; 2504 } 2505 break; 2506 case WRAP_BEHAVIOR_VERTICAL_ONLY: { 2507 horizontalParentWrapContent = false; 2508 } 2509 break; 2510 } 2511 } 2512 2513 if (!(mVisibility != GONE || mAnimated || hasDependencies() 2514 || mIsInBarrier[HORIZONTAL] || mIsInBarrier[VERTICAL])) { 2515 return; 2516 } 2517 2518 if (mResolvedHorizontal || mResolvedVertical) { 2519 if (LinearSystem.FULL_DEBUG) { 2520 System.out.println("\n----------------------------------------------"); 2521 System.out.println("-- setting " + getDebugName() 2522 + " to " + mX + ", " + mY + " " + mWidth + " x " + mHeight); 2523 System.out.println("----------------------------------------------\n"); 2524 } 2525 // For now apply all, but that won't work for wrap/wrap layouts. 2526 if (mResolvedHorizontal) { 2527 system.addEquality(left, mX); 2528 system.addEquality(right, mX + mWidth); 2529 if (horizontalParentWrapContent && mParent != null) { 2530 if (mOptimizeWrapOnResolved) { 2531 ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent; 2532 container.addHorizontalWrapMinVariable(mLeft); 2533 container.addHorizontalWrapMaxVariable(mRight); 2534 } else { 2535 int wrapStrength = SolverVariable.STRENGTH_EQUALITY; 2536 system.addGreaterThan(system.createObjectVariable(mParent.mRight), 2537 right, 0, wrapStrength); 2538 } 2539 } 2540 } 2541 if (mResolvedVertical) { 2542 system.addEquality(top, mY); 2543 system.addEquality(bottom, mY + mHeight); 2544 if (mBaseline.hasDependents()) { 2545 system.addEquality(baseline, mY + mBaselineDistance); 2546 } 2547 if (verticalParentWrapContent && mParent != null) { 2548 if (mOptimizeWrapOnResolved) { 2549 ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent; 2550 container.addVerticalWrapMinVariable(mTop); 2551 container.addVerticalWrapMaxVariable(mBottom); 2552 } else { 2553 int wrapStrength = SolverVariable.STRENGTH_EQUALITY; 2554 system.addGreaterThan(system.createObjectVariable(mParent.mBottom), 2555 bottom, 0, wrapStrength); 2556 } 2557 } 2558 } 2559 if (mResolvedHorizontal && mResolvedVertical) { 2560 mResolvedHorizontal = false; 2561 mResolvedVertical = false; 2562 if (LinearSystem.FULL_DEBUG) { 2563 System.out.println("\n----------------------------------------------"); 2564 System.out.println("-- setting COMPLETED for " + getDebugName()); 2565 System.out.println("----------------------------------------------\n"); 2566 } 2567 return; 2568 } 2569 } 2570 2571 if (LinearSystem.sMetrics != null) { 2572 LinearSystem.sMetrics.widgets++; 2573 } 2574 if (FULL_DEBUG) { 2575 if (optimize && mHorizontalRun != null && mVerticalRun != null) { 2576 System.out.println("-- horizontal run : " 2577 + mHorizontalRun.start + " : " + mHorizontalRun.end); 2578 System.out.println("-- vertical run : " 2579 + mVerticalRun.start + " : " + mVerticalRun.end); 2580 } 2581 } 2582 if (optimize && mHorizontalRun != null && mVerticalRun != null 2583 && mHorizontalRun.start.resolved && mHorizontalRun.end.resolved 2584 && mVerticalRun.start.resolved && mVerticalRun.end.resolved) { 2585 2586 if (LinearSystem.sMetrics != null) { 2587 LinearSystem.sMetrics.graphSolved++; 2588 } 2589 system.addEquality(left, mHorizontalRun.start.value); 2590 system.addEquality(right, mHorizontalRun.end.value); 2591 system.addEquality(top, mVerticalRun.start.value); 2592 system.addEquality(bottom, mVerticalRun.end.value); 2593 system.addEquality(baseline, mVerticalRun.baseline.value); 2594 if (mParent != null) { 2595 if (horizontalParentWrapContent 2596 && isTerminalWidget[HORIZONTAL] && !isInHorizontalChain()) { 2597 SolverVariable parentMax = system.createObjectVariable(mParent.mRight); 2598 system.addGreaterThan(parentMax, right, 0, SolverVariable.STRENGTH_FIXED); 2599 } 2600 if (verticalParentWrapContent 2601 && isTerminalWidget[VERTICAL] && !isInVerticalChain()) { 2602 SolverVariable parentMax = system.createObjectVariable(mParent.mBottom); 2603 system.addGreaterThan(parentMax, bottom, 0, SolverVariable.STRENGTH_FIXED); 2604 } 2605 } 2606 mResolvedHorizontal = false; 2607 mResolvedVertical = false; 2608 return; // we are done here 2609 } 2610 if (LinearSystem.sMetrics != null) { 2611 LinearSystem.sMetrics.linearSolved++; 2612 } 2613 2614 boolean inHorizontalChain = false; 2615 boolean inVerticalChain = false; 2616 2617 if (mParent != null) { 2618 // Add this widget to a horizontal chain if it is the Head of it. 2619 if (isChainHead(HORIZONTAL)) { 2620 ((ConstraintWidgetContainer) mParent).addChain(this, HORIZONTAL); 2621 inHorizontalChain = true; 2622 } else { 2623 inHorizontalChain = isInHorizontalChain(); 2624 } 2625 2626 // Add this widget to a vertical chain if it is the Head of it. 2627 if (isChainHead(VERTICAL)) { 2628 ((ConstraintWidgetContainer) mParent).addChain(this, VERTICAL); 2629 inVerticalChain = true; 2630 } else { 2631 inVerticalChain = isInVerticalChain(); 2632 } 2633 2634 if (!inHorizontalChain && horizontalParentWrapContent && mVisibility != GONE 2635 && mLeft.mTarget == null && mRight.mTarget == null) { 2636 if (FULL_DEBUG) { 2637 System.out.println("<>1 ADDING H WRAP GREATER FOR " + getDebugName()); 2638 } 2639 SolverVariable parentRight = system.createObjectVariable(mParent.mRight); 2640 system.addGreaterThan(parentRight, right, 0, SolverVariable.STRENGTH_LOW); 2641 } 2642 2643 if (!inVerticalChain && verticalParentWrapContent && mVisibility != GONE 2644 && mTop.mTarget == null && mBottom.mTarget == null && mBaseline == null) { 2645 if (FULL_DEBUG) { 2646 System.out.println("<>1 ADDING V WRAP GREATER FOR " + getDebugName()); 2647 } 2648 SolverVariable parentBottom = system.createObjectVariable(mParent.mBottom); 2649 system.addGreaterThan(parentBottom, bottom, 0, SolverVariable.STRENGTH_LOW); 2650 } 2651 } 2652 2653 int width = mWidth; 2654 if (width < mMinWidth) { 2655 width = mMinWidth; 2656 } 2657 int height = mHeight; 2658 if (height < mMinHeight) { 2659 height = mMinHeight; 2660 } 2661 2662 // Dimensions can be either fixed (a given value) 2663 // or dependent on the solver if set to MATCH_CONSTRAINT 2664 boolean horizontalDimensionFixed = 2665 mListDimensionBehaviors[DIMENSION_HORIZONTAL] != MATCH_CONSTRAINT; 2666 boolean verticalDimensionFixed = 2667 mListDimensionBehaviors[DIMENSION_VERTICAL] != MATCH_CONSTRAINT; 2668 2669 // We evaluate the dimension ratio here as the connections can change. 2670 // TODO: have a validation pass after connection instead 2671 boolean useRatio = false; 2672 mResolvedDimensionRatioSide = mDimensionRatioSide; 2673 mResolvedDimensionRatio = mDimensionRatio; 2674 2675 int matchConstraintDefaultWidth = mMatchConstraintDefaultWidth; 2676 int matchConstraintDefaultHeight = mMatchConstraintDefaultHeight; 2677 2678 if (mDimensionRatio > 0 && mVisibility != GONE) { 2679 useRatio = true; 2680 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT 2681 && matchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) { 2682 matchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO; 2683 } 2684 if (mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT 2685 && matchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) { 2686 matchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO; 2687 } 2688 2689 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT 2690 && mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT 2691 && matchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO 2692 && matchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) { 2693 setupDimensionRatio(horizontalParentWrapContent, verticalParentWrapContent, 2694 horizontalDimensionFixed, verticalDimensionFixed); 2695 } else if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == MATCH_CONSTRAINT 2696 && matchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) { 2697 mResolvedDimensionRatioSide = HORIZONTAL; 2698 width = (int) (mResolvedDimensionRatio * mHeight); 2699 if (mListDimensionBehaviors[DIMENSION_VERTICAL] != MATCH_CONSTRAINT) { 2700 matchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO_RESOLVED; 2701 useRatio = false; 2702 } 2703 } else if (mListDimensionBehaviors[DIMENSION_VERTICAL] == MATCH_CONSTRAINT 2704 && matchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) { 2705 mResolvedDimensionRatioSide = VERTICAL; 2706 if (mDimensionRatioSide == UNKNOWN) { 2707 // need to reverse the ratio as the parsing is done in horizontal mode 2708 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; 2709 } 2710 height = (int) (mResolvedDimensionRatio * mWidth); 2711 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] != MATCH_CONSTRAINT) { 2712 matchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO_RESOLVED; 2713 useRatio = false; 2714 } 2715 } 2716 } 2717 2718 mResolvedMatchConstraintDefault[HORIZONTAL] = matchConstraintDefaultWidth; 2719 mResolvedMatchConstraintDefault[VERTICAL] = matchConstraintDefaultHeight; 2720 mResolvedHasRatio = useRatio; 2721 2722 boolean useHorizontalRatio = useRatio && (mResolvedDimensionRatioSide == HORIZONTAL 2723 || mResolvedDimensionRatioSide == UNKNOWN); 2724 2725 boolean useVerticalRatio = useRatio && (mResolvedDimensionRatioSide == VERTICAL 2726 || mResolvedDimensionRatioSide == UNKNOWN); 2727 2728 // Horizontal resolution 2729 boolean wrapContent = (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT) 2730 && (this instanceof ConstraintWidgetContainer); 2731 if (wrapContent) { 2732 width = 0; 2733 } 2734 2735 boolean applyPosition = true; 2736 if (mCenter.isConnected()) { 2737 applyPosition = false; 2738 } 2739 2740 boolean isInHorizontalBarrier = mIsInBarrier[HORIZONTAL]; 2741 boolean isInVerticalBarrier = mIsInBarrier[VERTICAL]; 2742 2743 if (mHorizontalResolution != DIRECT && !mResolvedHorizontal) { 2744 if (!optimize || !(mHorizontalRun != null 2745 && mHorizontalRun.start.resolved && mHorizontalRun.end.resolved)) { 2746 SolverVariable parentMax = mParent != null 2747 ? system.createObjectVariable(mParent.mRight) : null; 2748 SolverVariable parentMin = mParent != null 2749 ? system.createObjectVariable(mParent.mLeft) : null; 2750 applyConstraints(system, true, horizontalParentWrapContent, 2751 verticalParentWrapContent, isTerminalWidget[HORIZONTAL], parentMin, 2752 parentMax, mListDimensionBehaviors[DIMENSION_HORIZONTAL], wrapContent, 2753 mLeft, mRight, mX, width, 2754 mMinWidth, mMaxDimension[HORIZONTAL], 2755 mHorizontalBiasPercent, useHorizontalRatio, 2756 mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT, 2757 inHorizontalChain, inVerticalChain, isInHorizontalBarrier, 2758 matchConstraintDefaultWidth, matchConstraintDefaultHeight, 2759 mMatchConstraintMinWidth, mMatchConstraintMaxWidth, 2760 mMatchConstraintPercentWidth, applyPosition); 2761 } else if (optimize) { 2762 system.addEquality(left, mHorizontalRun.start.value); 2763 system.addEquality(right, mHorizontalRun.end.value); 2764 if (mParent != null) { 2765 if (horizontalParentWrapContent 2766 && isTerminalWidget[HORIZONTAL] && !isInHorizontalChain()) { 2767 if (FULL_DEBUG) { 2768 System.out.println("<>2 ADDING H WRAP GREATER FOR " + getDebugName()); 2769 } 2770 SolverVariable parentMax = system.createObjectVariable(mParent.mRight); 2771 system.addGreaterThan(parentMax, right, 0, SolverVariable.STRENGTH_FIXED); 2772 } 2773 } 2774 } 2775 } 2776 2777 boolean applyVerticalConstraints = true; 2778 if (optimize && mVerticalRun != null 2779 && mVerticalRun.start.resolved && mVerticalRun.end.resolved) { 2780 system.addEquality(top, mVerticalRun.start.value); 2781 system.addEquality(bottom, mVerticalRun.end.value); 2782 system.addEquality(baseline, mVerticalRun.baseline.value); 2783 if (mParent != null) { 2784 if (!inVerticalChain && verticalParentWrapContent && isTerminalWidget[VERTICAL]) { 2785 if (FULL_DEBUG) { 2786 System.out.println("<>2 ADDING V WRAP GREATER FOR " + getDebugName()); 2787 } 2788 SolverVariable parentMax = system.createObjectVariable(mParent.mBottom); 2789 system.addGreaterThan(parentMax, bottom, 0, SolverVariable.STRENGTH_FIXED); 2790 } 2791 } 2792 applyVerticalConstraints = false; 2793 } 2794 if (mVerticalResolution == DIRECT) { 2795 if (LinearSystem.FULL_DEBUG) { 2796 System.out.println("\n----------------------------------------------"); 2797 System.out.println("-- DONE adding " + getDebugName() + " to the solver"); 2798 System.out.println("-- SKIP VERTICAL RESOLUTION"); 2799 System.out.println("----------------------------------------------\n"); 2800 } 2801 applyVerticalConstraints = false; 2802 } 2803 if (applyVerticalConstraints && !mResolvedVertical) { 2804 // Vertical Resolution 2805 wrapContent = (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT) 2806 && (this instanceof ConstraintWidgetContainer); 2807 if (wrapContent) { 2808 height = 0; 2809 } 2810 2811 SolverVariable parentMax = mParent != null 2812 ? system.createObjectVariable(mParent.mBottom) : null; 2813 SolverVariable parentMin = mParent != null 2814 ? system.createObjectVariable(mParent.mTop) : null; 2815 2816 if (mBaselineDistance > 0 || mVisibility == GONE) { 2817 // if we are GONE we might still have to deal with baseline, 2818 // even if our baseline distance would be zero 2819 if (mBaseline.mTarget != null) { 2820 system.addEquality(baseline, top, getBaselineDistance(), 2821 SolverVariable.STRENGTH_FIXED); 2822 SolverVariable baselineTarget = system.createObjectVariable(mBaseline.mTarget); 2823 int baselineMargin = mBaseline.getMargin(); 2824 system.addEquality(baseline, baselineTarget, baselineMargin, 2825 SolverVariable.STRENGTH_FIXED); 2826 applyPosition = false; 2827 if (verticalParentWrapContent) { 2828 if (FULL_DEBUG) { 2829 System.out.println("<>3 ADDING V WRAP GREATER FOR " + getDebugName()); 2830 } 2831 SolverVariable end = system.createObjectVariable(mBottom); 2832 int wrapStrength = SolverVariable.STRENGTH_EQUALITY; 2833 system.addGreaterThan(parentMax, end, 0, wrapStrength); 2834 } 2835 } else if (mVisibility == GONE) { 2836 // TODO: use the constraints graph here to help 2837 system.addEquality(baseline, top, mBaseline.getMargin(), 2838 SolverVariable.STRENGTH_FIXED); 2839 } else { 2840 system.addEquality(baseline, top, getBaselineDistance(), 2841 SolverVariable.STRENGTH_FIXED); 2842 } 2843 } 2844 2845 applyConstraints(system, false, verticalParentWrapContent, 2846 horizontalParentWrapContent, isTerminalWidget[VERTICAL], parentMin, 2847 parentMax, mListDimensionBehaviors[DIMENSION_VERTICAL], 2848 wrapContent, mTop, mBottom, mY, height, 2849 mMinHeight, mMaxDimension[VERTICAL], mVerticalBiasPercent, useVerticalRatio, 2850 mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT, 2851 inVerticalChain, inHorizontalChain, isInVerticalBarrier, 2852 matchConstraintDefaultHeight, matchConstraintDefaultWidth, 2853 mMatchConstraintMinHeight, mMatchConstraintMaxHeight, 2854 mMatchConstraintPercentHeight, applyPosition); 2855 } 2856 2857 if (useRatio) { 2858 int strength = SolverVariable.STRENGTH_FIXED; 2859 if (mResolvedDimensionRatioSide == VERTICAL) { 2860 system.addRatio(bottom, top, right, left, mResolvedDimensionRatio, strength); 2861 } else { 2862 system.addRatio(right, left, bottom, top, mResolvedDimensionRatio, strength); 2863 } 2864 } 2865 2866 if (mCenter.isConnected()) { 2867 system.addCenterPoint(this, mCenter.getTarget().getOwner(), 2868 (float) Math.toRadians(mCircleConstraintAngle + 90), mCenter.getMargin()); 2869 } 2870 2871 if (LinearSystem.FULL_DEBUG) { 2872 System.out.println("\n----------------------------------------------"); 2873 System.out.println("-- DONE adding " + getDebugName() + " to the solver"); 2874 System.out.println("----------------------------------------------\n"); 2875 } 2876 mResolvedHorizontal = false; 2877 mResolvedVertical = false; 2878 if (LinearSystem.sMetrics != null) { 2879 LinearSystem.sMetrics.mEquations = system.getNumEquations(); 2880 LinearSystem.sMetrics.mVariables = system.getNumVariables(); 2881 } 2882 2883 } 2884 2885 /** 2886 * Used to select which widgets should be added to the solver first 2887 */ addFirst()2888 boolean addFirst() { 2889 return this instanceof VirtualLayout || this instanceof Guideline; 2890 } 2891 2892 /** 2893 * Resolves the dimension ratio parameters 2894 * (mResolvedDimensionRatioSide & mDimensionRatio) 2895 * 2896 * @param hParentWrapContent true if parent is in wrap content horizontally 2897 * @param vParentWrapContent true if parent is in wrap content vertically 2898 * @param horizontalDimensionFixed true if this widget horizontal dimension is fixed 2899 * @param verticalDimensionFixed true if this widget vertical dimension is fixed 2900 */ setupDimensionRatio(boolean hParentWrapContent, boolean vParentWrapContent, boolean horizontalDimensionFixed, boolean verticalDimensionFixed)2901 public void setupDimensionRatio(boolean hParentWrapContent, 2902 boolean vParentWrapContent, 2903 boolean horizontalDimensionFixed, 2904 boolean verticalDimensionFixed) { 2905 if (mResolvedDimensionRatioSide == UNKNOWN) { 2906 if (horizontalDimensionFixed && !verticalDimensionFixed) { 2907 mResolvedDimensionRatioSide = HORIZONTAL; 2908 } else if (!horizontalDimensionFixed && verticalDimensionFixed) { 2909 mResolvedDimensionRatioSide = VERTICAL; 2910 if (mDimensionRatioSide == UNKNOWN) { 2911 // need to reverse the ratio as the parsing is done in horizontal mode 2912 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; 2913 } 2914 } 2915 } 2916 2917 if (mResolvedDimensionRatioSide == HORIZONTAL 2918 && !(mTop.isConnected() && mBottom.isConnected())) { 2919 mResolvedDimensionRatioSide = VERTICAL; 2920 } else if (mResolvedDimensionRatioSide == VERTICAL 2921 && !(mLeft.isConnected() && mRight.isConnected())) { 2922 mResolvedDimensionRatioSide = HORIZONTAL; 2923 } 2924 2925 // if dimension is still unknown... check parentWrap 2926 if (mResolvedDimensionRatioSide == UNKNOWN) { 2927 if (!(mTop.isConnected() && mBottom.isConnected() 2928 && mLeft.isConnected() && mRight.isConnected())) { 2929 // only do that if not all connections are set 2930 if (mTop.isConnected() && mBottom.isConnected()) { 2931 mResolvedDimensionRatioSide = HORIZONTAL; 2932 } else if (mLeft.isConnected() && mRight.isConnected()) { 2933 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; 2934 mResolvedDimensionRatioSide = VERTICAL; 2935 } 2936 } 2937 } 2938 2939 if (DO_NOT_USE && mResolvedDimensionRatioSide == UNKNOWN) { 2940 if (hParentWrapContent && !vParentWrapContent) { 2941 mResolvedDimensionRatioSide = HORIZONTAL; 2942 } else if (!hParentWrapContent && vParentWrapContent) { 2943 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; 2944 mResolvedDimensionRatioSide = VERTICAL; 2945 } 2946 } 2947 2948 if (mResolvedDimensionRatioSide == UNKNOWN) { 2949 if (mMatchConstraintMinWidth > 0 && mMatchConstraintMinHeight == 0) { 2950 mResolvedDimensionRatioSide = HORIZONTAL; 2951 } else if (mMatchConstraintMinWidth == 0 && mMatchConstraintMinHeight > 0) { 2952 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; 2953 mResolvedDimensionRatioSide = VERTICAL; 2954 } 2955 } 2956 2957 if (DO_NOT_USE && mResolvedDimensionRatioSide == UNKNOWN 2958 && hParentWrapContent && vParentWrapContent) { 2959 mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; 2960 mResolvedDimensionRatioSide = VERTICAL; 2961 } 2962 } 2963 2964 /** 2965 * Apply the constraints in the system depending on the existing anchors, in one dimension 2966 * 2967 * @param system the linear system we are adding constraints to 2968 * @param wrapContent is the widget trying to wrap its content 2969 * (i.e. its size will depends on its content) 2970 * @param beginAnchor the first anchor 2971 * @param endAnchor the second anchor 2972 * @param beginPosition the original position of the anchor 2973 * @param dimension the dimension 2974 * @param matchPercentDimension the percentage relative to the parent, 2975 * applied if in match constraint and percent mode 2976 */ applyConstraints(LinearSystem system, boolean isHorizontal, boolean parentWrapContent, boolean oppositeParentWrapContent, boolean isTerminal, SolverVariable parentMin, SolverVariable parentMax, DimensionBehaviour dimensionBehaviour, boolean wrapContent, ConstraintAnchor beginAnchor, ConstraintAnchor endAnchor, int beginPosition, int dimension, int minDimension, int maxDimension, float bias, boolean useRatio, boolean oppositeVariable, boolean inChain, boolean oppositeInChain, boolean inBarrier, int matchConstraintDefault, int oppositeMatchConstraintDefault, int matchMinDimension, int matchMaxDimension, float matchPercentDimension, boolean applyPosition)2977 private void applyConstraints(LinearSystem system, boolean isHorizontal, 2978 boolean parentWrapContent, boolean oppositeParentWrapContent, 2979 boolean isTerminal, SolverVariable parentMin, 2980 SolverVariable parentMax, 2981 DimensionBehaviour dimensionBehaviour, boolean wrapContent, 2982 ConstraintAnchor beginAnchor, ConstraintAnchor endAnchor, 2983 int beginPosition, int dimension, int minDimension, 2984 int maxDimension, float bias, boolean useRatio, 2985 boolean oppositeVariable, boolean inChain, 2986 boolean oppositeInChain, boolean inBarrier, 2987 int matchConstraintDefault, 2988 int oppositeMatchConstraintDefault, 2989 int matchMinDimension, int matchMaxDimension, 2990 float matchPercentDimension, boolean applyPosition) { 2991 SolverVariable begin = system.createObjectVariable(beginAnchor); 2992 SolverVariable end = system.createObjectVariable(endAnchor); 2993 SolverVariable beginTarget = system.createObjectVariable(beginAnchor.getTarget()); 2994 SolverVariable endTarget = system.createObjectVariable(endAnchor.getTarget()); 2995 2996 if (system.getMetrics() != null) { 2997 system.getMetrics().nonresolvedWidgets++; 2998 } 2999 3000 boolean isBeginConnected = beginAnchor.isConnected(); 3001 boolean isEndConnected = endAnchor.isConnected(); 3002 boolean isCenterConnected = mCenter.isConnected(); 3003 3004 boolean variableSize = false; 3005 3006 int numConnections = 0; 3007 if (isBeginConnected) { 3008 numConnections++; 3009 } 3010 if (isEndConnected) { 3011 numConnections++; 3012 } 3013 if (isCenterConnected) { 3014 numConnections++; 3015 } 3016 3017 if (useRatio) { 3018 matchConstraintDefault = MATCH_CONSTRAINT_RATIO; 3019 } 3020 switch (dimensionBehaviour) { 3021 case FIXED: { 3022 variableSize = false; 3023 } 3024 break; 3025 case WRAP_CONTENT: { 3026 variableSize = false; 3027 } 3028 break; 3029 case MATCH_PARENT: { 3030 variableSize = false; 3031 } 3032 break; 3033 case MATCH_CONSTRAINT: { 3034 variableSize = matchConstraintDefault != MATCH_CONSTRAINT_RATIO_RESOLVED; 3035 } 3036 break; 3037 } 3038 3039 3040 if (mWidthOverride != -1 && isHorizontal) { 3041 if (FULL_DEBUG) { 3042 System.out.println("OVERRIDE WIDTH to " + mWidthOverride); 3043 } 3044 variableSize = false; 3045 dimension = mWidthOverride; 3046 mWidthOverride = -1; 3047 } 3048 if (mHeightOverride != -1 && !isHorizontal) { 3049 if (FULL_DEBUG) { 3050 System.out.println("OVERRIDE HEIGHT to " + mHeightOverride); 3051 } 3052 variableSize = false; 3053 dimension = mHeightOverride; 3054 mHeightOverride = -1; 3055 } 3056 3057 if (mVisibility == ConstraintWidget.GONE) { 3058 dimension = 0; 3059 variableSize = false; 3060 } 3061 3062 // First apply starting direct connections (more solver-friendly) 3063 if (applyPosition) { 3064 if (!isBeginConnected && !isEndConnected && !isCenterConnected) { 3065 system.addEquality(begin, beginPosition); 3066 } else if (isBeginConnected && !isEndConnected) { 3067 system.addEquality(begin, beginTarget, 3068 beginAnchor.getMargin(), SolverVariable.STRENGTH_FIXED); 3069 } 3070 } 3071 3072 // Then apply the dimension 3073 if (!variableSize) { 3074 if (wrapContent) { 3075 system.addEquality(end, begin, 0, SolverVariable.STRENGTH_HIGH); 3076 if (minDimension > 0) { 3077 system.addGreaterThan(end, begin, minDimension, SolverVariable.STRENGTH_FIXED); 3078 } 3079 if (maxDimension < Integer.MAX_VALUE) { 3080 system.addLowerThan(end, begin, maxDimension, SolverVariable.STRENGTH_FIXED); 3081 } 3082 } else { 3083 system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_FIXED); 3084 } 3085 } else { 3086 if (numConnections != 2 3087 && !useRatio 3088 && ((matchConstraintDefault == MATCH_CONSTRAINT_WRAP) 3089 || (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD))) { 3090 variableSize = false; 3091 int d = Math.max(matchMinDimension, dimension); 3092 if (matchMaxDimension > 0) { 3093 d = Math.min(matchMaxDimension, d); 3094 } 3095 system.addEquality(end, begin, d, SolverVariable.STRENGTH_FIXED); 3096 } else { 3097 if (matchMinDimension == WRAP) { 3098 matchMinDimension = dimension; 3099 } 3100 if (matchMaxDimension == WRAP) { 3101 matchMaxDimension = dimension; 3102 } 3103 if (dimension > 0 3104 && matchConstraintDefault != MATCH_CONSTRAINT_WRAP) { 3105 if (USE_WRAP_DIMENSION_FOR_SPREAD 3106 && (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD)) { 3107 system.addGreaterThan(end, begin, dimension, 3108 SolverVariable.STRENGTH_HIGHEST); 3109 } 3110 dimension = 0; 3111 } 3112 3113 if (matchMinDimension > 0) { 3114 system.addGreaterThan(end, begin, matchMinDimension, 3115 SolverVariable.STRENGTH_FIXED); 3116 dimension = Math.max(dimension, matchMinDimension); 3117 } 3118 if (matchMaxDimension > 0) { 3119 boolean applyLimit = true; 3120 if (parentWrapContent && matchConstraintDefault == MATCH_CONSTRAINT_WRAP) { 3121 applyLimit = false; 3122 } 3123 if (applyLimit) { 3124 system.addLowerThan(end, begin, 3125 matchMaxDimension, SolverVariable.STRENGTH_FIXED); 3126 } 3127 dimension = Math.min(dimension, matchMaxDimension); 3128 } 3129 if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) { 3130 if (parentWrapContent) { 3131 system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_FIXED); 3132 } else if (inChain) { 3133 system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_EQUALITY); 3134 system.addLowerThan(end, begin, dimension, SolverVariable.STRENGTH_FIXED); 3135 } else { 3136 system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_EQUALITY); 3137 system.addLowerThan(end, begin, dimension, SolverVariable.STRENGTH_FIXED); 3138 } 3139 } else if (matchConstraintDefault == MATCH_CONSTRAINT_PERCENT) { 3140 SolverVariable percentBegin = null; 3141 SolverVariable percentEnd = null; 3142 if (beginAnchor.getType() == ConstraintAnchor.Type.TOP 3143 || beginAnchor.getType() == ConstraintAnchor.Type.BOTTOM) { 3144 // vertical 3145 percentBegin = system.createObjectVariable( 3146 mParent.getAnchor(ConstraintAnchor.Type.TOP)); 3147 percentEnd = system.createObjectVariable( 3148 mParent.getAnchor(ConstraintAnchor.Type.BOTTOM)); 3149 } else { 3150 percentBegin = system.createObjectVariable( 3151 mParent.getAnchor(ConstraintAnchor.Type.LEFT)); 3152 percentEnd = system.createObjectVariable( 3153 mParent.getAnchor(ConstraintAnchor.Type.RIGHT)); 3154 } 3155 system.addConstraint(system.createRow().createRowDimensionRatio(end, 3156 begin, percentEnd, percentBegin, matchPercentDimension)); 3157 if (parentWrapContent) { 3158 variableSize = false; 3159 } 3160 } else { 3161 isTerminal = true; 3162 } 3163 } 3164 } 3165 3166 if (!applyPosition || inChain) { 3167 // If we don't need to apply the position, let's finish now. 3168 if (LinearSystem.FULL_DEBUG) { 3169 System.out.println("only deal with dimension for " + mDebugName 3170 + ", not positioning (applyPosition: " 3171 + applyPosition + " inChain: " + inChain + ")"); 3172 } 3173 if (numConnections < 2 && parentWrapContent && isTerminal) { 3174 system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_FIXED); 3175 boolean applyEnd = isHorizontal || (mBaseline.mTarget == null); 3176 if (!isHorizontal && mBaseline.mTarget != null) { 3177 // generally we wouldn't take the current widget in the wrap content, 3178 // but if the connected element is a ratio widget, 3179 // then we can contribute (as the ratio widget may not be enough by itself) 3180 // to it. 3181 ConstraintWidget target = mBaseline.mTarget.mOwner; 3182 if (target.mDimensionRatio != 0 3183 && target.mListDimensionBehaviors[0] == MATCH_CONSTRAINT 3184 && target.mListDimensionBehaviors[1] == MATCH_CONSTRAINT) { 3185 applyEnd = true; 3186 } else { 3187 applyEnd = false; 3188 } 3189 } 3190 if (applyEnd) { 3191 if (FULL_DEBUG) { 3192 System.out.println("<>4 ADDING WRAP GREATER FOR " + getDebugName()); 3193 } 3194 system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_FIXED); 3195 } 3196 } 3197 return; 3198 } 3199 3200 // Ok, we are dealing with single or centered constraints, let's apply them 3201 3202 int wrapStrength = SolverVariable.STRENGTH_EQUALITY; 3203 3204 if (!isBeginConnected && !isEndConnected && !isCenterConnected) { 3205 // note we already applied the start position before, no need to redo it... 3206 } else if (isBeginConnected && !isEndConnected) { 3207 // note we already applied the start position before, no need to redo it... 3208 3209 // If we are constrained to a barrier, make sure that we are not bypassed in the wrap 3210 ConstraintWidget beginWidget = beginAnchor.mTarget.mOwner; 3211 if (parentWrapContent && beginWidget instanceof Barrier) { 3212 wrapStrength = SolverVariable.STRENGTH_FIXED; 3213 } 3214 } else if (!isBeginConnected && isEndConnected) { 3215 system.addEquality(end, endTarget, 3216 -endAnchor.getMargin(), SolverVariable.STRENGTH_FIXED); 3217 if (parentWrapContent) { 3218 if (mOptimizeWrapO && begin.isFinalValue && mParent != null) { 3219 ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent; 3220 if (isHorizontal) { 3221 container.addHorizontalWrapMinVariable(beginAnchor); 3222 } else { 3223 container.addVerticalWrapMinVariable(beginAnchor); 3224 } 3225 } else { 3226 if (FULL_DEBUG) { 3227 System.out.println("<>5 ADDING WRAP GREATER FOR " + getDebugName()); 3228 } 3229 system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_EQUALITY); 3230 } 3231 } 3232 } else if (isBeginConnected && isEndConnected) { 3233 boolean applyBoundsCheck = true; 3234 boolean applyCentering = false; 3235 boolean applyStrongChecks = false; 3236 boolean applyRangeCheck = false; 3237 int rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3238 3239 // TODO: might not need it here (it's overridden) 3240 int boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST; 3241 int centeringStrength = SolverVariable.STRENGTH_BARRIER; 3242 3243 if (parentWrapContent) { 3244 rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3245 } 3246 ConstraintWidget beginWidget = beginAnchor.mTarget.mOwner; 3247 ConstraintWidget endWidget = endAnchor.mTarget.mOwner; 3248 ConstraintWidget parent = getParent(); 3249 3250 if (variableSize) { 3251 if (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD) { 3252 if (matchMaxDimension == 0 && matchMinDimension == 0) { 3253 applyStrongChecks = true; 3254 rangeCheckStrength = SolverVariable.STRENGTH_FIXED; 3255 boundsCheckStrength = SolverVariable.STRENGTH_FIXED; 3256 // Optimization in case of centering in parent 3257 if (beginTarget.isFinalValue && endTarget.isFinalValue) { 3258 system.addEquality(begin, beginTarget, 3259 beginAnchor.getMargin(), SolverVariable.STRENGTH_FIXED); 3260 system.addEquality(end, endTarget, 3261 -endAnchor.getMargin(), SolverVariable.STRENGTH_FIXED); 3262 return; 3263 } 3264 } else { 3265 applyCentering = true; 3266 rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3267 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3268 applyBoundsCheck = true; 3269 applyRangeCheck = true; 3270 } 3271 if (beginWidget instanceof Barrier || endWidget instanceof Barrier) { 3272 boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST; 3273 } 3274 } else if (matchConstraintDefault == MATCH_CONSTRAINT_PERCENT) { 3275 applyCentering = true; 3276 rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3277 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3278 applyBoundsCheck = true; 3279 applyRangeCheck = true; 3280 if (beginWidget instanceof Barrier || endWidget instanceof Barrier) { 3281 boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST; 3282 } 3283 } else if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) { 3284 applyCentering = true; 3285 applyRangeCheck = true; 3286 rangeCheckStrength = SolverVariable.STRENGTH_FIXED; 3287 } else if (matchConstraintDefault == MATCH_CONSTRAINT_RATIO) { 3288 if (mResolvedDimensionRatioSide == UNKNOWN) { 3289 applyCentering = true; 3290 applyRangeCheck = true; 3291 applyStrongChecks = true; 3292 rangeCheckStrength = SolverVariable.STRENGTH_FIXED; 3293 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3294 if (oppositeInChain) { 3295 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3296 centeringStrength = SolverVariable.STRENGTH_HIGHEST; 3297 if (parentWrapContent) { 3298 centeringStrength = SolverVariable.STRENGTH_EQUALITY; 3299 } 3300 } else { 3301 centeringStrength = SolverVariable.STRENGTH_FIXED; 3302 } 3303 } else { 3304 applyCentering = true; 3305 applyRangeCheck = true; 3306 applyStrongChecks = true; 3307 if (useRatio) { 3308 // useRatio is true 3309 // if the side we base ourselves on for the ratio is this one 3310 // if that's not the case, we need to have a stronger constraint. 3311 boolean otherSideInvariable = 3312 oppositeMatchConstraintDefault == MATCH_CONSTRAINT_PERCENT 3313 || oppositeMatchConstraintDefault 3314 == MATCH_CONSTRAINT_WRAP; 3315 if (!otherSideInvariable) { 3316 rangeCheckStrength = SolverVariable.STRENGTH_FIXED; 3317 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3318 } 3319 } else { 3320 rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3321 if (matchMaxDimension > 0) { 3322 boundsCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3323 } else if (matchMaxDimension == 0 && matchMinDimension == 0) { 3324 if (!oppositeInChain) { 3325 boundsCheckStrength = SolverVariable.STRENGTH_FIXED; 3326 } else { 3327 if (beginWidget != parent && endWidget != parent) { 3328 rangeCheckStrength = SolverVariable.STRENGTH_HIGHEST; 3329 } else { 3330 rangeCheckStrength = SolverVariable.STRENGTH_EQUALITY; 3331 } 3332 boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST; 3333 } 3334 } 3335 } 3336 } 3337 } 3338 } else { 3339 applyCentering = true; 3340 applyRangeCheck = true; 3341 3342 // Let's optimize away if we can... 3343 if (beginTarget.isFinalValue && endTarget.isFinalValue) { 3344 system.addCentering(begin, beginTarget, beginAnchor.getMargin(), 3345 bias, endTarget, end, endAnchor.getMargin(), 3346 SolverVariable.STRENGTH_FIXED); 3347 if (parentWrapContent && isTerminal) { 3348 int margin = 0; 3349 if (endAnchor.mTarget != null) { 3350 margin = endAnchor.getMargin(); 3351 } 3352 if (endTarget != parentMax) { // if not already applied 3353 if (FULL_DEBUG) { 3354 System.out.println("<>6 ADDING WRAP GREATER FOR " + getDebugName()); 3355 } 3356 system.addGreaterThan(parentMax, end, margin, wrapStrength); 3357 } 3358 } 3359 return; 3360 } 3361 } 3362 3363 if (applyRangeCheck && beginTarget == endTarget && beginWidget != parent) { 3364 // no need to apply range / bounds check if we are centered on the same anchor 3365 applyRangeCheck = false; 3366 applyBoundsCheck = false; 3367 } 3368 3369 if (applyCentering) { 3370 if (!variableSize && !oppositeVariable && !oppositeInChain 3371 && beginTarget == parentMin && endTarget == parentMax) { 3372 // for fixed size widgets, we can simplify the constraints 3373 centeringStrength = SolverVariable.STRENGTH_FIXED; 3374 rangeCheckStrength = SolverVariable.STRENGTH_FIXED; 3375 applyBoundsCheck = false; 3376 parentWrapContent = false; 3377 } 3378 3379 system.addCentering(begin, beginTarget, beginAnchor.getMargin(), 3380 bias, endTarget, end, endAnchor.getMargin(), centeringStrength); 3381 } 3382 3383 if (mVisibility == GONE && !endAnchor.hasDependents()) { 3384 return; 3385 } 3386 3387 if (applyRangeCheck) { 3388 if (parentWrapContent && beginTarget != endTarget 3389 && !variableSize) { 3390 if (beginWidget instanceof Barrier || endWidget instanceof Barrier) { 3391 rangeCheckStrength = SolverVariable.STRENGTH_BARRIER; 3392 } 3393 } 3394 system.addGreaterThan(begin, beginTarget, 3395 beginAnchor.getMargin(), rangeCheckStrength); 3396 system.addLowerThan(end, endTarget, -endAnchor.getMargin(), rangeCheckStrength); 3397 } 3398 3399 if (parentWrapContent && inBarrier // if we are referenced by a barrier 3400 && !(beginWidget instanceof Barrier || endWidget instanceof Barrier) 3401 && !(endWidget == parent)) { 3402 // ... but not directly constrained by it 3403 // ... then make sure we can hold our own 3404 boundsCheckStrength = SolverVariable.STRENGTH_BARRIER; 3405 rangeCheckStrength = SolverVariable.STRENGTH_BARRIER; 3406 applyBoundsCheck = true; 3407 } 3408 3409 if (applyBoundsCheck) { 3410 if (applyStrongChecks && (!oppositeInChain || oppositeParentWrapContent)) { 3411 int strength = boundsCheckStrength; 3412 if (beginWidget == parent || endWidget == parent) { 3413 strength = SolverVariable.STRENGTH_BARRIER; 3414 } 3415 if (beginWidget instanceof Guideline || endWidget instanceof Guideline) { 3416 strength = SolverVariable.STRENGTH_EQUALITY; 3417 } 3418 if (beginWidget instanceof Barrier || endWidget instanceof Barrier) { 3419 strength = SolverVariable.STRENGTH_EQUALITY; 3420 } 3421 if (oppositeInChain) { 3422 strength = SolverVariable.STRENGTH_EQUALITY; 3423 } 3424 boundsCheckStrength = Math.max(strength, boundsCheckStrength); 3425 } 3426 3427 if (parentWrapContent) { 3428 boundsCheckStrength = Math.min(rangeCheckStrength, boundsCheckStrength); 3429 if (useRatio && !oppositeInChain 3430 && (beginWidget == parent || endWidget == parent)) { 3431 // When using ratio, relax some strength to allow other parts of the system 3432 // to take precedence rather than driving it 3433 boundsCheckStrength = SolverVariable.STRENGTH_HIGHEST; 3434 } 3435 } 3436 system.addEquality(begin, beginTarget, 3437 beginAnchor.getMargin(), boundsCheckStrength); 3438 system.addEquality(end, endTarget, -endAnchor.getMargin(), boundsCheckStrength); 3439 } 3440 3441 if (parentWrapContent) { 3442 int margin = 0; 3443 if (parentMin == beginTarget) { 3444 margin = beginAnchor.getMargin(); 3445 } 3446 if (beginTarget != parentMin) { // already done otherwise 3447 if (FULL_DEBUG) { 3448 System.out.println("<>7 ADDING WRAP GREATER FOR " + getDebugName()); 3449 } 3450 system.addGreaterThan(begin, parentMin, margin, wrapStrength); 3451 } 3452 } 3453 3454 if (parentWrapContent && variableSize && minDimension == 0 && matchMinDimension == 0) { 3455 if (FULL_DEBUG) { 3456 System.out.println("<>8 ADDING WRAP GREATER FOR " + getDebugName()); 3457 } 3458 if (variableSize && matchConstraintDefault == MATCH_CONSTRAINT_RATIO) { 3459 system.addGreaterThan(end, begin, 0, SolverVariable.STRENGTH_FIXED); 3460 } else { 3461 system.addGreaterThan(end, begin, 0, wrapStrength); 3462 } 3463 } 3464 } 3465 3466 if (parentWrapContent && isTerminal) { 3467 int margin = 0; 3468 if (endAnchor.mTarget != null) { 3469 margin = endAnchor.getMargin(); 3470 } 3471 if (endTarget != parentMax) { // if not already applied 3472 if (mOptimizeWrapO && end.isFinalValue && mParent != null) { 3473 ConstraintWidgetContainer container = (ConstraintWidgetContainer) mParent; 3474 if (isHorizontal) { 3475 container.addHorizontalWrapMaxVariable(endAnchor); 3476 } else { 3477 container.addVerticalWrapMaxVariable(endAnchor); 3478 } 3479 return; 3480 } 3481 if (FULL_DEBUG) { 3482 System.out.println("<>9 ADDING WRAP GREATER FOR " + getDebugName()); 3483 } 3484 system.addGreaterThan(parentMax, end, margin, wrapStrength); 3485 } 3486 } 3487 } 3488 3489 /** 3490 * Update the widget from the values generated by the solver 3491 * 3492 * @param system the solver we get the values from. 3493 * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on 3494 */ updateFromSolver(LinearSystem system, boolean optimize)3495 public void updateFromSolver(LinearSystem system, boolean optimize) { 3496 int left = system.getObjectVariableValue(mLeft); 3497 int top = system.getObjectVariableValue(mTop); 3498 int right = system.getObjectVariableValue(mRight); 3499 int bottom = system.getObjectVariableValue(mBottom); 3500 3501 if (optimize && mHorizontalRun != null 3502 && mHorizontalRun.start.resolved && mHorizontalRun.end.resolved) { 3503 left = mHorizontalRun.start.value; 3504 right = mHorizontalRun.end.value; 3505 } 3506 if (optimize && mVerticalRun != null 3507 && mVerticalRun.start.resolved && mVerticalRun.end.resolved) { 3508 top = mVerticalRun.start.value; 3509 bottom = mVerticalRun.end.value; 3510 } 3511 3512 int w = right - left; 3513 int h = bottom - top; 3514 if (w < 0 || h < 0 3515 || left == Integer.MIN_VALUE || left == Integer.MAX_VALUE 3516 || top == Integer.MIN_VALUE || top == Integer.MAX_VALUE 3517 || right == Integer.MIN_VALUE || right == Integer.MAX_VALUE 3518 || bottom == Integer.MIN_VALUE || bottom == Integer.MAX_VALUE) { 3519 left = 0; 3520 top = 0; 3521 right = 0; 3522 bottom = 0; 3523 } 3524 setFrame(left, top, right, bottom); 3525 if (DEBUG) { 3526 System.out.println(" *** UPDATE FROM SOLVER " + this); 3527 } 3528 } 3529 3530 // @TODO: add description copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map)3531 public void copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map) { 3532 // Support for direct resolution 3533 mHorizontalResolution = src.mHorizontalResolution; 3534 mVerticalResolution = src.mVerticalResolution; 3535 3536 mMatchConstraintDefaultWidth = src.mMatchConstraintDefaultWidth; 3537 mMatchConstraintDefaultHeight = src.mMatchConstraintDefaultHeight; 3538 3539 mResolvedMatchConstraintDefault[0] = src.mResolvedMatchConstraintDefault[0]; 3540 mResolvedMatchConstraintDefault[1] = src.mResolvedMatchConstraintDefault[1]; 3541 3542 mMatchConstraintMinWidth = src.mMatchConstraintMinWidth; 3543 mMatchConstraintMaxWidth = src.mMatchConstraintMaxWidth; 3544 mMatchConstraintMinHeight = src.mMatchConstraintMinHeight; 3545 mMatchConstraintMaxHeight = src.mMatchConstraintMaxHeight; 3546 mMatchConstraintPercentHeight = src.mMatchConstraintPercentHeight; 3547 mIsWidthWrapContent = src.mIsWidthWrapContent; 3548 mIsHeightWrapContent = src.mIsHeightWrapContent; 3549 3550 mResolvedDimensionRatioSide = src.mResolvedDimensionRatioSide; 3551 mResolvedDimensionRatio = src.mResolvedDimensionRatio; 3552 3553 mMaxDimension = Arrays.copyOf(src.mMaxDimension, src.mMaxDimension.length); 3554 mCircleConstraintAngle = src.mCircleConstraintAngle; 3555 3556 mHasBaseline = src.mHasBaseline; 3557 mInPlaceholder = src.mInPlaceholder; 3558 3559 // The anchors available on the widget 3560 // note: all anchors should be added to the mAnchors array (see addAnchors()) 3561 3562 mLeft.reset(); 3563 mTop.reset(); 3564 mRight.reset(); 3565 mBottom.reset(); 3566 mBaseline.reset(); 3567 mCenterX.reset(); 3568 mCenterY.reset(); 3569 mCenter.reset(); 3570 mListDimensionBehaviors = Arrays.copyOf(mListDimensionBehaviors, 2); 3571 mParent = (mParent == null) ? null : map.get(src.mParent); 3572 3573 mWidth = src.mWidth; 3574 mHeight = src.mHeight; 3575 mDimensionRatio = src.mDimensionRatio; 3576 mDimensionRatioSide = src.mDimensionRatioSide; 3577 3578 mX = src.mX; 3579 mY = src.mY; 3580 mRelX = src.mRelX; 3581 mRelY = src.mRelY; 3582 3583 mOffsetX = src.mOffsetX; 3584 mOffsetY = src.mOffsetY; 3585 3586 mBaselineDistance = src.mBaselineDistance; 3587 mMinWidth = src.mMinWidth; 3588 mMinHeight = src.mMinHeight; 3589 3590 mHorizontalBiasPercent = src.mHorizontalBiasPercent; 3591 mVerticalBiasPercent = src.mVerticalBiasPercent; 3592 3593 mCompanionWidget = src.mCompanionWidget; 3594 mContainerItemSkip = src.mContainerItemSkip; 3595 mVisibility = src.mVisibility; 3596 mAnimated = src.mAnimated; 3597 mDebugName = src.mDebugName; 3598 mType = src.mType; 3599 3600 mDistToTop = src.mDistToTop; 3601 mDistToLeft = src.mDistToLeft; 3602 mDistToRight = src.mDistToRight; 3603 mDistToBottom = src.mDistToBottom; 3604 mLeftHasCentered = src.mLeftHasCentered; 3605 mRightHasCentered = src.mRightHasCentered; 3606 3607 mTopHasCentered = src.mTopHasCentered; 3608 mBottomHasCentered = src.mBottomHasCentered; 3609 3610 mHorizontalWrapVisited = src.mHorizontalWrapVisited; 3611 mVerticalWrapVisited = src.mVerticalWrapVisited; 3612 3613 mHorizontalChainStyle = src.mHorizontalChainStyle; 3614 mVerticalChainStyle = src.mVerticalChainStyle; 3615 mHorizontalChainFixedPosition = src.mHorizontalChainFixedPosition; 3616 mVerticalChainFixedPosition = src.mVerticalChainFixedPosition; 3617 mWeight[0] = src.mWeight[0]; 3618 mWeight[1] = src.mWeight[1]; 3619 3620 mListNextMatchConstraintsWidget[0] = src.mListNextMatchConstraintsWidget[0]; 3621 mListNextMatchConstraintsWidget[1] = src.mListNextMatchConstraintsWidget[1]; 3622 3623 mNextChainWidget[0] = src.mNextChainWidget[0]; 3624 mNextChainWidget[1] = src.mNextChainWidget[1]; 3625 3626 mHorizontalNextWidget = (src.mHorizontalNextWidget == null) 3627 ? null : map.get(src.mHorizontalNextWidget); 3628 mVerticalNextWidget = (src.mVerticalNextWidget == null) 3629 ? null : map.get(src.mVerticalNextWidget); 3630 } 3631 3632 // @TODO: add description updateFromRuns(boolean updateHorizontal, boolean updateVertical)3633 public void updateFromRuns(boolean updateHorizontal, boolean updateVertical) { 3634 updateHorizontal &= mHorizontalRun.isResolved(); 3635 updateVertical &= mVerticalRun.isResolved(); 3636 int left = mHorizontalRun.start.value; 3637 int top = mVerticalRun.start.value; 3638 int right = mHorizontalRun.end.value; 3639 int bottom = mVerticalRun.end.value; 3640 int w = right - left; 3641 int h = bottom - top; 3642 if (w < 0 || h < 0 3643 || left == Integer.MIN_VALUE || left == Integer.MAX_VALUE 3644 || top == Integer.MIN_VALUE || top == Integer.MAX_VALUE 3645 || right == Integer.MIN_VALUE || right == Integer.MAX_VALUE 3646 || bottom == Integer.MIN_VALUE || bottom == Integer.MAX_VALUE) { 3647 left = 0; 3648 top = 0; 3649 right = 0; 3650 bottom = 0; 3651 } 3652 3653 w = right - left; 3654 h = bottom - top; 3655 3656 if (updateHorizontal) { 3657 mX = left; 3658 } 3659 if (updateVertical) { 3660 mY = top; 3661 } 3662 3663 if (mVisibility == ConstraintWidget.GONE) { 3664 mWidth = 0; 3665 mHeight = 0; 3666 return; 3667 } 3668 3669 // correct dimensional instability caused by rounding errors 3670 if (updateHorizontal) { 3671 if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] 3672 == DimensionBehaviour.FIXED && w < mWidth) { 3673 w = mWidth; 3674 } 3675 mWidth = w; 3676 if (mWidth < mMinWidth) { 3677 mWidth = mMinWidth; 3678 } 3679 } 3680 3681 if (updateVertical) { 3682 if (mListDimensionBehaviors[DIMENSION_VERTICAL] 3683 == DimensionBehaviour.FIXED && h < mHeight) { 3684 h = mHeight; 3685 } 3686 mHeight = h; 3687 if (mHeight < mMinHeight) { 3688 mHeight = mMinHeight; 3689 } 3690 } 3691 3692 } 3693 3694 // @TODO: add description addChildrenToSolverByDependency(ConstraintWidgetContainer container, LinearSystem system, HashSet<ConstraintWidget> widgets, int orientation, boolean addSelf)3695 public void addChildrenToSolverByDependency(ConstraintWidgetContainer container, 3696 LinearSystem system, 3697 HashSet<ConstraintWidget> widgets, 3698 int orientation, 3699 boolean addSelf) { 3700 if (addSelf) { 3701 if (!widgets.contains(this)) { 3702 return; 3703 } 3704 Optimizer.checkMatchParent(container, system, this); 3705 widgets.remove(this); 3706 addToSolver(system, container.optimizeFor(Optimizer.OPTIMIZATION_GRAPH)); 3707 } 3708 if (orientation == HORIZONTAL) { 3709 HashSet<ConstraintAnchor> dependents = mLeft.getDependents(); 3710 if (dependents != null) { 3711 for (ConstraintAnchor anchor : dependents) { 3712 anchor.mOwner.addChildrenToSolverByDependency(container, 3713 system, widgets, orientation, true); 3714 } 3715 } 3716 dependents = mRight.getDependents(); 3717 if (dependents != null) { 3718 for (ConstraintAnchor anchor : dependents) { 3719 anchor.mOwner.addChildrenToSolverByDependency(container, 3720 system, widgets, orientation, true); 3721 } 3722 } 3723 } else { 3724 HashSet<ConstraintAnchor> dependents = mTop.getDependents(); 3725 if (dependents != null) { 3726 for (ConstraintAnchor anchor : dependents) { 3727 anchor.mOwner.addChildrenToSolverByDependency(container, 3728 system, widgets, orientation, true); 3729 } 3730 } 3731 dependents = mBottom.getDependents(); 3732 if (dependents != null) { 3733 for (ConstraintAnchor anchor : dependents) { 3734 anchor.mOwner.addChildrenToSolverByDependency(container, 3735 system, widgets, orientation, true); 3736 } 3737 } 3738 dependents = mBaseline.getDependents(); 3739 if (dependents != null) { 3740 for (ConstraintAnchor anchor : dependents) { 3741 anchor.mOwner.addChildrenToSolverByDependency(container, 3742 system, widgets, orientation, true); 3743 } 3744 } 3745 } 3746 // horizontal 3747 } 3748 3749 // @TODO: add description getSceneString(StringBuilder ret)3750 public void getSceneString(StringBuilder ret) { 3751 3752 ret.append(" " + stringId + ":{\n"); 3753 ret.append(" actualWidth:" + mWidth); 3754 ret.append("\n"); 3755 ret.append(" actualHeight:" + mHeight); 3756 ret.append("\n"); 3757 ret.append(" actualLeft:" + mX); 3758 ret.append("\n"); 3759 ret.append(" actualTop:" + mY); 3760 ret.append("\n"); 3761 getSceneString(ret, "left", mLeft); 3762 getSceneString(ret, "top", mTop); 3763 getSceneString(ret, "right", mRight); 3764 getSceneString(ret, "bottom", mBottom); 3765 getSceneString(ret, "baseline", mBaseline); 3766 getSceneString(ret, "centerX", mCenterX); 3767 getSceneString(ret, "centerY", mCenterY); 3768 getSceneString(ret, " width", 3769 mWidth, 3770 mMinWidth, 3771 mMaxDimension[HORIZONTAL], 3772 mWidthOverride, 3773 mMatchConstraintMinWidth, 3774 mMatchConstraintDefaultWidth, 3775 mMatchConstraintPercentWidth, 3776 mListDimensionBehaviors[HORIZONTAL], 3777 mWeight[DIMENSION_HORIZONTAL] 3778 ); 3779 3780 getSceneString(ret, " height", 3781 mHeight, 3782 mMinHeight, 3783 mMaxDimension[VERTICAL], 3784 mHeightOverride, 3785 mMatchConstraintMinHeight, 3786 mMatchConstraintDefaultHeight, 3787 mMatchConstraintPercentHeight, 3788 mListDimensionBehaviors[VERTICAL], 3789 mWeight[DIMENSION_VERTICAL]); 3790 serializeDimensionRatio(ret, " dimensionRatio", mDimensionRatio, mDimensionRatioSide); 3791 serializeAttribute(ret, " horizontalBias", mHorizontalBiasPercent, DEFAULT_BIAS); 3792 serializeAttribute(ret, " verticalBias", mVerticalBiasPercent, DEFAULT_BIAS); 3793 serializeAttribute(ret, " horizontalChainStyle", mHorizontalChainStyle, CHAIN_SPREAD); 3794 serializeAttribute(ret, " verticalChainStyle", mVerticalChainStyle, CHAIN_SPREAD); 3795 3796 ret.append(" }"); 3797 3798 } 3799 getSceneString(StringBuilder ret, String type, int size, int min, int max, @SuppressWarnings("unused") int override, int matchConstraintMin, int matchConstraintDefault, float matchConstraintPercent, DimensionBehaviour behavior, @SuppressWarnings("unused") float weight)3800 private void getSceneString(StringBuilder ret, String type, int size, 3801 int min, int max, 3802 @SuppressWarnings("unused") int override, 3803 int matchConstraintMin, int matchConstraintDefault, 3804 float matchConstraintPercent, 3805 DimensionBehaviour behavior, 3806 @SuppressWarnings("unused") float weight) { 3807 ret.append(type); 3808 ret.append(" : {\n"); 3809 serializeAttribute(ret, " behavior", behavior.toString(), 3810 DimensionBehaviour.FIXED.toString()); 3811 serializeAttribute(ret, " size", size, 0); 3812 serializeAttribute(ret, " min", min, 0); 3813 serializeAttribute(ret, " max", max, Integer.MAX_VALUE); 3814 serializeAttribute(ret, " matchMin", matchConstraintMin, 0); 3815 serializeAttribute(ret, " matchDef", matchConstraintDefault, MATCH_CONSTRAINT_SPREAD); 3816 serializeAttribute(ret, " matchPercent", matchConstraintPercent, 1); 3817 ret.append(" },\n"); 3818 } 3819 getSceneString(StringBuilder ret, String side, ConstraintAnchor a)3820 private void getSceneString(StringBuilder ret, String side, ConstraintAnchor a) { 3821 if (a.mTarget == null) { 3822 return; 3823 } 3824 ret.append(" "); 3825 ret.append(side); 3826 ret.append(" : [ '"); 3827 ret.append(a.mTarget); 3828 ret.append("'"); 3829 if (a.mGoneMargin != Integer.MIN_VALUE || a.mMargin != 0) { 3830 ret.append(","); 3831 ret.append(a.mMargin); 3832 if (a.mGoneMargin != Integer.MIN_VALUE) { 3833 ret.append(","); 3834 ret.append(a.mGoneMargin); 3835 ret.append(","); 3836 } 3837 } 3838 ret.append(" ] ,\n"); 3839 } 3840 } 3841