1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.constraintlayout.core.state; 18 19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.CHAIN_PACKED; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.CHAIN_SPREAD; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.CHAIN_SPREAD_INSIDE; 22 23 import androidx.constraintlayout.core.state.helpers.AlignHorizontallyReference; 24 import androidx.constraintlayout.core.state.helpers.AlignVerticallyReference; 25 import androidx.constraintlayout.core.state.helpers.BarrierReference; 26 import androidx.constraintlayout.core.state.helpers.FlowReference; 27 import androidx.constraintlayout.core.state.helpers.GridReference; 28 import androidx.constraintlayout.core.state.helpers.GuidelineReference; 29 import androidx.constraintlayout.core.state.helpers.HorizontalChainReference; 30 import androidx.constraintlayout.core.state.helpers.VerticalChainReference; 31 import androidx.constraintlayout.core.widgets.ConstraintWidget; 32 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer; 33 import androidx.constraintlayout.core.widgets.HelperWidget; 34 35 import org.jspecify.annotations.NonNull; 36 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.Map; 40 41 /** 42 * Represents a full state of a ConstraintLayout 43 */ 44 public class State { 45 private CorePixelDp mDpToPixel; 46 private boolean mIsLtr = true; 47 protected HashMap<Object, Reference> mReferences = new HashMap<>(); 48 protected HashMap<Object, HelperReference> mHelperReferences = new HashMap<>(); 49 HashMap<String, ArrayList<String>> mTags = new HashMap<>(); 50 51 static final int UNKNOWN = -1; 52 static final int CONSTRAINT_SPREAD = 0; 53 static final int CONSTRAINT_WRAP = 1; 54 static final int CONSTRAINT_RATIO = 2; 55 56 public static final Integer PARENT = 0; 57 58 public final ConstraintReference mParent = new ConstraintReference(this); 59 60 public enum Constraint { 61 LEFT_TO_LEFT, 62 LEFT_TO_RIGHT, 63 RIGHT_TO_LEFT, 64 RIGHT_TO_RIGHT, 65 START_TO_START, 66 START_TO_END, 67 END_TO_START, 68 END_TO_END, 69 TOP_TO_TOP, 70 TOP_TO_BOTTOM, 71 TOP_TO_BASELINE, 72 BOTTOM_TO_TOP, 73 BOTTOM_TO_BOTTOM, 74 BOTTOM_TO_BASELINE, 75 BASELINE_TO_BASELINE, 76 BASELINE_TO_TOP, 77 BASELINE_TO_BOTTOM, 78 CENTER_HORIZONTALLY, 79 CENTER_VERTICALLY, 80 CIRCULAR_CONSTRAINT 81 } 82 83 public enum Direction { 84 LEFT, 85 RIGHT, 86 START, 87 END, 88 TOP, 89 BOTTOM 90 } 91 92 public enum Helper { 93 HORIZONTAL_CHAIN, 94 VERTICAL_CHAIN, 95 ALIGN_HORIZONTALLY, 96 ALIGN_VERTICALLY, 97 BARRIER, 98 LAYER, 99 HORIZONTAL_FLOW, 100 VERTICAL_FLOW, 101 GRID, 102 ROW, 103 COLUMN, 104 FLOW 105 } 106 107 public enum Chain { 108 SPREAD, 109 SPREAD_INSIDE, 110 PACKED; 111 112 public static Map<String, Chain> chainMap = new HashMap<>(); 113 public static Map<String, Integer> valueMap = new HashMap<>(); 114 static { 115 chainMap.put("packed", PACKED); 116 chainMap.put("spread_inside", SPREAD_INSIDE); 117 chainMap.put("spread", SPREAD); 118 119 valueMap.put("packed", CHAIN_PACKED); 120 valueMap.put("spread_inside", CHAIN_SPREAD_INSIDE); 121 valueMap.put("spread", CHAIN_SPREAD); 122 } 123 124 /** 125 * Get the Enum value with a String 126 * @param str a String representation of a Enum value 127 * @return a Enum value 128 */ getValueByString(String str)129 public static int getValueByString(String str) { 130 if (valueMap.containsKey(str)) { 131 return valueMap.get(str); 132 } 133 return UNKNOWN; 134 } 135 136 /** 137 * Get the actual int value with a String 138 * @param str a String representation of a Enum value 139 * @return an actual int value 140 */ getChainByString(String str)141 public static Chain getChainByString(String str) { 142 if (chainMap.containsKey(str)) { 143 return chainMap.get(str); 144 } 145 return null; 146 } 147 148 } 149 150 public enum Wrap { 151 NONE, 152 CHAIN, 153 ALIGNED; 154 public static Map<String, Wrap> wrapMap = new HashMap<>(); 155 public static Map<String, Integer> valueMap = new HashMap<>(); 156 static { 157 wrapMap.put("none", NONE); 158 wrapMap.put("chain", CHAIN); 159 wrapMap.put("aligned", ALIGNED); 160 161 valueMap.put("none", 0); 162 valueMap.put("chain", 3); // Corresponds to CHAIN_NEW 163 valueMap.put("aligned", 2); 164 } 165 166 /** 167 * Get the actual int value with a String 168 * @param str a String representation of a Enum value 169 * @return a actual int value 170 */ getValueByString(String str)171 public static int getValueByString(String str) { 172 if (valueMap.containsKey(str)) { 173 return valueMap.get(str); 174 } 175 return UNKNOWN; 176 } 177 178 /** 179 * Get the Enum value with a String 180 * @param str a String representation of a Enum value 181 * @return a Enum value 182 */ getChainByString(String str)183 public static Wrap getChainByString(String str) { 184 if (wrapMap.containsKey(str)) { 185 return wrapMap.get(str); 186 } 187 return null; 188 } 189 } 190 State()191 public State() { 192 mParent.setKey(PARENT); 193 mReferences.put(PARENT, mParent); 194 } 195 getDpToPixel()196 CorePixelDp getDpToPixel() { 197 return mDpToPixel; 198 } 199 200 /** 201 * Set the function that converts dp to Pixels 202 */ setDpToPixel(CorePixelDp dpToPixel)203 public void setDpToPixel(CorePixelDp dpToPixel) { 204 this.mDpToPixel = dpToPixel; 205 } 206 207 /** 208 * Set whether the layout direction is left to right (Ltr). 209 * 210 * @deprecated For consistency, use {@link #setRtl(boolean)} instead. 211 */ 212 @Deprecated setLtr(boolean isLtr)213 public void setLtr(boolean isLtr) { 214 mIsLtr = isLtr; 215 } 216 217 /** 218 * Returns true if layout direction is left to right. False for right to left. 219 * 220 * @deprecated For consistency, use {@link #isRtl()} instead. 221 */ 222 @Deprecated isLtr()223 public boolean isLtr() { 224 return mIsLtr; 225 } 226 227 /** 228 * Set whether the layout direction is right to left (Rtl). 229 */ setRtl(boolean isRtl)230 public void setRtl(boolean isRtl) { 231 mIsLtr = !isRtl; 232 } 233 234 /** 235 * Returns true if layout direction is right to left. False for left to right. 236 */ isRtl()237 public boolean isRtl() { 238 return !mIsLtr; 239 } 240 241 /** 242 * Clear the state 243 */ reset()244 public void reset() { 245 for (Object ref : mReferences.keySet()) { 246 mReferences.get(ref).getConstraintWidget().reset(); 247 } 248 mReferences.clear(); 249 mReferences.put(PARENT, mParent); 250 mHelperReferences.clear(); 251 mTags.clear(); 252 mBaselineNeeded.clear(); 253 mDirtyBaselineNeededWidgets = true; 254 } 255 256 /** 257 * Implements a conversion function for values, returning int. 258 * This can be used in case values (e.g. margins) are represented 259 * via an object, not directly an int. 260 * 261 * @param value the object to convert from 262 */ convertDimension(Object value)263 public int convertDimension(Object value) { 264 if (value instanceof Float) { 265 return Math.round((Float) value); 266 } 267 if (value instanceof Integer) { 268 return (Integer) value; 269 } 270 return 0; 271 } 272 273 /** 274 * Create a new reference given a key. 275 */ createConstraintReference(Object key)276 public ConstraintReference createConstraintReference(Object key) { 277 return new ConstraintReference(this); 278 } 279 280 // @TODO: add description sameFixedWidth(int width)281 public boolean sameFixedWidth(int width) { 282 return mParent.getWidth().equalsFixedValue(width); 283 } 284 285 // @TODO: add description sameFixedHeight(int height)286 public boolean sameFixedHeight(int height) { 287 return mParent.getHeight().equalsFixedValue(height); 288 } 289 290 // @TODO: add description width(Dimension dimension)291 public State width(Dimension dimension) { 292 return setWidth(dimension); 293 } 294 295 // @TODO: add description height(Dimension dimension)296 public State height(Dimension dimension) { 297 return setHeight(dimension); 298 } 299 300 // @TODO: add description setWidth(Dimension dimension)301 public State setWidth(Dimension dimension) { 302 mParent.setWidth(dimension); 303 return this; 304 } 305 306 // @TODO: add description setHeight(Dimension dimension)307 public State setHeight(Dimension dimension) { 308 mParent.setHeight(dimension); 309 return this; 310 } 311 reference(Object key)312 Reference reference(Object key) { 313 return mReferences.get(key); 314 } 315 316 // @TODO: add description constraints(Object key)317 public ConstraintReference constraints(Object key) { 318 Reference reference = mReferences.get(key); 319 320 if (reference == null) { 321 reference = createConstraintReference(key); 322 mReferences.put(key, reference); 323 reference.setKey(key); 324 } 325 if (reference instanceof ConstraintReference) { 326 return (ConstraintReference) reference; 327 } 328 return null; 329 } 330 331 private int mNumHelpers = 0; 332 createHelperKey()333 private String createHelperKey() { 334 return "__HELPER_KEY_" + mNumHelpers++ + "__"; 335 } 336 337 // @TODO: add description helper(Object key, State.Helper type)338 public HelperReference helper(Object key, State.Helper type) { 339 if (key == null) { 340 key = createHelperKey(); 341 } 342 HelperReference reference = mHelperReferences.get(key); 343 if (reference == null) { 344 switch (type) { 345 case HORIZONTAL_CHAIN: { 346 reference = new HorizontalChainReference(this); 347 } 348 break; 349 case VERTICAL_CHAIN: { 350 reference = new VerticalChainReference(this); 351 } 352 break; 353 case ALIGN_HORIZONTALLY: { 354 reference = new AlignHorizontallyReference(this); 355 } 356 break; 357 case ALIGN_VERTICALLY: { 358 reference = new AlignVerticallyReference(this); 359 } 360 break; 361 case BARRIER: { 362 reference = new BarrierReference(this); 363 } 364 break; 365 case VERTICAL_FLOW: 366 case HORIZONTAL_FLOW: { 367 reference = new FlowReference(this, type); 368 } 369 break; 370 case GRID: 371 case ROW: 372 case COLUMN: { 373 reference = new GridReference(this, type); 374 } 375 break; 376 default: { 377 reference = new HelperReference(this, type); 378 } 379 } 380 reference.setKey(key); 381 mHelperReferences.put(key, reference); 382 } 383 return reference; 384 } 385 386 // @TODO: add description horizontalGuideline(Object key)387 public GuidelineReference horizontalGuideline(Object key) { 388 return guideline(key, ConstraintWidget.HORIZONTAL); 389 } 390 391 // @TODO: add description verticalGuideline(Object key)392 public GuidelineReference verticalGuideline(Object key) { 393 return guideline(key, ConstraintWidget.VERTICAL); 394 } 395 396 // @TODO: add description guideline(Object key, int orientation)397 public GuidelineReference guideline(Object key, int orientation) { 398 ConstraintReference reference = constraints(key); 399 if (reference.getFacade() == null 400 || !(reference.getFacade() instanceof GuidelineReference)) { 401 GuidelineReference guidelineReference = new GuidelineReference(this); 402 guidelineReference.setOrientation(orientation); 403 guidelineReference.setKey(key); 404 reference.setFacade(guidelineReference); 405 } 406 return (GuidelineReference) reference.getFacade(); 407 } 408 409 // @TODO: add description barrier(Object key, Direction direction)410 public BarrierReference barrier(Object key, Direction direction) { 411 ConstraintReference reference = constraints(key); 412 if (reference.getFacade() == null || !(reference.getFacade() instanceof BarrierReference)) { 413 BarrierReference barrierReference = new BarrierReference(this); 414 barrierReference.setBarrierDirection(direction); 415 reference.setFacade(barrierReference); 416 } 417 return (BarrierReference) reference.getFacade(); 418 } 419 420 /** 421 * Get a Grid reference 422 * 423 * @param key name of the reference object 424 * @param gridType type of Grid pattern - Grid, Row, or Column 425 * @return a GridReference object 426 */ getGrid(@onNull Object key, @NonNull String gridType)427 public @NonNull GridReference getGrid(@NonNull Object key, @NonNull String gridType) { 428 ConstraintReference reference = constraints(key); 429 if (reference.getFacade() == null || !(reference.getFacade() instanceof GridReference)) { 430 State.Helper Type = Helper.GRID; 431 if (gridType.charAt(0) == 'r') { 432 Type = Helper.ROW; 433 } else if (gridType.charAt(0) == 'c') { 434 Type = Helper.COLUMN; 435 } 436 GridReference gridReference = new GridReference(this, Type); 437 reference.setFacade(gridReference); 438 } 439 return (GridReference) reference.getFacade(); 440 } 441 442 /** 443 * Gets a reference to a Flow object. Creating it if needed. 444 * @param key id of the reference 445 * @param vertical is it a vertical or horizontal flow 446 * @return a FlowReference 447 */ getFlow(Object key, boolean vertical)448 public FlowReference getFlow(Object key, boolean vertical) { 449 ConstraintReference reference = constraints(key); 450 if (reference.getFacade() == null || !(reference.getFacade() instanceof FlowReference)) { 451 FlowReference flowReference = 452 vertical ? new FlowReference(this, Helper.VERTICAL_FLOW) 453 : new FlowReference(this, Helper.HORIZONTAL_FLOW); 454 455 reference.setFacade(flowReference); 456 } 457 return (FlowReference) reference.getFacade(); 458 } 459 460 // @TODO: add description verticalChain()461 public VerticalChainReference verticalChain() { 462 return (VerticalChainReference) helper(null, Helper.VERTICAL_CHAIN); 463 } 464 465 // @TODO: add description verticalChain(Object... references)466 public VerticalChainReference verticalChain(Object... references) { 467 VerticalChainReference reference = 468 (VerticalChainReference) helper(null, State.Helper.VERTICAL_CHAIN); 469 reference.add(references); 470 return reference; 471 } 472 473 // @TODO: add description horizontalChain()474 public HorizontalChainReference horizontalChain() { 475 return (HorizontalChainReference) helper(null, Helper.HORIZONTAL_CHAIN); 476 } 477 478 // @TODO: add description horizontalChain(Object... references)479 public HorizontalChainReference horizontalChain(Object... references) { 480 HorizontalChainReference reference = 481 (HorizontalChainReference) helper(null, Helper.HORIZONTAL_CHAIN); 482 reference.add(references); 483 return reference; 484 } 485 486 /** 487 * Get a VerticalFlowReference 488 * 489 * @return a VerticalFlowReference 490 */ getVerticalFlow()491 public FlowReference getVerticalFlow() { 492 return (FlowReference) helper(null, Helper.VERTICAL_FLOW); 493 } 494 495 /** 496 * Get a VerticalFlowReference and add it to references 497 * 498 * @param references where we add the VerticalFlowReference 499 * @return a VerticalFlowReference 500 */ getVerticalFlow(Object... references)501 public FlowReference getVerticalFlow(Object... references) { 502 FlowReference reference = 503 (FlowReference) helper(null, State.Helper.VERTICAL_FLOW); 504 reference.add(references); 505 return reference; 506 } 507 508 /** 509 * Get a HorizontalFlowReference 510 * 511 * @return a HorizontalFlowReference 512 */ getHorizontalFlow()513 public FlowReference getHorizontalFlow() { 514 return (FlowReference) helper(null, Helper.HORIZONTAL_FLOW); 515 } 516 517 /** 518 * Get a HorizontalFlowReference and add it to references 519 * 520 * @param references references where we the HorizontalFlowReference 521 * @return a HorizontalFlowReference 522 */ getHorizontalFlow(Object... references)523 public FlowReference getHorizontalFlow(Object... references) { 524 FlowReference reference = 525 (FlowReference) helper(null, Helper.HORIZONTAL_FLOW); 526 reference.add(references); 527 return reference; 528 } 529 530 // @TODO: add description centerHorizontally(Object... references)531 public AlignHorizontallyReference centerHorizontally(Object... references) { 532 AlignHorizontallyReference reference = 533 (AlignHorizontallyReference) helper(null, Helper.ALIGN_HORIZONTALLY); 534 reference.add(references); 535 return reference; 536 } 537 538 // @TODO: add description centerVertically(Object... references)539 public AlignVerticallyReference centerVertically(Object... references) { 540 AlignVerticallyReference reference = 541 (AlignVerticallyReference) helper(null, Helper.ALIGN_VERTICALLY); 542 reference.add(references); 543 return reference; 544 } 545 546 // @TODO: add description directMapping()547 public void directMapping() { 548 for (Object key : mReferences.keySet()) { 549 Reference ref = constraints(key); 550 if (!(ref instanceof ConstraintReference)) { 551 continue; 552 } 553 ConstraintReference reference = (ConstraintReference) ref; 554 reference.setView(key); 555 } 556 } 557 558 // @TODO: add description map(Object key, Object view)559 public void map(Object key, Object view) { 560 ConstraintReference ref = constraints(key); 561 if (ref != null) { 562 ref.setView(view); 563 } 564 } 565 566 // @TODO: add description setTag(String key, String tag)567 public void setTag(String key, String tag) { 568 Reference ref = constraints(key); 569 if (ref instanceof ConstraintReference) { 570 ConstraintReference reference = (ConstraintReference) ref; 571 reference.setTag(tag); 572 ArrayList<String> list = null; 573 if (!mTags.containsKey(tag)) { 574 list = new ArrayList<>(); 575 mTags.put(tag, list); 576 } else { 577 list = mTags.get(tag); 578 } 579 list.add(key); 580 } 581 } 582 583 // @TODO: add description getIdsForTag(String tag)584 public ArrayList<String> getIdsForTag(String tag) { 585 if (mTags.containsKey(tag)) { 586 return mTags.get(tag); 587 } 588 return null; 589 } 590 591 // @TODO: add description apply(ConstraintWidgetContainer container)592 public void apply(ConstraintWidgetContainer container) { 593 container.removeAllChildren(); 594 mParent.getWidth().apply(this, container, ConstraintWidget.HORIZONTAL); 595 mParent.getHeight().apply(this, container, ConstraintWidget.VERTICAL); 596 // add helper references 597 for (Object key : mHelperReferences.keySet()) { 598 HelperReference reference = mHelperReferences.get(key); 599 HelperWidget helperWidget = reference.getHelperWidget(); 600 if (helperWidget != null) { 601 Reference constraintReference = mReferences.get(key); 602 if (constraintReference == null) { 603 constraintReference = constraints(key); 604 } 605 constraintReference.setConstraintWidget(helperWidget); 606 } 607 } 608 for (Object key : mReferences.keySet()) { 609 Reference reference = mReferences.get(key); 610 if (reference != mParent && reference.getFacade() instanceof HelperReference) { 611 HelperWidget helperWidget = 612 ((HelperReference) reference.getFacade()).getHelperWidget(); 613 if (helperWidget != null) { 614 Reference constraintReference = mReferences.get(key); 615 if (constraintReference == null) { 616 constraintReference = constraints(key); 617 } 618 constraintReference.setConstraintWidget(helperWidget); 619 } 620 } 621 } 622 for (Object key : mReferences.keySet()) { 623 Reference reference = mReferences.get(key); 624 if (reference != mParent) { 625 ConstraintWidget widget = reference.getConstraintWidget(); 626 widget.setDebugName(reference.getKey().toString()); 627 widget.setParent(null); 628 if (reference.getFacade() instanceof GuidelineReference) { 629 // we apply Guidelines first to correctly setup their ConstraintWidget. 630 reference.apply(); 631 } 632 container.add(widget); 633 } else { 634 reference.setConstraintWidget(container); 635 } 636 } 637 for (Object key : mHelperReferences.keySet()) { 638 // We need this pass to apply chains properly 639 HelperReference reference = mHelperReferences.get(key); 640 HelperWidget helperWidget = reference.getHelperWidget(); 641 if (helperWidget != null) { 642 for (Object keyRef : reference.mReferences) { 643 Reference constraintReference = mReferences.get(keyRef); 644 reference.getHelperWidget().add(constraintReference.getConstraintWidget()); 645 } 646 reference.apply(); 647 } else { 648 reference.apply(); 649 } 650 } 651 for (Object key : mReferences.keySet()) { 652 Reference reference = mReferences.get(key); 653 if (reference != mParent && reference.getFacade() instanceof HelperReference) { 654 HelperReference helperReference = (HelperReference) reference.getFacade(); 655 HelperWidget helperWidget = helperReference.getHelperWidget(); 656 if (helperWidget != null) { 657 for (Object keyRef : helperReference.mReferences) { 658 Reference constraintReference = mReferences.get(keyRef); 659 if (constraintReference != null) { 660 helperWidget.add(constraintReference.getConstraintWidget()); 661 } else if (keyRef instanceof Reference) { 662 helperWidget.add(((Reference) keyRef).getConstraintWidget()); 663 } else { 664 System.out.println("couldn't find reference for " + keyRef); 665 } 666 } 667 reference.apply(); 668 } 669 } 670 } 671 for (Object key : mReferences.keySet()) { 672 Reference reference = mReferences.get(key); 673 reference.apply(); 674 ConstraintWidget widget = reference.getConstraintWidget(); 675 if (widget != null && key != null) { 676 widget.stringId = key.toString(); 677 } 678 } 679 } 680 681 // ================= add baseline code================================ 682 ArrayList<Object> mBaselineNeeded = new ArrayList<>(); 683 ArrayList<ConstraintWidget> mBaselineNeededWidgets = new ArrayList<>(); 684 boolean mDirtyBaselineNeededWidgets = true; 685 686 /** 687 * Baseline is needed for this object 688 */ baselineNeededFor(Object id)689 public void baselineNeededFor(Object id) { 690 mBaselineNeeded.add(id); 691 mDirtyBaselineNeededWidgets = true; 692 } 693 694 /** 695 * Does this constraintWidget need a baseline 696 * 697 * @return true if the constraintWidget needs a baseline 698 */ isBaselineNeeded(ConstraintWidget constraintWidget)699 public boolean isBaselineNeeded(ConstraintWidget constraintWidget) { 700 if (mDirtyBaselineNeededWidgets) { 701 mBaselineNeededWidgets.clear(); 702 for (Object id : mBaselineNeeded) { 703 ConstraintWidget widget = mReferences.get(id).getConstraintWidget(); 704 if (widget != null) mBaselineNeededWidgets.add(widget); 705 } 706 707 mDirtyBaselineNeededWidgets = false; 708 } 709 return mBaselineNeededWidgets.contains(constraintWidget); 710 } 711 } 712