1 /* 2 * Copyright (C) 2020 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.analyzer; 17 18 import static androidx.constraintlayout.core.widgets.ConstraintWidget.BOTH; 19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_PARENT; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; 22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL; 23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL; 24 25 import androidx.constraintlayout.core.widgets.Barrier; 26 import androidx.constraintlayout.core.widgets.ConstraintAnchor; 27 import androidx.constraintlayout.core.widgets.ConstraintWidget; 28 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer; 29 import androidx.constraintlayout.core.widgets.Flow; 30 import androidx.constraintlayout.core.widgets.Guideline; 31 import androidx.constraintlayout.core.widgets.HelperWidget; 32 33 import java.util.ArrayList; 34 35 /** 36 * Implements a simple grouping mechanism, to group interdependent widgets together. 37 * 38 * TODO: we should move towards a more leaner implementation 39 * -- this is more expensive as it could be. 40 */ 41 public class Grouping { 42 43 private static final boolean DEBUG = false; 44 private static final boolean DEBUG_GROUPING = false; 45 private static final boolean FORCE_USE = true; 46 47 // @TODO: add description validInGroup(ConstraintWidget.DimensionBehaviour layoutHorizontal, ConstraintWidget.DimensionBehaviour layoutVertical, ConstraintWidget.DimensionBehaviour widgetHorizontal, ConstraintWidget.DimensionBehaviour widgetVertical)48 public static boolean validInGroup(ConstraintWidget.DimensionBehaviour layoutHorizontal, 49 ConstraintWidget.DimensionBehaviour layoutVertical, 50 ConstraintWidget.DimensionBehaviour widgetHorizontal, 51 ConstraintWidget.DimensionBehaviour widgetVertical) { 52 boolean fixedHorizontal = widgetHorizontal == FIXED || widgetHorizontal == WRAP_CONTENT 53 || (widgetHorizontal == MATCH_PARENT && layoutHorizontal != WRAP_CONTENT); 54 boolean fixedVertical = widgetVertical == FIXED || widgetVertical == WRAP_CONTENT 55 || (widgetVertical == MATCH_PARENT && layoutVertical != WRAP_CONTENT); 56 if (fixedHorizontal || fixedVertical) { 57 return true; 58 } 59 return false; 60 } 61 62 // @TODO: add description simpleSolvingPass(ConstraintWidgetContainer layout, BasicMeasure.Measurer measurer)63 public static boolean simpleSolvingPass(ConstraintWidgetContainer layout, 64 BasicMeasure.Measurer measurer) { 65 66 if (DEBUG) { 67 System.out.println("*** GROUP SOLVING ***"); 68 } 69 ArrayList<ConstraintWidget> children = layout.getChildren(); 70 71 final int count = children.size(); 72 73 ArrayList<Guideline> verticalGuidelines = null; 74 ArrayList<Guideline> horizontalGuidelines = null; 75 ArrayList<HelperWidget> horizontalBarriers = null; 76 ArrayList<HelperWidget> verticalBarriers = null; 77 ArrayList<ConstraintWidget> isolatedHorizontalChildren = null; 78 ArrayList<ConstraintWidget> isolatedVerticalChildren = null; 79 80 for (int i = 0; i < count; i++) { 81 ConstraintWidget child = children.get(i); 82 if (!validInGroup(layout.getHorizontalDimensionBehaviour(), 83 layout.getVerticalDimensionBehaviour(), 84 child.getHorizontalDimensionBehaviour(), 85 child.getVerticalDimensionBehaviour())) { 86 if (DEBUG) { 87 System.out.println("*** NO GROUP SOLVING ***"); 88 } 89 return false; 90 } 91 if (child instanceof Flow) { 92 return false; 93 } 94 } 95 if (layout.mMetrics != null) { 96 layout.mMetrics.grouping++; 97 } 98 for (int i = 0; i < count; i++) { 99 ConstraintWidget child = children.get(i); 100 if (!validInGroup(layout.getHorizontalDimensionBehaviour(), 101 layout.getVerticalDimensionBehaviour(), 102 child.getHorizontalDimensionBehaviour(), 103 child.getVerticalDimensionBehaviour())) { 104 ConstraintWidgetContainer.measure(0, child, measurer, 105 layout.mMeasure, BasicMeasure.Measure.SELF_DIMENSIONS); 106 } 107 if (child instanceof Guideline) { 108 Guideline guideline = (Guideline) child; 109 if (guideline.getOrientation() == HORIZONTAL) { 110 if (horizontalGuidelines == null) { 111 horizontalGuidelines = new ArrayList<>(); 112 } 113 horizontalGuidelines.add(guideline); 114 } 115 if (guideline.getOrientation() == VERTICAL) { 116 if (verticalGuidelines == null) { 117 verticalGuidelines = new ArrayList<>(); 118 } 119 verticalGuidelines.add(guideline); 120 } 121 } 122 if (child instanceof HelperWidget) { 123 if (child instanceof Barrier) { 124 Barrier barrier = (Barrier) child; 125 if (barrier.getOrientation() == HORIZONTAL) { 126 if (horizontalBarriers == null) { 127 horizontalBarriers = new ArrayList<>(); 128 } 129 horizontalBarriers.add(barrier); 130 } 131 if (barrier.getOrientation() == VERTICAL) { 132 if (verticalBarriers == null) { 133 verticalBarriers = new ArrayList<>(); 134 } 135 verticalBarriers.add(barrier); 136 } 137 } else { 138 HelperWidget helper = (HelperWidget) child; 139 if (horizontalBarriers == null) { 140 horizontalBarriers = new ArrayList<>(); 141 } 142 horizontalBarriers.add(helper); 143 if (verticalBarriers == null) { 144 verticalBarriers = new ArrayList<>(); 145 } 146 verticalBarriers.add(helper); 147 } 148 } 149 if (child.mLeft.mTarget == null && child.mRight.mTarget == null 150 && !(child instanceof Guideline) && !(child instanceof Barrier)) { 151 if (isolatedHorizontalChildren == null) { 152 isolatedHorizontalChildren = new ArrayList<>(); 153 } 154 isolatedHorizontalChildren.add(child); 155 } 156 if (child.mTop.mTarget == null && child.mBottom.mTarget == null 157 && child.mBaseline.mTarget == null 158 && !(child instanceof Guideline) && !(child instanceof Barrier)) { 159 if (isolatedVerticalChildren == null) { 160 isolatedVerticalChildren = new ArrayList<>(); 161 } 162 isolatedVerticalChildren.add(child); 163 } 164 } 165 ArrayList<WidgetGroup> allDependencyLists = new ArrayList<>(); 166 167 if (FORCE_USE || layout.getHorizontalDimensionBehaviour() 168 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) { 169 //horizontalDependencyLists; //new ArrayList<>(); 170 ArrayList<WidgetGroup> dependencyLists = allDependencyLists; 171 172 if (verticalGuidelines != null) { 173 for (Guideline guideline : verticalGuidelines) { 174 findDependents(guideline, HORIZONTAL, dependencyLists, null); 175 } 176 } 177 if (horizontalBarriers != null) { 178 for (HelperWidget barrier : horizontalBarriers) { 179 WidgetGroup group = findDependents(barrier, HORIZONTAL, dependencyLists, null); 180 barrier.addDependents(dependencyLists, HORIZONTAL, group); 181 group.cleanup(dependencyLists); 182 } 183 } 184 185 ConstraintAnchor left = layout.getAnchor(ConstraintAnchor.Type.LEFT); 186 if (left.getDependents() != null) { 187 for (ConstraintAnchor first : left.getDependents()) { 188 findDependents(first.mOwner, ConstraintWidget.HORIZONTAL, 189 dependencyLists, null); 190 } 191 } 192 193 ConstraintAnchor right = layout.getAnchor(ConstraintAnchor.Type.RIGHT); 194 if (right.getDependents() != null) { 195 for (ConstraintAnchor first : right.getDependents()) { 196 findDependents(first.mOwner, ConstraintWidget.HORIZONTAL, 197 dependencyLists, null); 198 } 199 } 200 201 ConstraintAnchor center = layout.getAnchor(ConstraintAnchor.Type.CENTER); 202 if (center.getDependents() != null) { 203 for (ConstraintAnchor first : center.getDependents()) { 204 findDependents(first.mOwner, ConstraintWidget.HORIZONTAL, 205 dependencyLists, null); 206 } 207 } 208 209 if (isolatedHorizontalChildren != null) { 210 for (ConstraintWidget widget : isolatedHorizontalChildren) { 211 findDependents(widget, HORIZONTAL, dependencyLists, null); 212 } 213 } 214 } 215 216 if (FORCE_USE || layout.getVerticalDimensionBehaviour() 217 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) { 218 //verticalDependencyLists; //new ArrayList<>(); 219 ArrayList<WidgetGroup> dependencyLists = allDependencyLists; 220 221 if (horizontalGuidelines != null) { 222 for (Guideline guideline : horizontalGuidelines) { 223 findDependents(guideline, VERTICAL, dependencyLists, null); 224 } 225 } 226 if (verticalBarriers != null) { 227 for (HelperWidget barrier : verticalBarriers) { 228 WidgetGroup group = findDependents(barrier, VERTICAL, dependencyLists, null); 229 barrier.addDependents(dependencyLists, VERTICAL, group); 230 group.cleanup(dependencyLists); 231 } 232 } 233 234 ConstraintAnchor top = layout.getAnchor(ConstraintAnchor.Type.TOP); 235 if (top.getDependents() != null) { 236 for (ConstraintAnchor first : top.getDependents()) { 237 findDependents(first.mOwner, VERTICAL, dependencyLists, null); 238 } 239 } 240 241 ConstraintAnchor baseline = layout.getAnchor(ConstraintAnchor.Type.BASELINE); 242 if (baseline.getDependents() != null) { 243 for (ConstraintAnchor first : baseline.getDependents()) { 244 findDependents(first.mOwner, VERTICAL, dependencyLists, null); 245 } 246 } 247 248 ConstraintAnchor bottom = layout.getAnchor(ConstraintAnchor.Type.BOTTOM); 249 if (bottom.getDependents() != null) { 250 for (ConstraintAnchor first : bottom.getDependents()) { 251 findDependents(first.mOwner, VERTICAL, dependencyLists, null); 252 } 253 } 254 255 ConstraintAnchor center = layout.getAnchor(ConstraintAnchor.Type.CENTER); 256 if (center.getDependents() != null) { 257 for (ConstraintAnchor first : center.getDependents()) { 258 findDependents(first.mOwner, VERTICAL, dependencyLists, null); 259 } 260 } 261 262 if (isolatedVerticalChildren != null) { 263 for (ConstraintWidget widget : isolatedVerticalChildren) { 264 findDependents(widget, VERTICAL, dependencyLists, null); 265 } 266 } 267 } 268 // Now we may have to merge horizontal/vertical dependencies 269 for (int i = 0; i < count; i++) { 270 ConstraintWidget child = children.get(i); 271 if (child.oppositeDimensionsTied()) { 272 WidgetGroup horizontalGroup = findGroup(allDependencyLists, child.horizontalGroup); 273 WidgetGroup verticalGroup = findGroup(allDependencyLists, child.verticalGroup); 274 if (horizontalGroup != null && verticalGroup != null) { 275 if (DEBUG_GROUPING) { 276 System.out.println("Merging " + horizontalGroup 277 + " to " + verticalGroup + " for " + child); 278 } 279 horizontalGroup.moveTo(HORIZONTAL, verticalGroup); 280 verticalGroup.setOrientation(BOTH); 281 allDependencyLists.remove(horizontalGroup); 282 } 283 } 284 if (DEBUG_GROUPING) { 285 System.out.println("Widget " + child + " => " 286 + child.horizontalGroup + " : " + child.verticalGroup); 287 } 288 } 289 290 if (allDependencyLists.size() <= 1) { 291 return false; 292 } 293 294 if (DEBUG) { 295 System.out.println("----------------------------------"); 296 System.out.println("-- Horizontal dependency lists:"); 297 System.out.println("----------------------------------"); 298 for (WidgetGroup list : allDependencyLists) { 299 if (list.getOrientation() != VERTICAL) { 300 System.out.println("list: " + list); 301 } 302 } 303 System.out.println("----------------------------------"); 304 System.out.println("-- Vertical dependency lists:"); 305 System.out.println("----------------------------------"); 306 for (WidgetGroup list : allDependencyLists) { 307 if (list.getOrientation() != HORIZONTAL) { 308 System.out.println("list: " + list); 309 } 310 } 311 System.out.println("----------------------------------"); 312 } 313 314 WidgetGroup horizontalPick = null; 315 WidgetGroup verticalPick = null; 316 317 if (layout.getHorizontalDimensionBehaviour() 318 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) { 319 int maxWrap = 0; 320 WidgetGroup picked = null; 321 for (WidgetGroup list : allDependencyLists) { 322 if (list.getOrientation() == VERTICAL) { 323 continue; 324 } 325 list.setAuthoritative(false); 326 int wrap = list.measureWrap(layout.getSystem(), HORIZONTAL); 327 if (wrap > maxWrap) { 328 picked = list; 329 maxWrap = wrap; 330 } 331 if (DEBUG) { 332 System.out.println("list: " + list + " => " + wrap); 333 } 334 } 335 if (picked != null) { 336 if (DEBUG) { 337 System.out.println("Horizontal MaxWrap : " + maxWrap + " with group " + picked); 338 } 339 layout.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED); 340 layout.setWidth(maxWrap); 341 picked.setAuthoritative(true); 342 horizontalPick = picked; 343 } 344 } 345 346 if (layout.getVerticalDimensionBehaviour() 347 == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) { 348 int maxWrap = 0; 349 WidgetGroup picked = null; 350 for (WidgetGroup list : allDependencyLists) { 351 if (list.getOrientation() == HORIZONTAL) { 352 continue; 353 } 354 list.setAuthoritative(false); 355 int wrap = list.measureWrap(layout.getSystem(), VERTICAL); 356 if (wrap > maxWrap) { 357 picked = list; 358 maxWrap = wrap; 359 } 360 if (DEBUG) { 361 System.out.println(" " + list + " => " + wrap); 362 } 363 } 364 if (picked != null) { 365 if (DEBUG) { 366 System.out.println("Vertical MaxWrap : " + maxWrap + " with group " + picked); 367 } 368 layout.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED); 369 layout.setHeight(maxWrap); 370 picked.setAuthoritative(true); 371 verticalPick = picked; 372 } 373 } 374 return horizontalPick != null || verticalPick != null; 375 } 376 findGroup(ArrayList<WidgetGroup> horizontalDependencyLists, int groupId)377 private static WidgetGroup findGroup(ArrayList<WidgetGroup> horizontalDependencyLists, 378 int groupId) { 379 final int count = horizontalDependencyLists.size(); 380 for (int i = 0; i < count; i++) { 381 WidgetGroup group = horizontalDependencyLists.get(i); 382 if (groupId == group.getId()) { 383 return group; 384 } 385 } 386 return null; 387 } 388 389 // @TODO: add description findDependents(ConstraintWidget constraintWidget, int orientation, ArrayList<WidgetGroup> list, WidgetGroup group)390 public static WidgetGroup findDependents(ConstraintWidget constraintWidget, 391 int orientation, 392 ArrayList<WidgetGroup> list, 393 WidgetGroup group) { 394 int groupId = -1; 395 if (orientation == ConstraintWidget.HORIZONTAL) { 396 groupId = constraintWidget.horizontalGroup; 397 } else { 398 groupId = constraintWidget.verticalGroup; 399 } 400 if (DEBUG_GROUPING) { 401 System.out.println("--- find " + (orientation == HORIZONTAL ? "Horiz" : "Vert") 402 + " dependents of " + constraintWidget.getDebugName() 403 + " group " + group + " widget group id " + groupId); 404 } 405 if (groupId != -1 && (group == null || (groupId != group.getId()))) { 406 // already in a group! 407 if (DEBUG_GROUPING) { 408 System.out.println("widget " + constraintWidget.getDebugName() 409 + " already in group " + groupId + " group: " + group); 410 } 411 for (int i = 0; i < list.size(); i++) { 412 WidgetGroup widgetGroup = list.get(i); 413 if (widgetGroup.getId() == groupId) { 414 if (group != null) { 415 if (DEBUG_GROUPING) { 416 System.out.println("Move group " + group + " to " + widgetGroup); 417 } 418 group.moveTo(orientation, widgetGroup); 419 list.remove(group); 420 } 421 group = widgetGroup; 422 break; 423 } 424 } 425 } else if (groupId != -1) { 426 return group; 427 } 428 if (group == null) { 429 if (constraintWidget instanceof HelperWidget) { 430 HelperWidget helper = (HelperWidget) constraintWidget; 431 groupId = helper.findGroupInDependents(orientation); 432 if (groupId != -1) { 433 for (int i = 0; i < list.size(); i++) { 434 WidgetGroup widgetGroup = list.get(i); 435 if (widgetGroup.getId() == groupId) { 436 group = widgetGroup; 437 break; 438 } 439 } 440 } 441 } 442 if (group == null) { 443 group = new WidgetGroup(orientation); 444 } 445 if (DEBUG_GROUPING) { 446 System.out.println("Create group " + group 447 + " for widget " + constraintWidget.getDebugName()); 448 } 449 list.add(group); 450 } 451 if (group.add(constraintWidget)) { 452 if (constraintWidget instanceof Guideline) { 453 Guideline guideline = (Guideline) constraintWidget; 454 guideline.getAnchor().findDependents(guideline.getOrientation() 455 == Guideline.HORIZONTAL ? VERTICAL : HORIZONTAL, list, group); 456 } 457 if (orientation == ConstraintWidget.HORIZONTAL) { 458 constraintWidget.horizontalGroup = group.getId(); 459 if (DEBUG_GROUPING) { 460 System.out.println("Widget " + constraintWidget.getDebugName() 461 + " H group is " + constraintWidget.horizontalGroup); 462 } 463 constraintWidget.mLeft.findDependents(orientation, list, group); 464 constraintWidget.mRight.findDependents(orientation, list, group); 465 } else { 466 constraintWidget.verticalGroup = group.getId(); 467 if (DEBUG_GROUPING) { 468 System.out.println("Widget " + constraintWidget.getDebugName() 469 + " V group is " + constraintWidget.verticalGroup); 470 } 471 constraintWidget.mTop.findDependents(orientation, list, group); 472 constraintWidget.mBaseline.findDependents(orientation, list, group); 473 constraintWidget.mBottom.findDependents(orientation, list, group); 474 } 475 constraintWidget.mCenter.findDependents(orientation, list, group); 476 } 477 return group; 478 } 479 } 480