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.MATCH_CONSTRAINT; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL; 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.widgets.ConstraintAnchor; 26 import androidx.constraintlayout.core.widgets.ConstraintWidget; 27 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer; 28 29 import java.util.ArrayList; 30 31 public class ChainRun extends WidgetRun { 32 ArrayList<WidgetRun> mWidgets = new ArrayList<>(); 33 private int mChainStyle; 34 ChainRun(ConstraintWidget widget, int orientation)35 public ChainRun(ConstraintWidget widget, int orientation) { 36 super(widget); 37 this.orientation = orientation; 38 build(); 39 } 40 41 @Override toString()42 public String toString() { 43 StringBuilder log = new StringBuilder("ChainRun "); 44 log.append((orientation == HORIZONTAL ? "horizontal : " : "vertical : ")); 45 for (WidgetRun run : mWidgets) { 46 log.append("<"); 47 log.append(run); 48 log.append("> "); 49 } 50 return log.toString(); 51 } 52 53 @Override supportsWrapComputation()54 boolean supportsWrapComputation() { 55 final int count = mWidgets.size(); 56 for (int i = 0; i < count; i++) { 57 WidgetRun run = mWidgets.get(i); 58 if (!run.supportsWrapComputation()) { 59 return false; 60 } 61 } 62 return true; 63 } 64 65 // @TODO: add description 66 @Override getWrapDimension()67 public long getWrapDimension() { 68 final int count = mWidgets.size(); 69 long wrapDimension = 0; 70 for (int i = 0; i < count; i++) { 71 WidgetRun run = mWidgets.get(i); 72 wrapDimension += run.start.mMargin; 73 wrapDimension += run.getWrapDimension(); 74 wrapDimension += run.end.mMargin; 75 } 76 return wrapDimension; 77 } 78 build()79 private void build() { 80 ConstraintWidget current = mWidget; 81 ConstraintWidget previous = current.getPreviousChainMember(orientation); 82 while (previous != null) { 83 current = previous; 84 previous = current.getPreviousChainMember(orientation); 85 } 86 mWidget = current; // first element of the chain 87 mWidgets.add(current.getRun(orientation)); 88 ConstraintWidget next = current.getNextChainMember(orientation); 89 while (next != null) { 90 current = next; 91 mWidgets.add(current.getRun(orientation)); 92 next = current.getNextChainMember(orientation); 93 } 94 for (WidgetRun run : mWidgets) { 95 if (orientation == HORIZONTAL) { 96 run.mWidget.horizontalChainRun = this; 97 } else if (orientation == ConstraintWidget.VERTICAL) { 98 run.mWidget.verticalChainRun = this; 99 } 100 } 101 boolean isInRtl = (orientation == HORIZONTAL) 102 && ((ConstraintWidgetContainer) mWidget.getParent()).isRtl(); 103 if (isInRtl && mWidgets.size() > 1) { 104 mWidget = mWidgets.get(mWidgets.size() - 1).mWidget; 105 } 106 mChainStyle = orientation == HORIZONTAL 107 ? mWidget.getHorizontalChainStyle() : mWidget.getVerticalChainStyle(); 108 } 109 110 111 @Override clear()112 void clear() { 113 mRunGroup = null; 114 for (WidgetRun run : mWidgets) { 115 run.clear(); 116 } 117 } 118 119 @Override reset()120 void reset() { 121 start.resolved = false; 122 end.resolved = false; 123 } 124 125 @Override update(Dependency dependency)126 public void update(Dependency dependency) { 127 if (!(start.resolved && end.resolved)) { 128 return; 129 } 130 131 ConstraintWidget parent = mWidget.getParent(); 132 boolean isInRtl = false; 133 if (parent instanceof ConstraintWidgetContainer) { 134 isInRtl = ((ConstraintWidgetContainer) parent).isRtl(); 135 } 136 int distance = end.value - start.value; 137 int size = 0; 138 int numMatchConstraints = 0; 139 float weights = 0; 140 int numVisibleWidgets = 0; 141 final int count = mWidgets.size(); 142 // let's find the first visible widget... 143 int firstVisibleWidget = -1; 144 for (int i = 0; i < count; i++) { 145 WidgetRun run = mWidgets.get(i); 146 if (run.mWidget.getVisibility() == GONE) { 147 continue; 148 } 149 firstVisibleWidget = i; 150 break; 151 } 152 // now the last visible widget... 153 int lastVisibleWidget = -1; 154 for (int i = count - 1; i >= 0; i--) { 155 WidgetRun run = mWidgets.get(i); 156 if (run.mWidget.getVisibility() == GONE) { 157 continue; 158 } 159 lastVisibleWidget = i; 160 break; 161 } 162 for (int j = 0; j < 2; j++) { 163 for (int i = 0; i < count; i++) { 164 WidgetRun run = mWidgets.get(i); 165 if (run.mWidget.getVisibility() == GONE) { 166 continue; 167 } 168 numVisibleWidgets++; 169 if (i > 0 && i >= firstVisibleWidget) { 170 size += run.start.mMargin; 171 } 172 int dimension = run.mDimension.value; 173 boolean treatAsFixed = run.mDimensionBehavior != MATCH_CONSTRAINT; 174 if (treatAsFixed) { 175 if (orientation == HORIZONTAL 176 && !run.mWidget.mHorizontalRun.mDimension.resolved) { 177 return; 178 } 179 if (orientation == VERTICAL && !run.mWidget.mVerticalRun.mDimension.resolved) { 180 return; 181 } 182 } else if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP && j == 0) { 183 treatAsFixed = true; 184 dimension = run.mDimension.wrapValue; 185 numMatchConstraints++; 186 } else if (run.mDimension.resolved) { 187 treatAsFixed = true; 188 } 189 if (!treatAsFixed) { // only for the first pass 190 numMatchConstraints++; 191 float weight = run.mWidget.mWeight[orientation]; 192 if (weight >= 0) { 193 weights += weight; 194 } 195 } else { 196 size += dimension; 197 } 198 if (i < count - 1 && i < lastVisibleWidget) { 199 size += -run.end.mMargin; 200 } 201 } 202 if (size < distance || numMatchConstraints == 0) { 203 break; // we are good to go! 204 } 205 // otherwise, let's do another pass with using match_constraints 206 numVisibleWidgets = 0; 207 numMatchConstraints = 0; 208 size = 0; 209 weights = 0; 210 } 211 212 int position = start.value; 213 if (isInRtl) { 214 position = end.value; 215 } 216 if (size > distance) { 217 if (isInRtl) { 218 position += (int) (0.5f + (size - distance) / 2f); 219 } else { 220 position -= (int) (0.5f + (size - distance) / 2f); 221 } 222 } 223 int matchConstraintsDimension = 0; 224 if (numMatchConstraints > 0) { 225 matchConstraintsDimension = 226 (int) (0.5f + (distance - size) / (float) numMatchConstraints); 227 228 int appliedLimits = 0; 229 for (int i = 0; i < count; i++) { 230 WidgetRun run = mWidgets.get(i); 231 if (run.mWidget.getVisibility() == GONE) { 232 continue; 233 } 234 if (run.mDimensionBehavior == MATCH_CONSTRAINT && !run.mDimension.resolved) { 235 int dimension = matchConstraintsDimension; 236 if (weights > 0) { 237 float weight = run.mWidget.mWeight[orientation]; 238 dimension = (int) (0.5f + weight * (distance - size) / weights); 239 } 240 int max; 241 int min; 242 int value = dimension; 243 if (orientation == HORIZONTAL) { 244 max = run.mWidget.mMatchConstraintMaxWidth; 245 min = run.mWidget.mMatchConstraintMinWidth; 246 } else { 247 max = run.mWidget.mMatchConstraintMaxHeight; 248 min = run.mWidget.mMatchConstraintMinHeight; 249 } 250 if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) { 251 value = Math.min(value, run.mDimension.wrapValue); 252 } 253 value = Math.max(min, value); 254 if (max > 0) { 255 value = Math.min(max, value); 256 } 257 if (value != dimension) { 258 appliedLimits++; 259 dimension = value; 260 } 261 run.mDimension.resolve(dimension); 262 } 263 } 264 if (appliedLimits > 0) { 265 numMatchConstraints -= appliedLimits; 266 // we have to recompute the sizes 267 size = 0; 268 for (int i = 0; i < count; i++) { 269 WidgetRun run = mWidgets.get(i); 270 if (run.mWidget.getVisibility() == GONE) { 271 continue; 272 } 273 if (i > 0 && i >= firstVisibleWidget) { 274 size += run.start.mMargin; 275 } 276 size += run.mDimension.value; 277 if (i < count - 1 && i < lastVisibleWidget) { 278 size += -run.end.mMargin; 279 } 280 } 281 } 282 if (mChainStyle == ConstraintWidget.CHAIN_PACKED && appliedLimits == 0) { 283 mChainStyle = ConstraintWidget.CHAIN_SPREAD; 284 } 285 } 286 287 if (size > distance) { 288 mChainStyle = ConstraintWidget.CHAIN_PACKED; 289 } 290 291 if (numVisibleWidgets > 0 && numMatchConstraints == 0 292 && firstVisibleWidget == lastVisibleWidget) { 293 // only one widget of fixed size to display... 294 mChainStyle = ConstraintWidget.CHAIN_PACKED; 295 } 296 297 if (mChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE) { 298 int gap = 0; 299 if (numVisibleWidgets > 1) { 300 gap = (distance - size) / (numVisibleWidgets - 1); 301 } else if (numVisibleWidgets == 1) { 302 gap = (distance - size) / 2; 303 } 304 if (numMatchConstraints > 0) { 305 gap = 0; 306 } 307 for (int i = 0; i < count; i++) { 308 int index = i; 309 if (isInRtl) { 310 index = count - (i + 1); 311 } 312 WidgetRun run = mWidgets.get(index); 313 if (run.mWidget.getVisibility() == GONE) { 314 run.start.resolve(position); 315 run.end.resolve(position); 316 continue; 317 } 318 if (i > 0) { 319 if (isInRtl) { 320 position -= gap; 321 } else { 322 position += gap; 323 } 324 } 325 if (i > 0 && i >= firstVisibleWidget) { 326 if (isInRtl) { 327 position -= run.start.mMargin; 328 } else { 329 position += run.start.mMargin; 330 } 331 } 332 333 if (isInRtl) { 334 run.end.resolve(position); 335 } else { 336 run.start.resolve(position); 337 } 338 339 int dimension = run.mDimension.value; 340 if (run.mDimensionBehavior == MATCH_CONSTRAINT 341 && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) { 342 dimension = run.mDimension.wrapValue; 343 } 344 if (isInRtl) { 345 position -= dimension; 346 } else { 347 position += dimension; 348 } 349 350 if (isInRtl) { 351 run.start.resolve(position); 352 } else { 353 run.end.resolve(position); 354 } 355 run.mResolved = true; 356 if (i < count - 1 && i < lastVisibleWidget) { 357 if (isInRtl) { 358 position -= -run.end.mMargin; 359 } else { 360 position += -run.end.mMargin; 361 } 362 } 363 } 364 } else if (mChainStyle == ConstraintWidget.CHAIN_SPREAD) { 365 int gap = (distance - size) / (numVisibleWidgets + 1); 366 if (numMatchConstraints > 0) { 367 gap = 0; 368 } 369 for (int i = 0; i < count; i++) { 370 int index = i; 371 if (isInRtl) { 372 index = count - (i + 1); 373 } 374 WidgetRun run = mWidgets.get(index); 375 if (run.mWidget.getVisibility() == GONE) { 376 run.start.resolve(position); 377 run.end.resolve(position); 378 continue; 379 } 380 if (isInRtl) { 381 position -= gap; 382 } else { 383 position += gap; 384 } 385 if (i > 0 && i >= firstVisibleWidget) { 386 if (isInRtl) { 387 position -= run.start.mMargin; 388 } else { 389 position += run.start.mMargin; 390 } 391 } 392 393 if (isInRtl) { 394 run.end.resolve(position); 395 } else { 396 run.start.resolve(position); 397 } 398 399 int dimension = run.mDimension.value; 400 if (run.mDimensionBehavior == MATCH_CONSTRAINT 401 && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) { 402 dimension = Math.min(dimension, run.mDimension.wrapValue); 403 } 404 405 if (isInRtl) { 406 position -= dimension; 407 } else { 408 position += dimension; 409 } 410 411 if (isInRtl) { 412 run.start.resolve(position); 413 } else { 414 run.end.resolve(position); 415 } 416 if (i < count - 1 && i < lastVisibleWidget) { 417 if (isInRtl) { 418 position -= -run.end.mMargin; 419 } else { 420 position += -run.end.mMargin; 421 } 422 } 423 } 424 } else if (mChainStyle == ConstraintWidget.CHAIN_PACKED) { 425 float bias = (orientation == HORIZONTAL) ? mWidget.getHorizontalBiasPercent() 426 : mWidget.getVerticalBiasPercent(); 427 if (isInRtl) { 428 bias = 1 - bias; 429 } 430 int gap = (int) (0.5f + (distance - size) * bias); 431 if (gap < 0 || numMatchConstraints > 0) { 432 gap = 0; 433 } 434 if (isInRtl) { 435 position -= gap; 436 } else { 437 position += gap; 438 } 439 for (int i = 0; i < count; i++) { 440 int index = i; 441 if (isInRtl) { 442 index = count - (i + 1); 443 } 444 WidgetRun run = mWidgets.get(index); 445 if (run.mWidget.getVisibility() == GONE) { 446 run.start.resolve(position); 447 run.end.resolve(position); 448 continue; 449 } 450 if (i > 0 && i >= firstVisibleWidget) { 451 if (isInRtl) { 452 position -= run.start.mMargin; 453 } else { 454 position += run.start.mMargin; 455 } 456 } 457 if (isInRtl) { 458 run.end.resolve(position); 459 } else { 460 run.start.resolve(position); 461 } 462 463 int dimension = run.mDimension.value; 464 if (run.mDimensionBehavior == MATCH_CONSTRAINT 465 && run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) { 466 dimension = run.mDimension.wrapValue; 467 } 468 if (isInRtl) { 469 position -= dimension; 470 } else { 471 position += dimension; 472 } 473 474 if (isInRtl) { 475 run.start.resolve(position); 476 } else { 477 run.end.resolve(position); 478 } 479 if (i < count - 1 && i < lastVisibleWidget) { 480 if (isInRtl) { 481 position -= -run.end.mMargin; 482 } else { 483 position += -run.end.mMargin; 484 } 485 } 486 } 487 } 488 } 489 490 // @TODO: add description 491 @Override applyToWidget()492 public void applyToWidget() { 493 for (int i = 0; i < mWidgets.size(); i++) { 494 WidgetRun run = mWidgets.get(i); 495 run.applyToWidget(); 496 } 497 } 498 getFirstVisibleWidget()499 private ConstraintWidget getFirstVisibleWidget() { 500 for (int i = 0; i < mWidgets.size(); i++) { 501 WidgetRun run = mWidgets.get(i); 502 if (run.mWidget.getVisibility() != GONE) { 503 return run.mWidget; 504 } 505 } 506 return null; 507 } 508 getLastVisibleWidget()509 private ConstraintWidget getLastVisibleWidget() { 510 for (int i = mWidgets.size() - 1; i >= 0; i--) { 511 WidgetRun run = mWidgets.get(i); 512 if (run.mWidget.getVisibility() != GONE) { 513 return run.mWidget; 514 } 515 } 516 return null; 517 } 518 519 520 @Override apply()521 void apply() { 522 for (WidgetRun run : mWidgets) { 523 run.apply(); 524 } 525 int count = mWidgets.size(); 526 if (count < 1) { 527 return; 528 } 529 530 // get the first and last element of the chain 531 ConstraintWidget firstWidget = mWidgets.get(0).mWidget; 532 ConstraintWidget lastWidget = mWidgets.get(count - 1).mWidget; 533 534 if (orientation == HORIZONTAL) { 535 ConstraintAnchor startAnchor = firstWidget.mLeft; 536 ConstraintAnchor endAnchor = lastWidget.mRight; 537 DependencyNode startTarget = getTarget(startAnchor, HORIZONTAL); 538 int startMargin = startAnchor.getMargin(); 539 ConstraintWidget firstVisibleWidget = getFirstVisibleWidget(); 540 if (firstVisibleWidget != null) { 541 startMargin = firstVisibleWidget.mLeft.getMargin(); 542 } 543 if (startTarget != null) { 544 addTarget(start, startTarget, startMargin); 545 } 546 DependencyNode endTarget = getTarget(endAnchor, HORIZONTAL); 547 int endMargin = endAnchor.getMargin(); 548 ConstraintWidget lastVisibleWidget = getLastVisibleWidget(); 549 if (lastVisibleWidget != null) { 550 endMargin = lastVisibleWidget.mRight.getMargin(); 551 } 552 if (endTarget != null) { 553 addTarget(end, endTarget, -endMargin); 554 } 555 } else { 556 ConstraintAnchor startAnchor = firstWidget.mTop; 557 ConstraintAnchor endAnchor = lastWidget.mBottom; 558 DependencyNode startTarget = getTarget(startAnchor, VERTICAL); 559 int startMargin = startAnchor.getMargin(); 560 ConstraintWidget firstVisibleWidget = getFirstVisibleWidget(); 561 if (firstVisibleWidget != null) { 562 startMargin = firstVisibleWidget.mTop.getMargin(); 563 } 564 if (startTarget != null) { 565 addTarget(start, startTarget, startMargin); 566 } 567 DependencyNode endTarget = getTarget(endAnchor, VERTICAL); 568 int endMargin = endAnchor.getMargin(); 569 ConstraintWidget lastVisibleWidget = getLastVisibleWidget(); 570 if (lastVisibleWidget != null) { 571 endMargin = lastVisibleWidget.mBottom.getMargin(); 572 } 573 if (endTarget != null) { 574 addTarget(end, endTarget, -endMargin); 575 } 576 } 577 start.updateDelegate = this; 578 end.updateDelegate = this; 579 } 580 581 } 582