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.DimensionBehaviour.FIXED; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_PARENT; 22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT; 23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO; 24 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD; 25 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP; 26 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL; 27 import static androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType.CENTER; 28 29 import androidx.constraintlayout.core.widgets.ConstraintAnchor; 30 import androidx.constraintlayout.core.widgets.ConstraintWidget; 31 import androidx.constraintlayout.core.widgets.Helper; 32 33 public class VerticalWidgetRun extends WidgetRun { 34 private static final boolean FORCE_USE = true; 35 public DependencyNode baseline = new DependencyNode(this); 36 androidx.constraintlayout.core.widgets.analyzer.DimensionDependency mBaselineDimension = null; 37 VerticalWidgetRun(ConstraintWidget widget)38 public VerticalWidgetRun(ConstraintWidget widget) { 39 super(widget); 40 start.mType = DependencyNode.Type.TOP; 41 end.mType = DependencyNode.Type.BOTTOM; 42 baseline.mType = DependencyNode.Type.BASELINE; 43 this.orientation = VERTICAL; 44 } 45 46 @Override toString()47 public String toString() { 48 return "VerticalRun " + mWidget.getDebugName(); 49 } 50 51 @Override clear()52 void clear() { 53 mRunGroup = null; 54 start.clear(); 55 end.clear(); 56 baseline.clear(); 57 mDimension.clear(); 58 mResolved = false; 59 } 60 61 @Override reset()62 void reset() { 63 mResolved = false; 64 start.clear(); 65 start.resolved = false; 66 end.clear(); 67 end.resolved = false; 68 baseline.clear(); 69 baseline.resolved = false; 70 mDimension.resolved = false; 71 } 72 73 @Override supportsWrapComputation()74 boolean supportsWrapComputation() { 75 if (super.mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { 76 if (super.mWidget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) { 77 return true; 78 } 79 return false; 80 } 81 return true; 82 } 83 84 @Override update(Dependency dependency)85 public void update(Dependency dependency) { 86 switch (mRunType) { 87 case START: { 88 updateRunStart(dependency); 89 } 90 break; 91 case END: { 92 updateRunEnd(dependency); 93 } 94 break; 95 case CENTER: { 96 updateRunCenter(dependency, mWidget.mTop, mWidget.mBottom, VERTICAL); 97 return; 98 } 99 default: 100 break; 101 } 102 if (FORCE_USE || dependency == mDimension) { 103 if (mDimension.readyToSolve && !mDimension.resolved) { 104 if (mDimensionBehavior == MATCH_CONSTRAINT) { 105 switch (mWidget.mMatchConstraintDefaultHeight) { 106 case MATCH_CONSTRAINT_RATIO: { 107 if (mWidget.mHorizontalRun.mDimension.resolved) { 108 int size = 0; 109 int ratioSide = mWidget.getDimensionRatioSide(); 110 switch (ratioSide) { 111 case ConstraintWidget.HORIZONTAL: { 112 size = (int) (0.5f + mWidget.mHorizontalRun.mDimension.value 113 * mWidget.getDimensionRatio()); 114 } 115 break; 116 case ConstraintWidget.VERTICAL: { 117 size = (int) (0.5f + mWidget.mHorizontalRun.mDimension.value 118 / mWidget.getDimensionRatio()); 119 } 120 break; 121 case ConstraintWidget.UNKNOWN: { 122 size = (int) (0.5f + mWidget.mHorizontalRun.mDimension.value 123 / mWidget.getDimensionRatio()); 124 } 125 break; 126 default: 127 break; 128 } 129 mDimension.resolve(size); 130 } 131 } 132 break; 133 case MATCH_CONSTRAINT_PERCENT: { 134 ConstraintWidget parent = mWidget.getParent(); 135 if (parent != null) { 136 if (parent.mVerticalRun.mDimension.resolved) { 137 float percent = mWidget.mMatchConstraintPercentHeight; 138 int targetDimensionValue = parent.mVerticalRun.mDimension.value; 139 int size = (int) (0.5f + targetDimensionValue * percent); 140 mDimension.resolve(size); 141 } 142 } 143 } 144 break; 145 default: 146 break; 147 } 148 } 149 } 150 } 151 if (!(start.readyToSolve && end.readyToSolve)) { 152 return; 153 } 154 if (start.resolved && end.resolved && mDimension.resolved) { 155 return; 156 } 157 158 if (!mDimension.resolved 159 && mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 160 && mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD 161 && !mWidget.isInVerticalChain()) { 162 163 DependencyNode startTarget = start.mTargets.get(0); 164 DependencyNode endTarget = end.mTargets.get(0); 165 int startPos = startTarget.value + start.mMargin; 166 int endPos = endTarget.value + end.mMargin; 167 168 int distance = endPos - startPos; 169 start.resolve(startPos); 170 end.resolve(endPos); 171 mDimension.resolve(distance); 172 return; 173 } 174 175 if (!mDimension.resolved 176 && mDimensionBehavior == MATCH_CONSTRAINT 177 && matchConstraintsType == MATCH_CONSTRAINT_WRAP) { 178 if (start.mTargets.size() > 0 && end.mTargets.size() > 0) { 179 DependencyNode startTarget = start.mTargets.get(0); 180 DependencyNode endTarget = end.mTargets.get(0); 181 int startPos = startTarget.value + start.mMargin; 182 int endPos = endTarget.value + end.mMargin; 183 int availableSpace = endPos - startPos; 184 if (availableSpace < mDimension.wrapValue) { 185 mDimension.resolve(availableSpace); 186 } else { 187 mDimension.resolve(mDimension.wrapValue); 188 } 189 } 190 } 191 192 if (!mDimension.resolved) { 193 return; 194 } 195 // ready to solve, centering. 196 if (start.mTargets.size() > 0 && end.mTargets.size() > 0) { 197 DependencyNode startTarget = start.mTargets.get(0); 198 DependencyNode endTarget = end.mTargets.get(0); 199 int startPos = startTarget.value + start.mMargin; 200 int endPos = endTarget.value + end.mMargin; 201 float bias = mWidget.getVerticalBiasPercent(); 202 if (startTarget == endTarget) { 203 startPos = startTarget.value; 204 endPos = endTarget.value; 205 // TODO: this might be a nice feature to support, but I guess for now let's stay 206 // compatible with 1.1 207 bias = 0.5f; 208 } 209 int distance = (endPos - startPos - mDimension.value); 210 start.resolve((int) (0.5f + startPos + distance * bias)); 211 end.resolve(start.value + mDimension.value); 212 } 213 } 214 215 @Override apply()216 void apply() { 217 if (mWidget.measured) { 218 mDimension.resolve(mWidget.getHeight()); 219 } 220 if (!mDimension.resolved) { 221 super.mDimensionBehavior = mWidget.getVerticalDimensionBehaviour(); 222 if (mWidget.hasBaseline()) { 223 mBaselineDimension = new BaselineDimensionDependency(this); 224 } 225 if (super.mDimensionBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { 226 if (mDimensionBehavior == MATCH_PARENT) { 227 ConstraintWidget parent = mWidget.getParent(); 228 if (parent != null && parent.getVerticalDimensionBehaviour() == FIXED) { 229 int resolvedDimension = parent.getHeight() 230 - mWidget.mTop.getMargin() - mWidget.mBottom.getMargin(); 231 addTarget(start, parent.mVerticalRun.start, mWidget.mTop.getMargin()); 232 addTarget(end, parent.mVerticalRun.end, -mWidget.mBottom.getMargin()); 233 mDimension.resolve(resolvedDimension); 234 return; 235 } 236 } 237 if (mDimensionBehavior == FIXED) { 238 mDimension.resolve(mWidget.getHeight()); 239 } 240 } 241 } else { 242 if (mDimensionBehavior == MATCH_PARENT) { 243 ConstraintWidget parent = mWidget.getParent(); 244 if (parent != null && parent.getVerticalDimensionBehaviour() == FIXED) { 245 addTarget(start, parent.mVerticalRun.start, mWidget.mTop.getMargin()); 246 addTarget(end, parent.mVerticalRun.end, -mWidget.mBottom.getMargin()); 247 return; 248 } 249 } 250 } 251 // three basic possibilities: 252 // <-s-e-> 253 // <-s-e 254 // s-e-> 255 // and a variation if the dimension is not yet known: 256 // <-s-d-e-> 257 // <-s<-d<-e 258 // s->d->e-> 259 260 if (mDimension.resolved && mWidget.measured) { 261 if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null 262 && mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget 263 != null) { // <-s-e-> 264 if (mWidget.isInVerticalChain()) { 265 start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin(); 266 end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin(); 267 } else { 268 DependencyNode startTarget = 269 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]); 270 if (startTarget != null) { 271 addTarget(start, startTarget, 272 mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin()); 273 } 274 DependencyNode endTarget = 275 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]); 276 if (endTarget != null) { 277 addTarget(end, endTarget, 278 -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin()); 279 } 280 start.delegateToWidgetRun = true; 281 end.delegateToWidgetRun = true; 282 } 283 if (mWidget.hasBaseline()) { 284 addTarget(baseline, start, mWidget.getBaselineDistance()); 285 } 286 } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null) { // <-s-e 287 DependencyNode target = 288 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]); 289 if (target != null) { 290 addTarget(start, target, 291 mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin()); 292 addTarget(end, start, mDimension.value); 293 if (mWidget.hasBaseline()) { 294 addTarget(baseline, start, mWidget.getBaselineDistance()); 295 } 296 } 297 } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget 298 != null) { // s-e-> 299 DependencyNode target = 300 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]); 301 if (target != null) { 302 addTarget(end, target, 303 -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin()); 304 addTarget(start, end, -mDimension.value); 305 } 306 if (mWidget.hasBaseline()) { 307 addTarget(baseline, start, mWidget.getBaselineDistance()); 308 } 309 } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE].mTarget 310 != null) { 311 DependencyNode target = 312 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE]); 313 if (target != null) { 314 addTarget(baseline, target, 0); 315 addTarget(start, baseline, -mWidget.getBaselineDistance()); 316 addTarget(end, start, mDimension.value); 317 } 318 } else { 319 // no connections, nothing to do. 320 if (!(mWidget instanceof Helper) && mWidget.getParent() != null 321 && mWidget.getAnchor(ConstraintAnchor.Type.CENTER).mTarget == null) { 322 DependencyNode top = mWidget.getParent().mVerticalRun.start; 323 addTarget(start, top, mWidget.getY()); 324 addTarget(end, start, mDimension.value); 325 if (mWidget.hasBaseline()) { 326 addTarget(baseline, start, mWidget.getBaselineDistance()); 327 } 328 } 329 } 330 } else { 331 if (!mDimension.resolved && mDimensionBehavior == MATCH_CONSTRAINT) { 332 switch (mWidget.mMatchConstraintDefaultHeight) { 333 case MATCH_CONSTRAINT_RATIO: { 334 if (!mWidget.isInVerticalChain()) { 335 if (mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) { 336 // need to look into both side 337 // do nothing here -- 338 // let the HorizontalWidgetRun::update() deal with it. 339 break; 340 } 341 // we have a ratio, but we depend on the other side computation 342 DependencyNode targetDimension = mWidget.mHorizontalRun.mDimension; 343 mDimension.mTargets.add(targetDimension); 344 targetDimension.mDependencies.add(mDimension); 345 mDimension.delegateToWidgetRun = true; 346 mDimension.mDependencies.add(start); 347 mDimension.mDependencies.add(end); 348 } 349 } 350 break; 351 case MATCH_CONSTRAINT_PERCENT: { 352 // we need to look up the parent dimension 353 ConstraintWidget parent = mWidget.getParent(); 354 if (parent == null) { 355 break; 356 } 357 DependencyNode targetDimension = parent.mVerticalRun.mDimension; 358 mDimension.mTargets.add(targetDimension); 359 targetDimension.mDependencies.add(mDimension); 360 mDimension.delegateToWidgetRun = true; 361 mDimension.mDependencies.add(start); 362 mDimension.mDependencies.add(end); 363 } 364 break; 365 case MATCH_CONSTRAINT_SPREAD: { 366 // the work is done in the update() 367 } 368 break; 369 default: 370 break; 371 } 372 } else { 373 mDimension.addDependency(this); 374 } 375 if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget != null 376 && mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget 377 != null) { // <-s-d-e-> 378 if (mWidget.isInVerticalChain()) { 379 start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin(); 380 end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin(); 381 } else { 382 DependencyNode startTarget = 383 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]); 384 DependencyNode endTarget = 385 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]); 386 if (false) { 387 if (startTarget != null) { 388 addTarget(start, startTarget, 389 mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin()); 390 } 391 if (endTarget != null) { 392 addTarget(end, endTarget, 393 -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM] 394 .getMargin()); 395 } 396 } else { 397 if (startTarget != null) { 398 startTarget.addDependency(this); 399 } 400 if (endTarget != null) { 401 endTarget.addDependency(this); 402 } 403 } 404 mRunType = CENTER; 405 } 406 if (mWidget.hasBaseline()) { 407 addTarget(baseline, start, 1, mBaselineDimension); 408 } 409 } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget 410 != null) { // <-s<-d<-e 411 DependencyNode target = 412 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP]); 413 if (target != null) { 414 addTarget(start, target, 415 mWidget.mListAnchors[ConstraintWidget.ANCHOR_TOP].getMargin()); 416 addTarget(end, start, 1, mDimension); 417 if (mWidget.hasBaseline()) { 418 addTarget(baseline, start, 1, mBaselineDimension); 419 } 420 if (mDimensionBehavior == MATCH_CONSTRAINT) { 421 if (mWidget.getDimensionRatio() > 0) { 422 if (mWidget.mHorizontalRun.mDimensionBehavior == MATCH_CONSTRAINT) { 423 mWidget.mHorizontalRun.mDimension.mDependencies.add(mDimension); 424 mDimension.mTargets.add(mWidget.mHorizontalRun.mDimension); 425 mDimension.updateDelegate = this; 426 } 427 } 428 } 429 } 430 } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget 431 != null) { // s->d->e-> 432 DependencyNode target = 433 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM]); 434 if (target != null) { 435 addTarget(end, target, 436 -mWidget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].getMargin()); 437 addTarget(start, end, -1, mDimension); 438 if (mWidget.hasBaseline()) { 439 addTarget(baseline, start, 1, mBaselineDimension); 440 } 441 } 442 } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE].mTarget != null) { 443 DependencyNode target = 444 getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_BASELINE]); 445 if (target != null) { 446 addTarget(baseline, target, 0); 447 addTarget(start, baseline, -1, mBaselineDimension); 448 addTarget(end, start, 1, mDimension); 449 } 450 } else { 451 // no connections, nothing to do. 452 if (!(mWidget instanceof Helper) && mWidget.getParent() != null) { 453 DependencyNode top = mWidget.getParent().mVerticalRun.start; 454 addTarget(start, top, mWidget.getY()); 455 addTarget(end, start, 1, mDimension); 456 if (mWidget.hasBaseline()) { 457 addTarget(baseline, start, 1, mBaselineDimension); 458 } 459 if (mDimensionBehavior == MATCH_CONSTRAINT) { 460 if (mWidget.getDimensionRatio() > 0) { 461 if (mWidget.mHorizontalRun.mDimensionBehavior == MATCH_CONSTRAINT) { 462 mWidget.mHorizontalRun.mDimension.mDependencies.add(mDimension); 463 mDimension.mTargets.add(mWidget.mHorizontalRun.mDimension); 464 mDimension.updateDelegate = this; 465 } 466 } 467 } 468 } 469 } 470 471 // if dimension has no dependency, mark it as ready to solve 472 if (mDimension.mTargets.size() == 0) { 473 mDimension.readyToSolve = true; 474 } 475 } 476 } 477 478 // @TODO: add description 479 @Override applyToWidget()480 public void applyToWidget() { 481 if (start.resolved) { 482 mWidget.setY(start.value); 483 } 484 } 485 } 486