1 /* 2 * Copyright (C) 2017 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; 18 19 import androidx.constraintlayout.core.LinearSystem; 20 import androidx.constraintlayout.core.SolverVariable; 21 22 import java.util.HashMap; 23 24 /** 25 * A Barrier takes multiple widgets 26 */ 27 public class Barrier extends HelperWidget { 28 29 public static final int LEFT = 0; 30 public static final int RIGHT = 1; 31 public static final int TOP = 2; 32 public static final int BOTTOM = 3; 33 private static final boolean USE_RESOLUTION = true; 34 private static final boolean USE_RELAX_GONE = false; 35 36 private int mBarrierType = LEFT; 37 38 private boolean mAllowsGoneWidget = true; 39 private int mMargin = 0; 40 boolean mResolved = false; 41 Barrier()42 public Barrier() { 43 } 44 Barrier(String debugName)45 public Barrier(String debugName) { 46 setDebugName(debugName); 47 } 48 49 @Override allowedInBarrier()50 public boolean allowedInBarrier() { 51 return true; 52 } 53 getBarrierType()54 public int getBarrierType() { 55 return mBarrierType; 56 } 57 setBarrierType(int barrierType)58 public void setBarrierType(int barrierType) { 59 mBarrierType = barrierType; 60 } 61 setAllowsGoneWidget(boolean allowsGoneWidget)62 public void setAllowsGoneWidget(boolean allowsGoneWidget) { 63 mAllowsGoneWidget = allowsGoneWidget; 64 } 65 66 /** 67 * Find if this barrier supports gone widgets. 68 * 69 * @return true if this barrier supports gone widgets, otherwise false 70 * @deprecated This method should be called {@code getAllowsGoneWidget} 71 * such that {@code allowsGoneWidget} 72 * can be accessed as a property from Kotlin; {@see https://android.github 73 * .io/kotlin-guides/interop.html#property-prefixes}. 74 * Use {@link #getAllowsGoneWidget()} instead. 75 */ 76 @Deprecated allowsGoneWidget()77 public boolean allowsGoneWidget() { 78 return mAllowsGoneWidget; 79 } 80 81 /** 82 * Find if this barrier supports gone widgets. 83 * 84 * @return true if this barrier supports gone widgets, otherwise false 85 */ getAllowsGoneWidget()86 public boolean getAllowsGoneWidget() { 87 return mAllowsGoneWidget; 88 } 89 90 @Override isResolvedHorizontally()91 public boolean isResolvedHorizontally() { 92 return mResolved; 93 } 94 95 @Override isResolvedVertically()96 public boolean isResolvedVertically() { 97 return mResolved; 98 } 99 100 @Override copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map)101 public void copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map) { 102 super.copy(src, map); 103 Barrier srcBarrier = (Barrier) src; 104 mBarrierType = srcBarrier.mBarrierType; 105 mAllowsGoneWidget = srcBarrier.mAllowsGoneWidget; 106 mMargin = srcBarrier.mMargin; 107 } 108 109 @Override toString()110 public String toString() { 111 String debug = "[Barrier] " + getDebugName() + " {"; 112 for (int i = 0; i < mWidgetsCount; i++) { 113 ConstraintWidget widget = mWidgets[i]; 114 if (i > 0) { 115 debug += ", "; 116 } 117 debug += widget.getDebugName(); 118 } 119 debug += "}"; 120 return debug; 121 } 122 markWidgets()123 protected void markWidgets() { 124 for (int i = 0; i < mWidgetsCount; i++) { 125 ConstraintWidget widget = mWidgets[i]; 126 if (!mAllowsGoneWidget && !widget.allowedInBarrier()) { 127 continue; 128 } 129 if (mBarrierType == LEFT || mBarrierType == RIGHT) { 130 widget.setInBarrier(HORIZONTAL, true); 131 } else if (mBarrierType == TOP || mBarrierType == BOTTOM) { 132 widget.setInBarrier(VERTICAL, true); 133 } 134 } 135 } 136 137 /** 138 * Add this widget to the solver 139 * 140 * @param system the solver we want to add the widget to 141 * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on 142 */ 143 @Override addToSolver(LinearSystem system, boolean optimize)144 public void addToSolver(LinearSystem system, boolean optimize) { 145 if (LinearSystem.FULL_DEBUG) { 146 System.out.println("\n----------------------------------------------"); 147 System.out.println("-- adding " + getDebugName() + " to the solver"); 148 System.out.println("----------------------------------------------\n"); 149 } 150 151 ConstraintAnchor position; 152 mListAnchors[LEFT] = mLeft; 153 mListAnchors[TOP] = mTop; 154 mListAnchors[RIGHT] = mRight; 155 mListAnchors[BOTTOM] = mBottom; 156 for (int i = 0; i < mListAnchors.length; i++) { 157 mListAnchors[i].mSolverVariable = system.createObjectVariable(mListAnchors[i]); 158 } 159 if (mBarrierType >= 0 && mBarrierType < 4) { 160 position = mListAnchors[mBarrierType]; 161 } else { 162 return; 163 } 164 165 if (USE_RESOLUTION) { 166 if (!mResolved) { 167 allSolved(); 168 } 169 if (mResolved) { 170 mResolved = false; 171 if (mBarrierType == LEFT || mBarrierType == RIGHT) { 172 system.addEquality(mLeft.mSolverVariable, mX); 173 system.addEquality(mRight.mSolverVariable, mX); 174 } else if (mBarrierType == TOP || mBarrierType == BOTTOM) { 175 system.addEquality(mTop.mSolverVariable, mY); 176 system.addEquality(mBottom.mSolverVariable, mY); 177 } 178 return; 179 } 180 } 181 182 // We have to handle the case where some of the elements 183 // referenced in the barrier are set as 184 // match_constraint; we have to take it in account to set the strength of the barrier. 185 boolean hasMatchConstraintWidgets = false; 186 for (int i = 0; i < mWidgetsCount; i++) { 187 ConstraintWidget widget = mWidgets[i]; 188 if (!mAllowsGoneWidget && !widget.allowedInBarrier()) { 189 continue; 190 } 191 if ((mBarrierType == LEFT || mBarrierType == RIGHT) 192 && (widget.getHorizontalDimensionBehaviour() 193 == DimensionBehaviour.MATCH_CONSTRAINT) 194 && widget.mLeft.mTarget != null && widget.mRight.mTarget != null) { 195 hasMatchConstraintWidgets = true; 196 break; 197 } else if ((mBarrierType == TOP || mBarrierType == BOTTOM) 198 && (widget.getVerticalDimensionBehaviour() 199 == DimensionBehaviour.MATCH_CONSTRAINT) 200 && widget.mTop.mTarget != null && widget.mBottom.mTarget != null) { 201 hasMatchConstraintWidgets = true; 202 break; 203 } 204 } 205 206 boolean mHasHorizontalCenteredDependents = 207 mLeft.hasCenteredDependents() || mRight.hasCenteredDependents(); 208 boolean mHasVerticalCenteredDependents = 209 mTop.hasCenteredDependents() || mBottom.hasCenteredDependents(); 210 boolean applyEqualityOnReferences = !hasMatchConstraintWidgets 211 && ((mBarrierType == LEFT && mHasHorizontalCenteredDependents) 212 || (mBarrierType == TOP && mHasVerticalCenteredDependents) 213 || (mBarrierType == RIGHT && mHasHorizontalCenteredDependents) 214 || (mBarrierType == BOTTOM && mHasVerticalCenteredDependents)); 215 216 int equalityOnReferencesStrength = SolverVariable.STRENGTH_EQUALITY; 217 if (!applyEqualityOnReferences) { 218 equalityOnReferencesStrength = SolverVariable.STRENGTH_HIGHEST; 219 } 220 for (int i = 0; i < mWidgetsCount; i++) { 221 ConstraintWidget widget = mWidgets[i]; 222 if (!mAllowsGoneWidget && !widget.allowedInBarrier()) { 223 continue; 224 } 225 SolverVariable target = system.createObjectVariable(widget.mListAnchors[mBarrierType]); 226 widget.mListAnchors[mBarrierType].mSolverVariable = target; 227 int margin = 0; 228 if (widget.mListAnchors[mBarrierType].mTarget != null 229 && widget.mListAnchors[mBarrierType].mTarget.mOwner == this) { 230 margin += widget.mListAnchors[mBarrierType].mMargin; 231 } 232 if (mBarrierType == LEFT || mBarrierType == TOP) { 233 system.addLowerBarrier(position.mSolverVariable, target, 234 mMargin - margin, hasMatchConstraintWidgets); 235 } else { 236 system.addGreaterBarrier(position.mSolverVariable, target, 237 mMargin + margin, hasMatchConstraintWidgets); 238 } 239 if (USE_RELAX_GONE) { 240 if (widget.getVisibility() != GONE 241 || widget instanceof Guideline || widget instanceof Barrier) { 242 system.addEquality(position.mSolverVariable, target, 243 mMargin + margin, equalityOnReferencesStrength); 244 } 245 } else { 246 system.addEquality(position.mSolverVariable, target, 247 mMargin + margin, equalityOnReferencesStrength); 248 } 249 } 250 251 int barrierParentStrength = SolverVariable.STRENGTH_HIGHEST; 252 int barrierParentStrengthOpposite = SolverVariable.STRENGTH_NONE; 253 254 if (mBarrierType == LEFT) { 255 system.addEquality(mRight.mSolverVariable, 256 mLeft.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED); 257 system.addEquality(mLeft.mSolverVariable, 258 mParent.mRight.mSolverVariable, 0, barrierParentStrength); 259 system.addEquality(mLeft.mSolverVariable, 260 mParent.mLeft.mSolverVariable, 0, barrierParentStrengthOpposite); 261 } else if (mBarrierType == RIGHT) { 262 system.addEquality(mLeft.mSolverVariable, 263 mRight.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED); 264 system.addEquality(mLeft.mSolverVariable, 265 mParent.mLeft.mSolverVariable, 0, barrierParentStrength); 266 system.addEquality(mLeft.mSolverVariable, 267 mParent.mRight.mSolverVariable, 0, barrierParentStrengthOpposite); 268 } else if (mBarrierType == TOP) { 269 system.addEquality(mBottom.mSolverVariable, 270 mTop.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED); 271 system.addEquality(mTop.mSolverVariable, 272 mParent.mBottom.mSolverVariable, 0, barrierParentStrength); 273 system.addEquality(mTop.mSolverVariable, 274 mParent.mTop.mSolverVariable, 0, barrierParentStrengthOpposite); 275 } else if (mBarrierType == BOTTOM) { 276 system.addEquality(mTop.mSolverVariable, 277 mBottom.mSolverVariable, 0, SolverVariable.STRENGTH_FIXED); 278 system.addEquality(mTop.mSolverVariable, 279 mParent.mTop.mSolverVariable, 0, barrierParentStrength); 280 system.addEquality(mTop.mSolverVariable, 281 mParent.mBottom.mSolverVariable, 0, barrierParentStrengthOpposite); 282 } 283 } 284 setMargin(int margin)285 public void setMargin(int margin) { 286 mMargin = margin; 287 } 288 getMargin()289 public int getMargin() { 290 return mMargin; 291 } 292 293 // @TODO: add description getOrientation()294 public int getOrientation() { 295 switch (mBarrierType) { 296 case LEFT: 297 case RIGHT: 298 return HORIZONTAL; 299 case TOP: 300 case BOTTOM: 301 return VERTICAL; 302 } 303 return UNKNOWN; 304 } 305 306 // @TODO: add description allSolved()307 public boolean allSolved() { 308 if (!USE_RESOLUTION) { 309 return false; 310 } 311 boolean hasAllWidgetsResolved = true; 312 for (int i = 0; i < mWidgetsCount; i++) { 313 ConstraintWidget widget = mWidgets[i]; 314 if (!mAllowsGoneWidget && !widget.allowedInBarrier()) { 315 continue; 316 } 317 if ((mBarrierType == LEFT || mBarrierType == RIGHT) 318 && !widget.isResolvedHorizontally()) { 319 hasAllWidgetsResolved = false; 320 } else if ((mBarrierType == TOP || mBarrierType == BOTTOM) 321 && !widget.isResolvedVertically()) { 322 hasAllWidgetsResolved = false; 323 } 324 } 325 326 if (hasAllWidgetsResolved && mWidgetsCount > 0) { 327 // we're done! 328 int barrierPosition = 0; 329 boolean initialized = false; 330 for (int i = 0; i < mWidgetsCount; i++) { 331 ConstraintWidget widget = mWidgets[i]; 332 if (!mAllowsGoneWidget && !widget.allowedInBarrier()) { 333 continue; 334 } 335 if (!initialized) { 336 if (mBarrierType == LEFT) { 337 barrierPosition = 338 widget.getAnchor(ConstraintAnchor.Type.LEFT).getFinalValue(); 339 } else if (mBarrierType == RIGHT) { 340 barrierPosition = 341 widget.getAnchor(ConstraintAnchor.Type.RIGHT).getFinalValue(); 342 } else if (mBarrierType == TOP) { 343 barrierPosition = 344 widget.getAnchor(ConstraintAnchor.Type.TOP).getFinalValue(); 345 } else if (mBarrierType == BOTTOM) { 346 barrierPosition = 347 widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getFinalValue(); 348 } 349 initialized = true; 350 } 351 if (mBarrierType == LEFT) { 352 barrierPosition = Math.min(barrierPosition, 353 widget.getAnchor(ConstraintAnchor.Type.LEFT).getFinalValue()); 354 } else if (mBarrierType == RIGHT) { 355 barrierPosition = Math.max(barrierPosition, 356 widget.getAnchor(ConstraintAnchor.Type.RIGHT).getFinalValue()); 357 } else if (mBarrierType == TOP) { 358 barrierPosition = Math.min(barrierPosition, 359 widget.getAnchor(ConstraintAnchor.Type.TOP).getFinalValue()); 360 } else if (mBarrierType == BOTTOM) { 361 barrierPosition = Math.max(barrierPosition, 362 widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getFinalValue()); 363 } 364 } 365 barrierPosition += mMargin; 366 if (mBarrierType == LEFT || mBarrierType == RIGHT) { 367 setFinalHorizontal(barrierPosition, barrierPosition); 368 } else { 369 setFinalVertical(barrierPosition, barrierPosition); 370 } 371 if (LinearSystem.FULL_DEBUG) { 372 System.out.println("*** BARRIER " + getDebugName() 373 + " SOLVED TO " + barrierPosition + " ***"); 374 } 375 mResolved = true; 376 return true; 377 } 378 return false; 379 } 380 } 381