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.widgets.analyzer; 18 19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD; 22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP; 23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL; 24 25 import androidx.constraintlayout.core.LinearSystem; 26 import androidx.constraintlayout.core.widgets.Barrier; 27 import androidx.constraintlayout.core.widgets.ConstraintAnchor; 28 import androidx.constraintlayout.core.widgets.ConstraintWidget; 29 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer; 30 import androidx.constraintlayout.core.widgets.Guideline; 31 import androidx.constraintlayout.core.widgets.Helper; 32 import androidx.constraintlayout.core.widgets.Optimizer; 33 import androidx.constraintlayout.core.widgets.VirtualLayout; 34 35 import java.util.ArrayList; 36 37 /** 38 * Implements basic measure for linear resolution 39 */ 40 public class BasicMeasure { 41 42 private static final boolean DEBUG = false; 43 private static final boolean DO_NOT_USE = false; 44 private static final int MODE_SHIFT = 30; 45 public static final int UNSPECIFIED = 0; 46 public static final int EXACTLY = 1 << MODE_SHIFT; 47 public static final int AT_MOST = 2 << MODE_SHIFT; 48 49 public static final int MATCH_PARENT = -1; 50 public static final int WRAP_CONTENT = -2; 51 public static final int FIXED = -3; 52 53 private final ArrayList<ConstraintWidget> mVariableDimensionsWidgets = new ArrayList<>(); 54 private Measure mMeasure = new Measure(); 55 56 // @TODO: add description updateHierarchy(ConstraintWidgetContainer layout)57 public void updateHierarchy(ConstraintWidgetContainer layout) { 58 mVariableDimensionsWidgets.clear(); 59 final int childCount = layout.mChildren.size(); 60 for (int i = 0; i < childCount; i++) { 61 ConstraintWidget widget = layout.mChildren.get(i); 62 if (widget.getHorizontalDimensionBehaviour() 63 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 64 || widget.getVerticalDimensionBehaviour() 65 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { 66 mVariableDimensionsWidgets.add(widget); 67 } 68 } 69 layout.invalidateGraph(); 70 } 71 72 private ConstraintWidgetContainer mConstraintWidgetContainer; 73 BasicMeasure(ConstraintWidgetContainer constraintWidgetContainer)74 public BasicMeasure(ConstraintWidgetContainer constraintWidgetContainer) { 75 this.mConstraintWidgetContainer = constraintWidgetContainer; 76 } 77 measureChildren(ConstraintWidgetContainer layout)78 private void measureChildren(ConstraintWidgetContainer layout) { 79 final int childCount = layout.mChildren.size(); 80 boolean optimize = layout.optimizeFor(Optimizer.OPTIMIZATION_GRAPH); 81 Measurer measurer = layout.getMeasurer(); 82 for (int i = 0; i < childCount; i++) { 83 ConstraintWidget child = layout.mChildren.get(i); 84 if (child instanceof Guideline) { 85 continue; 86 } 87 if (child instanceof Barrier) { 88 continue; 89 } 90 if (child.isInVirtualLayout()) { 91 continue; 92 } 93 94 if (optimize && child.mHorizontalRun != null && child.mVerticalRun != null 95 && child.mHorizontalRun.mDimension.resolved 96 && child.mVerticalRun.mDimension.resolved) { 97 continue; 98 } 99 100 ConstraintWidget.DimensionBehaviour widthBehavior = 101 child.getDimensionBehaviour(HORIZONTAL); 102 ConstraintWidget.DimensionBehaviour heightBehavior = 103 child.getDimensionBehaviour(VERTICAL); 104 105 boolean skip = widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 106 && child.mMatchConstraintDefaultWidth != MATCH_CONSTRAINT_WRAP 107 && heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 108 && child.mMatchConstraintDefaultHeight != MATCH_CONSTRAINT_WRAP; 109 110 if (!skip && layout.optimizeFor(Optimizer.OPTIMIZATION_DIRECT) 111 && !(child instanceof VirtualLayout)) { 112 if (widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 113 && child.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD 114 && heightBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 115 && !child.isInHorizontalChain()) { 116 skip = true; 117 } 118 119 if (heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 120 && child.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD 121 && widthBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 122 && !child.isInHorizontalChain()) { 123 skip = true; 124 } 125 126 // Don't measure yet -- let the direct solver have a shot at it. 127 if ((widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 128 || heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) 129 && child.mDimensionRatio > 0) { 130 skip = true; 131 } 132 } 133 134 if (skip) { 135 // we don't need to measure here as the dimension of the widget 136 // will be completely computed by the solver. 137 continue; 138 } 139 140 measure(measurer, child, Measure.SELF_DIMENSIONS); 141 if (layout.mMetrics != null) { 142 layout.mMetrics.measuredWidgets++; 143 } 144 } 145 measurer.didMeasures(); 146 } 147 solveLinearSystem(ConstraintWidgetContainer layout, String reason, int pass, int w, int h)148 private void solveLinearSystem(ConstraintWidgetContainer layout, 149 String reason, 150 int pass, 151 int w, 152 int h) { 153 long startLayout = 0; 154 if (layout.mMetrics != null) { 155 startLayout = System.nanoTime(); 156 } 157 158 int minWidth = layout.getMinWidth(); 159 int minHeight = layout.getMinHeight(); 160 layout.setMinWidth(0); 161 layout.setMinHeight(0); 162 layout.setWidth(w); 163 layout.setHeight(h); 164 layout.setMinWidth(minWidth); 165 layout.setMinHeight(minHeight); 166 if (DEBUG) { 167 System.out.println("### Solve <" + reason + "> ###"); 168 } 169 mConstraintWidgetContainer.setPass(pass); 170 mConstraintWidgetContainer.layout(); 171 if (layout.mMetrics != null) { 172 long endLayout = System.nanoTime(); 173 layout.mMetrics.mSolverPasses++; 174 layout.mMetrics.measuresLayoutDuration += (endLayout - startLayout); 175 } 176 } 177 178 /** 179 * Called by ConstraintLayout onMeasure() 180 */ solverMeasure(ConstraintWidgetContainer layout, int optimizationLevel, int paddingX, int paddingY, int widthMode, int widthSize, int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight)181 public long solverMeasure(ConstraintWidgetContainer layout, 182 int optimizationLevel, 183 int paddingX, int paddingY, 184 int widthMode, int widthSize, 185 int heightMode, int heightSize, 186 int lastMeasureWidth, 187 int lastMeasureHeight) { 188 Measurer measurer = layout.getMeasurer(); 189 long layoutTime = 0; 190 191 final int childCount = layout.mChildren.size(); 192 int startingWidth = layout.getWidth(); 193 int startingHeight = layout.getHeight(); 194 195 boolean optimizeWrap = 196 Optimizer.enabled(optimizationLevel, Optimizer.OPTIMIZATION_GRAPH_WRAP); 197 boolean optimize = optimizeWrap 198 || Optimizer.enabled(optimizationLevel, Optimizer.OPTIMIZATION_GRAPH); 199 200 if (optimize) { 201 for (int i = 0; i < childCount; i++) { 202 ConstraintWidget child = layout.mChildren.get(i); 203 boolean matchWidth = child.getHorizontalDimensionBehaviour() 204 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT; 205 boolean matchHeight = child.getVerticalDimensionBehaviour() 206 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT; 207 boolean ratio = matchWidth && matchHeight && child.getDimensionRatio() > 0; 208 if (child.isInHorizontalChain() && ratio) { 209 optimize = false; 210 break; 211 } 212 if (child.isInVerticalChain() && ratio) { 213 optimize = false; 214 break; 215 } 216 if (child instanceof VirtualLayout) { 217 optimize = false; 218 break; 219 } 220 if (child.isInHorizontalChain() 221 || child.isInVerticalChain()) { 222 optimize = false; 223 break; 224 } 225 } 226 } 227 228 if (optimize && LinearSystem.sMetrics != null) { 229 LinearSystem.sMetrics.measures++; 230 } 231 232 boolean allSolved = false; 233 234 optimize &= (widthMode == EXACTLY && heightMode == EXACTLY) || optimizeWrap; 235 236 int computations = 0; 237 238 if (optimize) { 239 // For non-optimizer this doesn't seem to be a problem. 240 // For both cases, having the width address max size early seems to work 241 // (which makes sense). 242 // Putting it specific to optimizer to reduce unnecessary risk. 243 widthSize = Math.min(layout.getMaxWidth(), widthSize); 244 heightSize = Math.min(layout.getMaxHeight(), heightSize); 245 246 if (widthMode == EXACTLY && layout.getWidth() != widthSize) { 247 layout.setWidth(widthSize); 248 layout.invalidateGraph(); 249 } 250 if (heightMode == EXACTLY && layout.getHeight() != heightSize) { 251 layout.setHeight(heightSize); 252 layout.invalidateGraph(); 253 } 254 if (widthMode == EXACTLY && heightMode == EXACTLY) { 255 allSolved = layout.directMeasure(optimizeWrap); 256 computations = 2; 257 } else { 258 allSolved = layout.directMeasureSetup(optimizeWrap); 259 if (widthMode == EXACTLY) { 260 allSolved &= layout.directMeasureWithOrientation(optimizeWrap, HORIZONTAL); 261 computations++; 262 } 263 if (heightMode == EXACTLY) { 264 allSolved &= layout.directMeasureWithOrientation(optimizeWrap, VERTICAL); 265 computations++; 266 } 267 } 268 if (allSolved) { 269 layout.updateFromRuns(widthMode == EXACTLY, heightMode == EXACTLY); 270 } 271 } else { 272 if (false) { 273 layout.mHorizontalRun.clear(); 274 layout.mVerticalRun.clear(); 275 for (ConstraintWidget child : layout.getChildren()) { 276 child.mHorizontalRun.clear(); 277 child.mVerticalRun.clear(); 278 } 279 } 280 } 281 282 if (!allSolved || computations != 2) { 283 int optimizations = layout.getOptimizationLevel(); 284 if (childCount > 0) { 285 measureChildren(layout); 286 } 287 if (layout.mMetrics != null) { 288 layoutTime = System.nanoTime(); 289 } 290 291 updateHierarchy(layout); 292 293 // let's update the size dependent widgets if any... 294 final int sizeDependentWidgetsCount = mVariableDimensionsWidgets.size(); 295 296 // let's solve the linear system. 297 if (childCount > 0) { 298 solveLinearSystem(layout, "First pass", 0, startingWidth, startingHeight); 299 } 300 301 if (DEBUG) { 302 System.out.println("size dependent widgets: " + sizeDependentWidgetsCount); 303 } 304 305 if (sizeDependentWidgetsCount > 0) { 306 boolean needSolverPass = false; 307 boolean containerWrapWidth = layout.getHorizontalDimensionBehaviour() 308 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 309 boolean containerWrapHeight = layout.getVerticalDimensionBehaviour() 310 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 311 int minWidth = Math.max(layout.getWidth(), 312 mConstraintWidgetContainer.getMinWidth()); 313 int minHeight = Math.max(layout.getHeight(), 314 mConstraintWidgetContainer.getMinHeight()); 315 316 //////////////////////////////////////////////////////////////////////////////////// 317 // Let's first apply sizes for VirtualLayouts if any 318 //////////////////////////////////////////////////////////////////////////////////// 319 for (int i = 0; i < sizeDependentWidgetsCount; i++) { 320 ConstraintWidget widget = mVariableDimensionsWidgets.get(i); 321 if (!(widget instanceof VirtualLayout)) { 322 continue; 323 } 324 int preWidth = widget.getWidth(); 325 int preHeight = widget.getHeight(); 326 needSolverPass |= measure(measurer, widget, Measure.TRY_GIVEN_DIMENSIONS); 327 if (layout.mMetrics != null) { 328 layout.mMetrics.measuredMatchWidgets++; 329 } 330 int measuredWidth = widget.getWidth(); 331 int measuredHeight = widget.getHeight(); 332 if (measuredWidth != preWidth) { 333 widget.setWidth(measuredWidth); 334 if (containerWrapWidth && widget.getRight() > minWidth) { 335 int w = widget.getRight() 336 + widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin(); 337 minWidth = Math.max(minWidth, w); 338 } 339 needSolverPass = true; 340 } 341 if (measuredHeight != preHeight) { 342 widget.setHeight(measuredHeight); 343 if (containerWrapHeight && widget.getBottom() > minHeight) { 344 int h = widget.getBottom() 345 + widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getMargin(); 346 minHeight = Math.max(minHeight, h); 347 } 348 needSolverPass = true; 349 } 350 VirtualLayout virtualLayout = (VirtualLayout) widget; 351 needSolverPass |= virtualLayout.needSolverPass(); 352 } 353 //////////////////////////////////////////////////////////////////////////////////// 354 355 int maxIterations = 2; 356 for (int j = 0; j < maxIterations; j++) { 357 for (int i = 0; i < sizeDependentWidgetsCount; i++) { 358 ConstraintWidget widget = mVariableDimensionsWidgets.get(i); 359 if ((widget instanceof Helper && !(widget instanceof VirtualLayout)) 360 || widget instanceof Guideline) { 361 continue; 362 } 363 if (widget.getVisibility() == GONE) { 364 continue; 365 } 366 if (optimize && widget.mHorizontalRun.mDimension.resolved 367 && widget.mVerticalRun.mDimension.resolved) { 368 continue; 369 } 370 if (widget instanceof VirtualLayout) { 371 continue; 372 } 373 374 int preWidth = widget.getWidth(); 375 int preHeight = widget.getHeight(); 376 int preBaselineDistance = widget.getBaselineDistance(); 377 378 int measureStrategy = Measure.TRY_GIVEN_DIMENSIONS; 379 if (j == maxIterations - 1) { 380 measureStrategy = Measure.USE_GIVEN_DIMENSIONS; 381 } 382 boolean hasMeasure = measure(measurer, widget, measureStrategy); 383 if (DO_NOT_USE && !widget.hasDependencies()) { 384 hasMeasure = false; 385 } 386 needSolverPass |= hasMeasure; 387 if (DEBUG && hasMeasure) { 388 System.out.println("{#} Needs Solver pass as measure true for " 389 + widget.getDebugName()); 390 } 391 if (layout.mMetrics != null) { 392 layout.mMetrics.measuredMatchWidgets++; 393 } 394 395 int measuredWidth = widget.getWidth(); 396 int measuredHeight = widget.getHeight(); 397 398 if (measuredWidth != preWidth) { 399 widget.setWidth(measuredWidth); 400 if (containerWrapWidth && widget.getRight() > minWidth) { 401 int w = widget.getRight() 402 + widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin(); 403 minWidth = Math.max(minWidth, w); 404 } 405 if (DEBUG) { 406 System.out.println("{#} Needs Solver pass as Width for " 407 + widget.getDebugName() + " changed: " 408 + measuredWidth + " != " + preWidth); 409 } 410 needSolverPass = true; 411 } 412 if (measuredHeight != preHeight) { 413 widget.setHeight(measuredHeight); 414 if (containerWrapHeight && widget.getBottom() > minHeight) { 415 int h = widget.getBottom() 416 + widget.getAnchor(ConstraintAnchor.Type.BOTTOM) 417 .getMargin(); 418 minHeight = Math.max(minHeight, h); 419 } 420 if (DEBUG) { 421 System.out.println("{#} Needs Solver pass as Height for " 422 + widget.getDebugName() + " changed: " 423 + measuredHeight + " != " + preHeight); 424 } 425 needSolverPass = true; 426 } 427 if (widget.hasBaseline() 428 && preBaselineDistance != widget.getBaselineDistance()) { 429 if (DEBUG) { 430 System.out.println("{#} Needs Solver pass as Baseline for " 431 + widget.getDebugName() + " changed: " 432 + widget.getBaselineDistance() + " != " 433 + preBaselineDistance); 434 } 435 needSolverPass = true; 436 } 437 } 438 if (needSolverPass) { 439 solveLinearSystem(layout, "intermediate pass", 440 1 + j, startingWidth, startingHeight); 441 needSolverPass = false; 442 } else { 443 break; 444 } 445 } 446 } 447 layout.setOptimizationLevel(optimizations); 448 } 449 if (layout.mMetrics != null) { 450 layoutTime = (System.nanoTime() - layoutTime); 451 } 452 return layoutTime; 453 } 454 455 /** 456 * Convenience function to fill in the measure spec 457 * 458 * @param measurer the measurer callback 459 * @param widget the widget to measure 460 * @param measureStrategy how to use the current ConstraintWidget dimensions during the measure 461 * @return true if needs another solver pass 462 */ measure(Measurer measurer, ConstraintWidget widget, int measureStrategy)463 private boolean measure(Measurer measurer, ConstraintWidget widget, int measureStrategy) { 464 mMeasure.horizontalBehavior = widget.getHorizontalDimensionBehaviour(); 465 mMeasure.verticalBehavior = widget.getVerticalDimensionBehaviour(); 466 mMeasure.horizontalDimension = widget.getWidth(); 467 mMeasure.verticalDimension = widget.getHeight(); 468 mMeasure.measuredNeedsSolverPass = false; 469 mMeasure.measureStrategy = measureStrategy; 470 471 boolean horizontalMatchConstraints = (mMeasure.horizontalBehavior 472 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT); 473 boolean verticalMatchConstraints = (mMeasure.verticalBehavior 474 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT); 475 boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0; 476 boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0; 477 478 if (horizontalUseRatio) { 479 if (widget.mResolvedMatchConstraintDefault[HORIZONTAL] 480 == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) { 481 mMeasure.horizontalBehavior = ConstraintWidget.DimensionBehaviour.FIXED; 482 } 483 } 484 if (verticalUseRatio) { 485 if (widget.mResolvedMatchConstraintDefault[VERTICAL] 486 == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) { 487 mMeasure.verticalBehavior = ConstraintWidget.DimensionBehaviour.FIXED; 488 } 489 } 490 491 measurer.measure(widget, mMeasure); 492 widget.setWidth(mMeasure.measuredWidth); 493 widget.setHeight(mMeasure.measuredHeight); 494 widget.setHasBaseline(mMeasure.measuredHasBaseline); 495 widget.setBaselineDistance(mMeasure.measuredBaseline); 496 mMeasure.measureStrategy = Measure.SELF_DIMENSIONS; 497 return mMeasure.measuredNeedsSolverPass; 498 } 499 500 public interface Measurer { 501 // @TODO: add description measure(ConstraintWidget widget, Measure measure)502 void measure(ConstraintWidget widget, Measure measure); 503 504 // @TODO: add description didMeasures()505 void didMeasures(); 506 } 507 508 public static class Measure { 509 public static int SELF_DIMENSIONS = 0; 510 public static int TRY_GIVEN_DIMENSIONS = 1; 511 public static int USE_GIVEN_DIMENSIONS = 2; 512 public ConstraintWidget.DimensionBehaviour horizontalBehavior; 513 public ConstraintWidget.DimensionBehaviour verticalBehavior; 514 public int horizontalDimension; 515 public int verticalDimension; 516 public int measuredWidth; 517 public int measuredHeight; 518 public int measuredBaseline; 519 public boolean measuredHasBaseline; 520 public boolean measuredNeedsSolverPass; 521 public int measureStrategy; 522 } 523 } 524