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.HORIZONTAL;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT;
24 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO;
25 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
26 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
27 import static androidx.constraintlayout.core.widgets.ConstraintWidget.UNKNOWN;
28 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
29 import static androidx.constraintlayout.core.widgets.analyzer.WidgetRun.RunType.CENTER;
30 
31 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
32 import androidx.constraintlayout.core.widgets.ConstraintWidget;
33 import androidx.constraintlayout.core.widgets.Helper;
34 
35 public class HorizontalWidgetRun extends WidgetRun {
36 
37     private static int[] sTempDimensions = new int[2];
38 
HorizontalWidgetRun(ConstraintWidget widget)39     public HorizontalWidgetRun(ConstraintWidget widget) {
40         super(widget);
41         start.mType = DependencyNode.Type.LEFT;
42         end.mType = DependencyNode.Type.RIGHT;
43         this.orientation = HORIZONTAL;
44     }
45 
46     @Override
toString()47     public String toString() {
48         return "HorizontalRun " + mWidget.getDebugName();
49     }
50 
51     @Override
clear()52     void clear() {
53         mRunGroup = null;
54         start.clear();
55         end.clear();
56         mDimension.clear();
57         mResolved = false;
58     }
59 
60     @Override
reset()61     void reset() {
62         mResolved = false;
63         start.clear();
64         start.resolved = false;
65         end.clear();
66         end.resolved = false;
67         mDimension.resolved = false;
68     }
69 
70     @Override
supportsWrapComputation()71     boolean supportsWrapComputation() {
72         if (super.mDimensionBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
73             if (super.mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
74                 return true;
75             }
76             return false;
77         }
78         return true;
79     }
80 
81     @Override
apply()82     void apply() {
83         if (mWidget.measured) {
84             mDimension.resolve(mWidget.getWidth());
85         }
86         if (!mDimension.resolved) {
87             super.mDimensionBehavior = mWidget.getHorizontalDimensionBehaviour();
88             if (super.mDimensionBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
89                 if (mDimensionBehavior == MATCH_PARENT) {
90                     ConstraintWidget parent = mWidget.getParent();
91                     if (parent != null
92                             && (parent.getHorizontalDimensionBehaviour() == FIXED
93                             || parent.getHorizontalDimensionBehaviour() == MATCH_PARENT)) {
94                         int resolvedDimension = parent.getWidth()
95                                 - mWidget.mLeft.getMargin() - mWidget.mRight.getMargin();
96                         addTarget(start, parent.mHorizontalRun.start, mWidget.mLeft.getMargin());
97                         addTarget(end, parent.mHorizontalRun.end, -mWidget.mRight.getMargin());
98                         mDimension.resolve(resolvedDimension);
99                         return;
100                     }
101                 }
102                 if (mDimensionBehavior == FIXED) {
103                     mDimension.resolve(mWidget.getWidth());
104                 }
105             }
106         } else {
107             if (mDimensionBehavior == MATCH_PARENT) {
108                 ConstraintWidget parent = mWidget.getParent();
109                 if (parent != null
110                         && (parent.getHorizontalDimensionBehaviour() == FIXED
111                         || parent.getHorizontalDimensionBehaviour() == MATCH_PARENT)) {
112                     addTarget(start, parent.mHorizontalRun.start, mWidget.mLeft.getMargin());
113                     addTarget(end, parent.mHorizontalRun.end, -mWidget.mRight.getMargin());
114                     return;
115                 }
116             }
117         }
118 
119         // three basic possibilities:
120         // <-s-e->
121         // <-s-e
122         //   s-e->
123         // and a variation if the dimension is not yet known:
124         // <-s-d-e->
125         // <-s<-d<-e
126         //   s->d->e->
127 
128         if (mDimension.resolved && mWidget.measured) {
129             if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget != null
130                     && mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
131                     != null) { // <-s-e->
132                 if (mWidget.isInHorizontalChain()) {
133                     start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin();
134                     end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin();
135                 } else {
136                     DependencyNode startTarget =
137                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
138                     if (startTarget != null) {
139                         addTarget(start, startTarget,
140                                 mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
141                     }
142                     DependencyNode endTarget =
143                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
144                     if (endTarget != null) {
145                         addTarget(end, endTarget,
146                                 -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin());
147                     }
148                     start.delegateToWidgetRun = true;
149                     end.delegateToWidgetRun = true;
150                 }
151             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget
152                     != null) { // <-s-e
153                 DependencyNode target =
154                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
155                 if (target != null) {
156                     addTarget(start, target,
157                             mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
158                     addTarget(end, start, mDimension.value);
159                 }
160             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
161                     != null) {   //   s-e->
162                 DependencyNode target =
163                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
164                 if (target != null) {
165                     addTarget(end, target,
166                             -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin());
167                     addTarget(start, end, -mDimension.value);
168                 }
169             } else {
170                 // no connections, nothing to do.
171                 if (!(mWidget instanceof Helper) && mWidget.getParent() != null
172                         && mWidget.getAnchor(ConstraintAnchor.Type.CENTER).mTarget == null) {
173                     DependencyNode left = mWidget.getParent().mHorizontalRun.start;
174                     addTarget(start, left, mWidget.getX());
175                     addTarget(end, start, mDimension.value);
176                 }
177             }
178         } else {
179             if (mDimensionBehavior == MATCH_CONSTRAINT) {
180                 switch (mWidget.mMatchConstraintDefaultWidth) {
181                     case MATCH_CONSTRAINT_RATIO: {
182                         if (mWidget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO
183                         ) {
184                             // need to look into both side
185                             start.updateDelegate = this;
186                             end.updateDelegate = this;
187                             mWidget.mVerticalRun.start.updateDelegate = this;
188                             mWidget.mVerticalRun.end.updateDelegate = this;
189                             mDimension.updateDelegate = this;
190 
191                             if (mWidget.isInVerticalChain()) {
192                                 mDimension.mTargets.add(mWidget.mVerticalRun.mDimension);
193                                 mWidget.mVerticalRun.mDimension.mDependencies.add(mDimension);
194                                 mWidget.mVerticalRun.mDimension.updateDelegate = this;
195                                 mDimension.mTargets.add(mWidget.mVerticalRun.start);
196                                 mDimension.mTargets.add(mWidget.mVerticalRun.end);
197                                 mWidget.mVerticalRun.start.mDependencies.add(mDimension);
198                                 mWidget.mVerticalRun.end.mDependencies.add(mDimension);
199                             } else if (mWidget.isInHorizontalChain()) {
200                                 mWidget.mVerticalRun.mDimension.mTargets.add(mDimension);
201                                 mDimension.mDependencies.add(mWidget.mVerticalRun.mDimension);
202                             } else {
203                                 mWidget.mVerticalRun.mDimension.mTargets.add(mDimension);
204                             }
205                             break;
206                         }
207                         // we have a ratio, but we depend on the other side computation
208                         DependencyNode targetDimension = mWidget.mVerticalRun.mDimension;
209                         mDimension.mTargets.add(targetDimension);
210                         targetDimension.mDependencies.add(mDimension);
211                         mWidget.mVerticalRun.start.mDependencies.add(mDimension);
212                         mWidget.mVerticalRun.end.mDependencies.add(mDimension);
213                         mDimension.delegateToWidgetRun = true;
214                         mDimension.mDependencies.add(start);
215                         mDimension.mDependencies.add(end);
216                         start.mTargets.add(mDimension);
217                         end.mTargets.add(mDimension);
218                     }
219                     break;
220                     case MATCH_CONSTRAINT_PERCENT: {
221                         // we need to look up the parent dimension
222                         ConstraintWidget parent = mWidget.getParent();
223                         if (parent == null) {
224                             break;
225                         }
226                         DependencyNode targetDimension = parent.mVerticalRun.mDimension;
227                         mDimension.mTargets.add(targetDimension);
228                         targetDimension.mDependencies.add(mDimension);
229                         mDimension.delegateToWidgetRun = true;
230                         mDimension.mDependencies.add(start);
231                         mDimension.mDependencies.add(end);
232                     }
233                     break;
234                     case MATCH_CONSTRAINT_SPREAD: {
235                         // the work is done in the update()
236                     }
237                     break;
238                     default:
239                         break;
240                 }
241             }
242             if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget != null
243                     && mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
244                     != null) { // <-s-d-e->
245 
246                 if (mWidget.isInHorizontalChain()) {
247                     start.mMargin = mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin();
248                     end.mMargin = -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin();
249                 } else {
250                     DependencyNode startTarget =
251                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
252                     DependencyNode endTarget =
253                             getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
254                     if (false) {
255                         if (startTarget != null) {
256                             addTarget(start, startTarget,
257                                     mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
258                         }
259                         if (endTarget != null) {
260                             addTarget(end, endTarget,
261                                     -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]
262                                             .getMargin());
263                         }
264                     } else {
265                         if (startTarget != null) {
266                             startTarget.addDependency(this);
267                         }
268                         if (endTarget != null) {
269                             endTarget.addDependency(this);
270                         }
271                     }
272                     mRunType = CENTER;
273                 }
274             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget
275                     != null) { // <-s<-d<-e
276                 DependencyNode target =
277                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT]);
278                 if (target != null) {
279                     addTarget(start, target,
280                             mWidget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].getMargin());
281                     addTarget(end, start, 1, mDimension);
282                 }
283             } else if (mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget
284                     != null) {   //   s->d->e->
285                 DependencyNode target =
286                         getTarget(mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT]);
287                 if (target != null) {
288                     addTarget(end, target,
289                             -mWidget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].getMargin());
290                     addTarget(start, end, -1, mDimension);
291                 }
292             } else {
293                 // no connections, nothing to do.
294                 if (!(mWidget instanceof Helper) && mWidget.getParent() != null) {
295                     DependencyNode left = mWidget.getParent().mHorizontalRun.start;
296                     addTarget(start, left, mWidget.getX());
297                     addTarget(end, start, 1, mDimension);
298                 }
299             }
300         }
301     }
302 
computeInsetRatio(int[] dimensions, int x1, int x2, int y1, int y2, float ratio, int side)303     private void computeInsetRatio(int[] dimensions,
304             int x1,
305             int x2,
306             int y1,
307             int y2,
308             float ratio,
309             int side) {
310         int dx = x2 - x1;
311         int dy = y2 - y1;
312         switch (side) {
313             case UNKNOWN: {
314                 int candidateX1 = (int) (0.5f + dy * ratio);
315                 int candidateY1 = dy;
316                 int candidateX2 = dx;
317                 int candidateY2 = (int) (0.5f + dx / ratio);
318                 if (candidateX1 <= dx && candidateY1 <= dy) {
319                     dimensions[HORIZONTAL] = candidateX1;
320                     dimensions[VERTICAL] = candidateY1;
321                 } else if (candidateX2 <= dx && candidateY2 <= dy) {
322                     dimensions[HORIZONTAL] = candidateX2;
323                     dimensions[VERTICAL] = candidateY2;
324                 }
325             }
326             break;
327             case HORIZONTAL: {
328                 int horizontalSide = (int) (0.5f + dy * ratio);
329                 dimensions[HORIZONTAL] = horizontalSide;
330                 dimensions[VERTICAL] = dy;
331             }
332             break;
333             case VERTICAL: {
334                 int verticalSide = (int) (0.5f + dx * ratio);
335                 dimensions[HORIZONTAL] = dx;
336                 dimensions[VERTICAL] = verticalSide;
337             }
338             break;
339             default:
340                 break;
341         }
342     }
343 
344     @Override
update(Dependency dependency)345     public void update(Dependency dependency) {
346         switch (mRunType) {
347             case START: {
348                 updateRunStart(dependency);
349             }
350             break;
351             case END: {
352                 updateRunEnd(dependency);
353             }
354             break;
355             case CENTER: {
356                 updateRunCenter(dependency, mWidget.mLeft, mWidget.mRight, HORIZONTAL);
357                 return;
358             }
359             default:
360                 break;
361         }
362 
363         if (!mDimension.resolved) {
364             if (mDimensionBehavior == MATCH_CONSTRAINT) {
365                 switch (mWidget.mMatchConstraintDefaultWidth) {
366                     case MATCH_CONSTRAINT_RATIO: {
367                         if (mWidget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
368                                 || mWidget.mMatchConstraintDefaultHeight
369                                 == MATCH_CONSTRAINT_RATIO) {
370                             DependencyNode secondStart = mWidget.mVerticalRun.start;
371                             DependencyNode secondEnd = mWidget.mVerticalRun.end;
372                             boolean s1 = mWidget.mLeft.mTarget != null;
373                             boolean s2 = mWidget.mTop.mTarget != null;
374                             boolean e1 = mWidget.mRight.mTarget != null;
375                             boolean e2 = mWidget.mBottom.mTarget != null;
376 
377                             int definedSide = mWidget.getDimensionRatioSide();
378 
379                             if (s1 && s2 && e1 && e2) {
380                                 float ratio = mWidget.getDimensionRatio();
381                                 if (secondStart.resolved && secondEnd.resolved) {
382                                     if (!(start.readyToSolve && end.readyToSolve)) {
383                                         return;
384                                     }
385                                     int x1 = start.mTargets.get(0).value + start.mMargin;
386                                     int x2 = end.mTargets.get(0).value - end.mMargin;
387                                     int y1 = secondStart.value + secondStart.mMargin;
388                                     int y2 = secondEnd.value - secondEnd.mMargin;
389                                     computeInsetRatio(sTempDimensions,
390                                             x1, x2, y1, y2, ratio, definedSide);
391                                     mDimension.resolve(sTempDimensions[HORIZONTAL]);
392                                     mWidget.mVerticalRun.mDimension
393                                             .resolve(sTempDimensions[VERTICAL]);
394                                     return;
395                                 }
396                                 if (start.resolved && end.resolved) {
397                                     if (!(secondStart.readyToSolve && secondEnd.readyToSolve)) {
398                                         return;
399                                     }
400                                     int x1 = start.value + start.mMargin;
401                                     int x2 = end.value - end.mMargin;
402                                     int y1 = secondStart.mTargets.get(0).value
403                                             + secondStart.mMargin;
404                                     int y2 = secondEnd.mTargets.get(0).value - secondEnd.mMargin;
405                                     computeInsetRatio(sTempDimensions,
406                                             x1, x2, y1, y2, ratio, definedSide);
407                                     mDimension.resolve(sTempDimensions[HORIZONTAL]);
408                                     mWidget.mVerticalRun.mDimension
409                                             .resolve(sTempDimensions[VERTICAL]);
410                                 }
411                                 if (!(start.readyToSolve && end.readyToSolve
412                                         && secondStart.readyToSolve
413                                         && secondEnd.readyToSolve)) {
414                                     return;
415                                 }
416                                 int x1 = start.mTargets.get(0).value + start.mMargin;
417                                 int x2 = end.mTargets.get(0).value - end.mMargin;
418                                 int y1 = secondStart.mTargets.get(0).value + secondStart.mMargin;
419                                 int y2 = secondEnd.mTargets.get(0).value - secondEnd.mMargin;
420                                 computeInsetRatio(sTempDimensions,
421                                         x1, x2, y1, y2, ratio, definedSide);
422                                 mDimension.resolve(sTempDimensions[HORIZONTAL]);
423                                 mWidget.mVerticalRun.mDimension.resolve(sTempDimensions[VERTICAL]);
424                             } else if (s1 && e1) {
425                                 if (!(start.readyToSolve && end.readyToSolve)) {
426                                     return;
427                                 }
428                                 float ratio = mWidget.getDimensionRatio();
429                                 int x1 = start.mTargets.get(0).value + start.mMargin;
430                                 int x2 = end.mTargets.get(0).value - end.mMargin;
431 
432                                 switch (definedSide) {
433                                     case UNKNOWN:
434                                     case HORIZONTAL: {
435                                         int dx = x2 - x1;
436                                         int ldx = getLimitedDimension(dx, HORIZONTAL);
437                                         int dy = (int) (0.5f + ldx * ratio);
438                                         int ldy = getLimitedDimension(dy, VERTICAL);
439                                         if (dy != ldy) {
440                                             ldx = (int) (0.5f + ldy / ratio);
441                                         }
442                                         mDimension.resolve(ldx);
443                                         mWidget.mVerticalRun.mDimension.resolve(ldy);
444                                     }
445                                     break;
446                                     case VERTICAL: {
447                                         int dx = x2 - x1;
448                                         int ldx = getLimitedDimension(dx, HORIZONTAL);
449                                         int dy = (int) (0.5f + ldx / ratio);
450                                         int ldy = getLimitedDimension(dy, VERTICAL);
451                                         if (dy != ldy) {
452                                             ldx = (int) (0.5f + ldy * ratio);
453                                         }
454                                         mDimension.resolve(ldx);
455                                         mWidget.mVerticalRun.mDimension.resolve(ldy);
456                                     }
457                                     break;
458                                     default:
459                                         break;
460                                 }
461                             } else if (s2 && e2) {
462                                 if (!(secondStart.readyToSolve && secondEnd.readyToSolve)) {
463                                     return;
464                                 }
465                                 float ratio = mWidget.getDimensionRatio();
466                                 int y1 = secondStart.mTargets.get(0).value + secondStart.mMargin;
467                                 int y2 = secondEnd.mTargets.get(0).value - secondEnd.mMargin;
468 
469                                 switch (definedSide) {
470                                     case UNKNOWN:
471                                     case VERTICAL: {
472                                         int dy = y2 - y1;
473                                         int ldy = getLimitedDimension(dy, VERTICAL);
474                                         int dx = (int) (0.5f + ldy / ratio);
475                                         int ldx = getLimitedDimension(dx, HORIZONTAL);
476                                         if (dx != ldx) {
477                                             ldy = (int) (0.5f + ldx * ratio);
478                                         }
479                                         mDimension.resolve(ldx);
480                                         mWidget.mVerticalRun.mDimension.resolve(ldy);
481                                     }
482                                     break;
483                                     case HORIZONTAL: {
484                                         int dy = y2 - y1;
485                                         int ldy = getLimitedDimension(dy, VERTICAL);
486                                         int dx = (int) (0.5f + ldy * ratio);
487                                         int ldx = getLimitedDimension(dx, HORIZONTAL);
488                                         if (dx != ldx) {
489                                             ldy = (int) (0.5f + ldx / ratio);
490                                         }
491                                         mDimension.resolve(ldx);
492                                         mWidget.mVerticalRun.mDimension.resolve(ldy);
493                                     }
494                                     break;
495                                     default:
496                                         break;
497                                 }
498                             }
499                         } else {
500                             int size = 0;
501                             int ratioSide = mWidget.getDimensionRatioSide();
502                             switch (ratioSide) {
503                                 case HORIZONTAL: {
504                                     size = (int) (0.5f + mWidget.mVerticalRun.mDimension.value
505                                             / mWidget.getDimensionRatio());
506                                 }
507                                 break;
508                                 case ConstraintWidget.VERTICAL: {
509                                     size = (int) (0.5f + mWidget.mVerticalRun.mDimension.value
510                                             * mWidget.getDimensionRatio());
511                                 }
512                                 break;
513                                 case ConstraintWidget.UNKNOWN: {
514                                     size = (int) (0.5f + mWidget.mVerticalRun.mDimension.value
515                                             * mWidget.getDimensionRatio());
516                                 }
517                                 break;
518                                 default:
519                                     break;
520                             }
521                             mDimension.resolve(size);
522                         }
523                     }
524                     break;
525                     case MATCH_CONSTRAINT_PERCENT: {
526                         ConstraintWidget parent = mWidget.getParent();
527                         if (parent != null) {
528                             if (parent.mHorizontalRun.mDimension.resolved) {
529                                 float percent = mWidget.mMatchConstraintPercentWidth;
530                                 int targetDimensionValue = parent.mHorizontalRun.mDimension.value;
531                                 int size = (int) (0.5f + targetDimensionValue * percent);
532                                 mDimension.resolve(size);
533                             }
534                         }
535                     }
536                     break;
537                     default:
538                         break;
539                 }
540             }
541         }
542 
543         if (!(start.readyToSolve && end.readyToSolve)) {
544             return;
545         }
546 
547         if (start.resolved && end.resolved && mDimension.resolved) {
548             return;
549         }
550 
551         if (!mDimension.resolved
552                 && mDimensionBehavior == MATCH_CONSTRAINT
553                 && mWidget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
554                 && !mWidget.isInHorizontalChain()) {
555 
556             DependencyNode startTarget = start.mTargets.get(0);
557             DependencyNode endTarget = end.mTargets.get(0);
558             int startPos = startTarget.value + start.mMargin;
559             int endPos = endTarget.value + end.mMargin;
560 
561             int distance = endPos - startPos;
562             start.resolve(startPos);
563             end.resolve(endPos);
564             mDimension.resolve(distance);
565             return;
566         }
567 
568         if (!mDimension.resolved
569                 && mDimensionBehavior == MATCH_CONSTRAINT
570                 && matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
571             if (start.mTargets.size() > 0 && end.mTargets.size() > 0) {
572                 DependencyNode startTarget = start.mTargets.get(0);
573                 DependencyNode endTarget = end.mTargets.get(0);
574                 int startPos = startTarget.value + start.mMargin;
575                 int endPos = endTarget.value + end.mMargin;
576                 int availableSpace = endPos - startPos;
577                 int value = Math.min(availableSpace, mDimension.wrapValue);
578                 int max = mWidget.mMatchConstraintMaxWidth;
579                 int min = mWidget.mMatchConstraintMinWidth;
580                 value = Math.max(min, value);
581                 if (max > 0) {
582                     value = Math.min(max, value);
583                 }
584                 mDimension.resolve(value);
585             }
586         }
587 
588         if (!mDimension.resolved) {
589             return;
590         }
591         // ready to solve, centering.
592         DependencyNode startTarget = start.mTargets.get(0);
593         DependencyNode endTarget = end.mTargets.get(0);
594         int startPos = startTarget.value + start.mMargin;
595         int endPos = endTarget.value + end.mMargin;
596         float bias = mWidget.getHorizontalBiasPercent();
597         if (startTarget == endTarget) {
598             startPos = startTarget.value;
599             endPos = endTarget.value;
600             // TODO: this might be a nice feature to support, but I guess for now let's stay
601             // compatible with 1.1
602             bias = 0.5f;
603         }
604         int distance = (endPos - startPos - mDimension.value);
605         start.resolve((int) (0.5f + startPos + distance * bias));
606         end.resolve(start.value + mDimension.value);
607     }
608 
609     // @TODO: add description
610     @Override
applyToWidget()611     public void applyToWidget() {
612         if (start.resolved) {
613             mWidget.setX(start.value);
614         }
615     }
616 
617 }
618