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;
18 
19 import static androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.AT_MOST;
20 import static androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.EXACTLY;
21 import static androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.UNSPECIFIED;
22 
23 import androidx.constraintlayout.core.LinearSystem;
24 
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 
29 /**
30  * Implements the Flow virtual layout.
31  */
32 public class Flow extends VirtualLayout {
33 
34     public static final int HORIZONTAL_ALIGN_START = 0;
35     public static final int HORIZONTAL_ALIGN_END = 1;
36     public static final int HORIZONTAL_ALIGN_CENTER = 2;
37 
38     public static final int VERTICAL_ALIGN_TOP = 0;
39     public static final int VERTICAL_ALIGN_BOTTOM = 1;
40     public static final int VERTICAL_ALIGN_CENTER = 2;
41     public static final int VERTICAL_ALIGN_BASELINE = 3;
42 
43     public static final int WRAP_NONE = 0;
44     public static final int WRAP_CHAIN = 1;
45     public static final int WRAP_ALIGNED = 2;
46     public static final int WRAP_CHAIN_NEW = 3;
47 
48     private int mHorizontalStyle = UNKNOWN;
49     private int mVerticalStyle = UNKNOWN;
50     private int mFirstHorizontalStyle = UNKNOWN;
51     private int mFirstVerticalStyle = UNKNOWN;
52     private int mLastHorizontalStyle = UNKNOWN;
53     private int mLastVerticalStyle = UNKNOWN;
54 
55     private float mHorizontalBias = 0.5f;
56     private float mVerticalBias = 0.5f;
57     private float mFirstHorizontalBias = 0.5f;
58     private float mFirstVerticalBias = 0.5f;
59     private float mLastHorizontalBias = 0.5f;
60     private float mLastVerticalBias = 0.5f;
61 
62     private int mHorizontalGap = 0;
63     private int mVerticalGap = 0;
64 
65     private int mHorizontalAlign = HORIZONTAL_ALIGN_CENTER;
66     private int mVerticalAlign = VERTICAL_ALIGN_CENTER;
67     private int mWrapMode = WRAP_NONE;
68 
69     private int mMaxElementsWrap = UNKNOWN;
70 
71     private int mOrientation = HORIZONTAL;
72 
73     private ArrayList<WidgetsList> mChainList = new ArrayList<>();
74 
75     // Aligned management
76 
77     private ConstraintWidget[] mAlignedBiggestElementsInRows = null;
78     private ConstraintWidget[] mAlignedBiggestElementsInCols = null;
79     private int[] mAlignedDimensions = null;
80     private ConstraintWidget[] mDisplayedWidgets;
81     private int mDisplayedWidgetsCount = 0;
82 
83 
84     @Override
copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map)85     public void copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map) {
86         super.copy(src, map);
87         Flow srcFLow = (Flow) src;
88 
89         mHorizontalStyle = srcFLow.mHorizontalStyle;
90         mVerticalStyle = srcFLow.mVerticalStyle;
91         mFirstHorizontalStyle = srcFLow.mFirstHorizontalStyle;
92         mFirstVerticalStyle = srcFLow.mFirstVerticalStyle;
93         mLastHorizontalStyle = srcFLow.mLastHorizontalStyle;
94         mLastVerticalStyle = srcFLow.mLastVerticalStyle;
95 
96         mHorizontalBias = srcFLow.mHorizontalBias;
97         mVerticalBias = srcFLow.mVerticalBias;
98         mFirstHorizontalBias = srcFLow.mFirstHorizontalBias;
99         mFirstVerticalBias = srcFLow.mFirstVerticalBias;
100         mLastHorizontalBias = srcFLow.mLastHorizontalBias;
101         mLastVerticalBias = srcFLow.mLastVerticalBias;
102 
103         mHorizontalGap = srcFLow.mHorizontalGap;
104         mVerticalGap = srcFLow.mVerticalGap;
105 
106         mHorizontalAlign = srcFLow.mHorizontalAlign;
107         mVerticalAlign = srcFLow.mVerticalAlign;
108         mWrapMode = srcFLow.mWrapMode;
109 
110         mMaxElementsWrap = srcFLow.mMaxElementsWrap;
111 
112         mOrientation = srcFLow.mOrientation;
113     }
114 
115     /////////////////////////////////////////////////////////////////////////////////////////////
116     // Accessors
117     /////////////////////////////////////////////////////////////////////////////////////////////
118 
setOrientation(int value)119     public void setOrientation(int value) {
120         mOrientation = value;
121     }
122 
setFirstHorizontalStyle(int value)123     public void setFirstHorizontalStyle(int value) {
124         mFirstHorizontalStyle = value;
125     }
126 
setFirstVerticalStyle(int value)127     public void setFirstVerticalStyle(int value) {
128         mFirstVerticalStyle = value;
129     }
130 
setLastHorizontalStyle(int value)131     public void setLastHorizontalStyle(int value) {
132         mLastHorizontalStyle = value;
133     }
134 
setLastVerticalStyle(int value)135     public void setLastVerticalStyle(int value) {
136         mLastVerticalStyle = value;
137     }
138 
setHorizontalStyle(int value)139     public void setHorizontalStyle(int value) {
140         mHorizontalStyle = value;
141     }
142 
setVerticalStyle(int value)143     public void setVerticalStyle(int value) {
144         mVerticalStyle = value;
145     }
146 
setHorizontalBias(float value)147     public void setHorizontalBias(float value) {
148         mHorizontalBias = value;
149     }
150 
setVerticalBias(float value)151     public void setVerticalBias(float value) {
152         mVerticalBias = value;
153     }
154 
setFirstHorizontalBias(float value)155     public void setFirstHorizontalBias(float value) {
156         mFirstHorizontalBias = value;
157     }
158 
setFirstVerticalBias(float value)159     public void setFirstVerticalBias(float value) {
160         mFirstVerticalBias = value;
161     }
162 
setLastHorizontalBias(float value)163     public void setLastHorizontalBias(float value) {
164         mLastHorizontalBias = value;
165     }
166 
setLastVerticalBias(float value)167     public void setLastVerticalBias(float value) {
168         mLastVerticalBias = value;
169     }
170 
setHorizontalAlign(int value)171     public void setHorizontalAlign(int value) {
172         mHorizontalAlign = value;
173     }
174 
setVerticalAlign(int value)175     public void setVerticalAlign(int value) {
176         mVerticalAlign = value;
177     }
178 
setWrapMode(int value)179     public void setWrapMode(int value) {
180         mWrapMode = value;
181     }
182 
setHorizontalGap(int value)183     public void setHorizontalGap(int value) {
184         mHorizontalGap = value;
185     }
186 
setVerticalGap(int value)187     public void setVerticalGap(int value) {
188         mVerticalGap = value;
189     }
190 
setMaxElementsWrap(int value)191     public void setMaxElementsWrap(int value) {
192         mMaxElementsWrap = value;
193     }
194 
getMaxElementsWrap()195     public float getMaxElementsWrap() {
196         return mMaxElementsWrap;
197     }
198     /////////////////////////////////////////////////////////////////////////////////////////////
199     // Utility methods
200     /////////////////////////////////////////////////////////////////////////////////////////////
201 
getWidgetWidth(ConstraintWidget widget, int max)202     private int getWidgetWidth(ConstraintWidget widget, int max) {
203         if (widget == null) {
204             return 0;
205         }
206         if (widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
207             if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
208                 return 0;
209             } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_PERCENT) {
210                 int value = (int) (widget.mMatchConstraintPercentWidth * max);
211                 if (value != widget.getWidth()) {
212                     widget.setMeasureRequested(true);
213                     measure(widget, DimensionBehaviour.FIXED, value,
214                             widget.getVerticalDimensionBehaviour(), widget.getHeight());
215                 }
216                 return value;
217             } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
218                 return widget.getWidth();
219             } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
220                 return (int) (widget.getHeight() * widget.mDimensionRatio + 0.5f);
221             }
222         }
223         return widget.getWidth();
224     }
225 
getWidgetHeight(ConstraintWidget widget, int max)226     private int getWidgetHeight(ConstraintWidget widget, int max) {
227         if (widget == null) {
228             return 0;
229         }
230         if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
231             if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
232                 return 0;
233             } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_PERCENT) {
234                 int value = (int) (widget.mMatchConstraintPercentHeight * max);
235                 if (value != widget.getHeight()) {
236                     widget.setMeasureRequested(true);
237                     measure(widget, widget.getHorizontalDimensionBehaviour(),
238                             widget.getWidth(), DimensionBehaviour.FIXED, value);
239                 }
240                 return value;
241             } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
242                 return widget.getHeight();
243             } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) {
244                 return (int) (widget.getWidth() * widget.mDimensionRatio + 0.5f);
245             }
246         }
247         return widget.getHeight();
248     }
249 
250     /////////////////////////////////////////////////////////////////////////////////////////////
251     // Measure
252     /////////////////////////////////////////////////////////////////////////////////////////////
253 
254     // @TODO: add description
255     @Override
measure(int widthMode, int widthSize, int heightMode, int heightSize)256     public void measure(int widthMode, int widthSize, int heightMode, int heightSize) {
257         if (mWidgetsCount > 0 && !measureChildren()) {
258             setMeasure(0, 0);
259             needsCallbackFromSolver(false);
260             return;
261         }
262 
263         @SuppressWarnings("unused") int width = 0;
264         @SuppressWarnings("unused") int height = 0;
265         int paddingLeft = getPaddingLeft();
266         int paddingRight = getPaddingRight();
267         int paddingTop = getPaddingTop();
268         int paddingBottom = getPaddingBottom();
269 
270         int[] measured = new int[2];
271         int max = widthSize - paddingLeft - paddingRight;
272         if (mOrientation == VERTICAL) {
273             max = heightSize - paddingTop - paddingBottom;
274         }
275 
276         if (mOrientation == HORIZONTAL) {
277             if (mHorizontalStyle == UNKNOWN) {
278                 mHorizontalStyle = CHAIN_SPREAD;
279             }
280             if (mVerticalStyle == UNKNOWN) {
281                 mVerticalStyle = CHAIN_SPREAD;
282             }
283         } else {
284             if (mHorizontalStyle == UNKNOWN) {
285                 mHorizontalStyle = CHAIN_SPREAD;
286             }
287             if (mVerticalStyle == UNKNOWN) {
288                 mVerticalStyle = CHAIN_SPREAD;
289             }
290         }
291 
292         ConstraintWidget[] widgets = mWidgets;
293 
294         int gone = 0;
295         for (int i = 0; i < mWidgetsCount; i++) {
296             ConstraintWidget widget = mWidgets[i];
297             if (widget.getVisibility() == GONE) {
298                 gone++;
299             }
300         }
301         int count = mWidgetsCount;
302         if (gone > 0) {
303             widgets = new ConstraintWidget[mWidgetsCount - gone];
304             int j = 0;
305             for (int i = 0; i < mWidgetsCount; i++) {
306                 ConstraintWidget widget = mWidgets[i];
307                 if (widget.getVisibility() != GONE) {
308                     widgets[j] = widget;
309                     j++;
310                 }
311             }
312             count = j;
313         }
314         mDisplayedWidgets = widgets;
315         mDisplayedWidgetsCount = count;
316         switch (mWrapMode) {
317             case WRAP_ALIGNED: {
318                 measureAligned(widgets, count, mOrientation, max, measured);
319             }
320             break;
321             case WRAP_CHAIN: {
322                 measureChainWrap(widgets, count, mOrientation, max, measured);
323             }
324             break;
325             case WRAP_NONE: {
326                 measureNoWrap(widgets, count, mOrientation, max, measured);
327             }
328             break;
329             case WRAP_CHAIN_NEW: {
330                 measureChainWrap_new(widgets, count, mOrientation, max, measured);
331             }
332             break;
333 
334         }
335 
336         width = measured[HORIZONTAL] + paddingLeft + paddingRight;
337         height = measured[VERTICAL] + paddingTop + paddingBottom;
338 
339         int measuredWidth = 0;
340         int measuredHeight = 0;
341 
342         if (widthMode == EXACTLY) {
343             measuredWidth = widthSize;
344         } else if (widthMode == AT_MOST) {
345             measuredWidth = Math.min(width, widthSize);
346         } else if (widthMode == UNSPECIFIED) {
347             measuredWidth = width;
348         }
349 
350         if (heightMode == EXACTLY) {
351             measuredHeight = heightSize;
352         } else if (heightMode == AT_MOST) {
353             measuredHeight = Math.min(height, heightSize);
354         } else if (heightMode == UNSPECIFIED) {
355             measuredHeight = height;
356         }
357 
358         setMeasure(measuredWidth, measuredHeight);
359         setWidth(measuredWidth);
360         setHeight(measuredHeight);
361         needsCallbackFromSolver(mWidgetsCount > 0);
362     }
363 
364     /////////////////////////////////////////////////////////////////////////////////////////////
365     // Utility class representing a single chain
366     /////////////////////////////////////////////////////////////////////////////////////////////
367 
368     private class WidgetsList {
369         private int mOrientation = HORIZONTAL;
370         private ConstraintWidget mBiggest = null;
371         int mBiggestDimension = 0;
372         private ConstraintAnchor mLeft;
373         private ConstraintAnchor mTop;
374         private ConstraintAnchor mRight;
375         private ConstraintAnchor mBottom;
376         private int mPaddingLeft = 0;
377         private int mPaddingTop = 0;
378         private int mPaddingRight = 0;
379         private int mPaddingBottom = 0;
380         private int mWidth = 0;
381         private int mHeight = 0;
382         private int mStartIndex = 0;
383         private int mCount = 0;
384         private int mNbMatchConstraintsWidgets = 0;
385         private int mMax = 0;
386 
WidgetsList(int orientation, ConstraintAnchor left, ConstraintAnchor top, ConstraintAnchor right, ConstraintAnchor bottom, int max)387         WidgetsList(int orientation,
388                 ConstraintAnchor left, ConstraintAnchor top,
389                 ConstraintAnchor right, ConstraintAnchor bottom,
390                 int max) {
391             mOrientation = orientation;
392             mLeft = left;
393             mTop = top;
394             mRight = right;
395             mBottom = bottom;
396             mPaddingLeft = getPaddingLeft();
397             mPaddingTop = getPaddingTop();
398             mPaddingRight = getPaddingRight();
399             mPaddingBottom = getPaddingBottom();
400             mMax = max;
401         }
402 
setup(int orientation, ConstraintAnchor left, ConstraintAnchor top, ConstraintAnchor right, ConstraintAnchor bottom, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom, int max)403         public void setup(int orientation, ConstraintAnchor left, ConstraintAnchor top,
404                 ConstraintAnchor right, ConstraintAnchor bottom,
405                 int paddingLeft, int paddingTop, int paddingRight, int paddingBottom,
406                 int max) {
407             mOrientation = orientation;
408             mLeft = left;
409             mTop = top;
410             mRight = right;
411             mBottom = bottom;
412             mPaddingLeft = paddingLeft;
413             mPaddingTop = paddingTop;
414             mPaddingRight = paddingRight;
415             mPaddingBottom = paddingBottom;
416             mMax = max;
417         }
418 
clear()419         public void clear() {
420             mBiggestDimension = 0;
421             mBiggest = null;
422             mWidth = 0;
423             mHeight = 0;
424             mStartIndex = 0;
425             mCount = 0;
426             mNbMatchConstraintsWidgets = 0;
427         }
428 
setStartIndex(int value)429         public void setStartIndex(int value) {
430             mStartIndex = value;
431         }
432 
getWidth()433         public int getWidth() {
434             if (mOrientation == HORIZONTAL) {
435                 return mWidth - mHorizontalGap;
436             }
437             return mWidth;
438         }
439 
getHeight()440         public int getHeight() {
441             if (mOrientation == VERTICAL) {
442                 return mHeight - mVerticalGap;
443             }
444             return mHeight;
445         }
446 
add(ConstraintWidget widget)447         public void add(ConstraintWidget widget) {
448             if (mOrientation == HORIZONTAL) {
449                 int width = getWidgetWidth(widget, mMax);
450                 if (widget.getHorizontalDimensionBehaviour()
451                         == DimensionBehaviour.MATCH_CONSTRAINT) {
452                     mNbMatchConstraintsWidgets++;
453                     width = 0;
454                 }
455                 int gap = mHorizontalGap;
456                 if (widget.getVisibility() == GONE) {
457                     gap = 0;
458                 }
459                 mWidth += width + gap;
460                 int height = getWidgetHeight(widget, mMax);
461                 if (mBiggest == null || mBiggestDimension < height) {
462                     mBiggest = widget;
463                     mBiggestDimension = height;
464                     mHeight = height;
465                 }
466             } else {
467                 int width = getWidgetWidth(widget, mMax);
468                 int height = getWidgetHeight(widget, mMax);
469                 if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
470                     mNbMatchConstraintsWidgets++;
471                     height = 0;
472                 }
473                 int gap = mVerticalGap;
474                 if (widget.getVisibility() == GONE) {
475                     gap = 0;
476                 }
477                 mHeight += height + gap;
478                 if (mBiggest == null || mBiggestDimension < width) {
479                     mBiggest = widget;
480                     mBiggestDimension = width;
481                     mWidth = width;
482                 }
483             }
484             mCount++;
485         }
486 
createConstraints(boolean isInRtl, int chainIndex, boolean isLastChain)487         public void createConstraints(boolean isInRtl, int chainIndex, boolean isLastChain) {
488             final int count = mCount;
489             for (int i = 0; i < count; i++) {
490                 if (mStartIndex + i >= mDisplayedWidgetsCount) {
491                     break;
492                 }
493                 ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
494                 if (widget != null) {
495                     widget.resetAnchors();
496                 }
497             }
498             if (count == 0 || mBiggest == null) {
499                 return;
500             }
501 
502             boolean singleChain = isLastChain && chainIndex == 0;
503             int firstVisible = -1;
504             int lastVisible = -1;
505             for (int i = 0; i < count; i++) {
506                 int index = i;
507                 if (isInRtl) {
508                     index = count - 1 - i;
509                 }
510                 if (mStartIndex + index >= mDisplayedWidgetsCount) {
511                     break;
512                 }
513                 ConstraintWidget widget = mDisplayedWidgets[mStartIndex + index];
514                 if (widget != null && widget.getVisibility() == VISIBLE) {
515                     if (firstVisible == -1) {
516                         firstVisible = i;
517                     }
518                     lastVisible = i;
519                 }
520             }
521 
522             ConstraintWidget previous = null;
523             if (mOrientation == HORIZONTAL) {
524                 ConstraintWidget verticalWidget = mBiggest;
525                 verticalWidget.setVerticalChainStyle(mVerticalStyle);
526                 int padding = mPaddingTop;
527                 if (chainIndex > 0) {
528                     padding += mVerticalGap;
529                 }
530                 verticalWidget.mTop.connect(mTop, padding);
531                 if (isLastChain) {
532                     verticalWidget.mBottom.connect(mBottom, mPaddingBottom);
533                 }
534                 if (chainIndex > 0) {
535                     ConstraintAnchor bottom = mTop.mOwner.mBottom;
536                     bottom.connect(verticalWidget.mTop, 0);
537                 }
538 
539                 ConstraintWidget baselineVerticalWidget = verticalWidget;
540                 if (mVerticalAlign == VERTICAL_ALIGN_BASELINE && !verticalWidget.hasBaseline()) {
541                     for (int i = 0; i < count; i++) {
542                         int index = i;
543                         if (isInRtl) {
544                             index = count - 1 - i;
545                         }
546                         if (mStartIndex + index >= mDisplayedWidgetsCount) {
547                             break;
548                         }
549                         ConstraintWidget widget = mDisplayedWidgets[mStartIndex + index];
550                         if (widget.hasBaseline()) {
551                             baselineVerticalWidget = widget;
552                             break;
553                         }
554                     }
555                 }
556 
557                 for (int i = 0; i < count; i++) {
558                     int index = i;
559                     if (isInRtl) {
560                         index = count - 1 - i;
561                     }
562                     if (mStartIndex + index >= mDisplayedWidgetsCount) {
563                         break;
564                     }
565                     ConstraintWidget widget = mDisplayedWidgets[mStartIndex + index];
566                     if (widget == null) {
567                         continue;
568                     }
569                     if (i == 0) {
570                         widget.connect(widget.mLeft, mLeft, mPaddingLeft);
571                     }
572 
573                     // ChainHead is always based on index, not i.
574                     // E.g. RTL would have head at the right most widget.
575                     if (index == 0) {
576                         int style = mHorizontalStyle;
577                         float bias = isInRtl ? (1 - mHorizontalBias) : mHorizontalBias;
578                         if (mStartIndex == 0 && mFirstHorizontalStyle != UNKNOWN) {
579                             style = mFirstHorizontalStyle;
580                             bias = isInRtl ? (1 - mFirstHorizontalBias) : mFirstHorizontalBias;
581                         } else if (isLastChain && mLastHorizontalStyle != UNKNOWN) {
582                             style = mLastHorizontalStyle;
583                             bias = isInRtl ? (1 - mLastHorizontalBias) : mLastHorizontalBias;
584                         }
585                         widget.setHorizontalChainStyle(style);
586                         widget.setHorizontalBiasPercent(bias);
587                     }
588                     if (i == count - 1) {
589                         widget.connect(widget.mRight, mRight, mPaddingRight);
590                     }
591                     if (previous != null) {
592                         widget.mLeft.connect(previous.mRight, mHorizontalGap);
593                         if (i == firstVisible) {
594                             widget.mLeft.setGoneMargin(mPaddingLeft);
595                         }
596                         previous.mRight.connect(widget.mLeft, 0);
597                         if (i == lastVisible + 1) {
598                             previous.mRight.setGoneMargin(mPaddingRight);
599                         }
600                     }
601                     if (widget != verticalWidget) {
602                         if (mVerticalAlign == VERTICAL_ALIGN_BASELINE
603                                 && baselineVerticalWidget.hasBaseline()
604                                 && widget != baselineVerticalWidget
605                                 && widget.hasBaseline()) {
606                             widget.mBaseline.connect(baselineVerticalWidget.mBaseline, 0);
607                         } else {
608                             switch (mVerticalAlign) {
609                                 case VERTICAL_ALIGN_TOP: {
610                                     widget.mTop.connect(verticalWidget.mTop, 0);
611                                 }
612                                 break;
613                                 case VERTICAL_ALIGN_BOTTOM: {
614                                     widget.mBottom.connect(verticalWidget.mBottom, 0);
615                                 }
616                                 break;
617                                 case VERTICAL_ALIGN_CENTER:
618                                 default: {
619                                     if (singleChain) {
620                                         widget.mTop.connect(mTop, mPaddingTop);
621                                         widget.mBottom.connect(mBottom, mPaddingBottom);
622                                     } else {
623                                         widget.mTop.connect(verticalWidget.mTop, 0);
624                                         widget.mBottom.connect(verticalWidget.mBottom, 0);
625                                     }
626                                 }
627                             }
628                         }
629                     }
630                     previous = widget;
631                 }
632             } else {
633                 ConstraintWidget horizontalWidget = mBiggest;
634                 horizontalWidget.setHorizontalChainStyle(mHorizontalStyle);
635                 int padding = mPaddingLeft;
636                 if (chainIndex > 0) {
637                     padding += mHorizontalGap;
638                 }
639                 if (isInRtl) {
640                     horizontalWidget.mRight.connect(mRight, padding);
641                     if (isLastChain) {
642                         horizontalWidget.mLeft.connect(mLeft, mPaddingRight);
643                     }
644                     if (chainIndex > 0) {
645                         ConstraintAnchor left = mRight.mOwner.mLeft;
646                         left.connect(horizontalWidget.mRight, 0);
647                     }
648                 } else {
649                     horizontalWidget.mLeft.connect(mLeft, padding);
650                     if (isLastChain) {
651                         horizontalWidget.mRight.connect(mRight, mPaddingRight);
652                     }
653                     if (chainIndex > 0) {
654                         ConstraintAnchor right = mLeft.mOwner.mRight;
655                         right.connect(horizontalWidget.mLeft, 0);
656                     }
657                 }
658                 for (int i = 0; i < count; i++) {
659                     if (mStartIndex + i >= mDisplayedWidgetsCount) {
660                         break;
661                     }
662                     ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
663                     if (widget == null) {
664                         continue;
665                     }
666                     if (i == 0) {
667                         widget.connect(widget.mTop, mTop, mPaddingTop);
668                         int style = mVerticalStyle;
669                         float bias = mVerticalBias;
670                         if (mStartIndex == 0 && mFirstVerticalStyle != UNKNOWN) {
671                             style = mFirstVerticalStyle;
672                             bias = mFirstVerticalBias;
673                         } else if (isLastChain && mLastVerticalStyle != UNKNOWN) {
674                             style = mLastVerticalStyle;
675                             bias = mLastVerticalBias;
676                         }
677                         widget.setVerticalChainStyle(style);
678                         widget.setVerticalBiasPercent(bias);
679                     }
680                     if (i == count - 1) {
681                         widget.connect(widget.mBottom, mBottom, mPaddingBottom);
682                     }
683                     if (previous != null) {
684                         widget.mTop.connect(previous.mBottom, mVerticalGap);
685                         if (i == firstVisible) {
686                             widget.mTop.setGoneMargin(mPaddingTop);
687                         }
688                         previous.mBottom.connect(widget.mTop, 0);
689                         if (i == lastVisible + 1) {
690                             previous.mBottom.setGoneMargin(mPaddingBottom);
691                         }
692                     }
693                     if (widget != horizontalWidget) {
694                         if (isInRtl) {
695                             switch (mHorizontalAlign) {
696                                 case HORIZONTAL_ALIGN_START: {
697                                     widget.mRight.connect(horizontalWidget.mRight, 0);
698                                 }
699                                 break;
700                                 case HORIZONTAL_ALIGN_CENTER: {
701                                     widget.mLeft.connect(horizontalWidget.mLeft, 0);
702                                     widget.mRight.connect(horizontalWidget.mRight, 0);
703                                 }
704                                 break;
705                                 case HORIZONTAL_ALIGN_END: {
706                                     widget.mLeft.connect(horizontalWidget.mLeft, 0);
707                                 }
708                                 break;
709                             }
710                         } else {
711                             switch (mHorizontalAlign) {
712                                 case HORIZONTAL_ALIGN_START: {
713                                     widget.mLeft.connect(horizontalWidget.mLeft, 0);
714                                 }
715                                 break;
716                                 case HORIZONTAL_ALIGN_CENTER: {
717                                     if (singleChain) {
718                                         widget.mLeft.connect(mLeft, mPaddingLeft);
719                                         widget.mRight.connect(mRight, mPaddingRight);
720                                     } else {
721                                         widget.mLeft.connect(horizontalWidget.mLeft, 0);
722                                         widget.mRight.connect(horizontalWidget.mRight, 0);
723                                     }
724                                 }
725                                 break;
726                                 case HORIZONTAL_ALIGN_END: {
727                                     widget.mRight.connect(horizontalWidget.mRight, 0);
728                                 }
729                                 break;
730                             }
731                         }
732                     }
733                     previous = widget;
734                 }
735             }
736         }
737 
measureMatchConstraints(int availableSpace)738         public void measureMatchConstraints(int availableSpace) {
739             if (mNbMatchConstraintsWidgets == 0) {
740                 return;
741             }
742             final int count = mCount;
743 
744             // that's completely incorrect and only works for spread with no weights?
745             int widgetSize = availableSpace / mNbMatchConstraintsWidgets;
746             for (int i = 0; i < count; i++) {
747                 if (mStartIndex + i >= mDisplayedWidgetsCount) {
748                     break;
749                 }
750                 ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
751                 if (mOrientation == HORIZONTAL) {
752                     if (widget != null && widget.getHorizontalDimensionBehaviour()
753                             == DimensionBehaviour.MATCH_CONSTRAINT) {
754                         if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
755                             measure(widget, DimensionBehaviour.FIXED, widgetSize,
756                                     widget.getVerticalDimensionBehaviour(), widget.getHeight());
757                         }
758                     }
759                 } else {
760                     if (widget != null && widget.getVerticalDimensionBehaviour()
761                             == DimensionBehaviour.MATCH_CONSTRAINT) {
762                         if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
763                             measure(widget, widget.getHorizontalDimensionBehaviour(),
764                                     widget.getWidth(), DimensionBehaviour.FIXED, widgetSize);
765                         }
766                     }
767                 }
768             }
769             recomputeDimensions();
770         }
771 
recomputeDimensions()772         private void recomputeDimensions() {
773             mWidth = 0;
774             mHeight = 0;
775             mBiggest = null;
776             mBiggestDimension = 0;
777             final int count = mCount;
778             for (int i = 0; i < count; i++) {
779                 if (mStartIndex + i >= mDisplayedWidgetsCount) {
780                     break;
781                 }
782                 ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
783                 if (mOrientation == HORIZONTAL) {
784                     int width = widget.getWidth();
785                     int gap = mHorizontalGap;
786                     if (widget.getVisibility() == GONE) {
787                         gap = 0;
788                     }
789                     mWidth += width + gap;
790                     int height = getWidgetHeight(widget, mMax);
791                     if (mBiggest == null || mBiggestDimension < height) {
792                         mBiggest = widget;
793                         mBiggestDimension = height;
794                         mHeight = height;
795                     }
796                 } else {
797                     int width = getWidgetWidth(widget, mMax);
798                     int height = getWidgetHeight(widget, mMax);
799                     int gap = mVerticalGap;
800                     if (widget.getVisibility() == GONE) {
801                         gap = 0;
802                     }
803                     mHeight += height + gap;
804                     if (mBiggest == null || mBiggestDimension < width) {
805                         mBiggest = widget;
806                         mBiggestDimension = width;
807                         mWidth = width;
808                     }
809                 }
810             }
811         }
812 
813     }
814 
815     /////////////////////////////////////////////////////////////////////////////////////////////
816     // Measure Chain Wrap
817     /////////////////////////////////////////////////////////////////////////////////////////////
818 
819     /**
820      * Measure the virtual layout using a list of chains for the children
821      *
822      * @param widgets     list of widgets
823      * @param orientation the layout orientation (horizontal or vertical)
824      * @param max         the maximum available space
825      * @param measured    output parameters -- will contain the resulting measure
826      */
measureChainWrap(ConstraintWidget[] widgets, int count, int orientation, int max, int[] measured)827     private void measureChainWrap(ConstraintWidget[] widgets,
828             int count,
829             int orientation,
830             int max,
831             int[] measured) {
832         if (count == 0) {
833             return;
834         }
835 
836         mChainList.clear();
837         WidgetsList list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
838         mChainList.add(list);
839 
840         int nbMatchConstraintsWidgets = 0;
841 
842         if (orientation == HORIZONTAL) {
843             int width = 0;
844             for (int i = 0; i < count; i++) {
845                 ConstraintWidget widget = widgets[i];
846                 int w = getWidgetWidth(widget, max);
847                 if (widget.getHorizontalDimensionBehaviour()
848                         == DimensionBehaviour.MATCH_CONSTRAINT) {
849                     nbMatchConstraintsWidgets++;
850                 }
851                 boolean doWrap = (width == max || (width + mHorizontalGap + w) > max)
852                         && list.mBiggest != null;
853                 if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (i % mMaxElementsWrap == 0)) {
854                     doWrap = true;
855                 }
856                 if (doWrap) {
857                     width = w;
858                     list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
859                     list.setStartIndex(i);
860                     mChainList.add(list);
861                 } else {
862                     if (i > 0) {
863                         width += mHorizontalGap + w;
864                     } else {
865                         width = w;
866                     }
867                 }
868                 list.add(widget);
869             }
870         } else {
871             int height = 0;
872             for (int i = 0; i < count; i++) {
873                 ConstraintWidget widget = widgets[i];
874                 int h = getWidgetHeight(widget, max);
875                 if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
876                     nbMatchConstraintsWidgets++;
877                 }
878                 boolean doWrap = (height == max || (height + mVerticalGap + h) > max)
879                         && list.mBiggest != null;
880                 if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (i % mMaxElementsWrap == 0)) {
881                     doWrap = true;
882                 }
883                 if (doWrap) {
884                     height = h;
885                     list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
886                     list.setStartIndex(i);
887                     mChainList.add(list);
888                 } else {
889                     if (i > 0) {
890                         height += mVerticalGap + h;
891                     } else {
892                         height = h;
893                     }
894                 }
895                 list.add(widget);
896             }
897         }
898         final int listCount = mChainList.size();
899 
900         ConstraintAnchor left = mLeft;
901         ConstraintAnchor top = mTop;
902         ConstraintAnchor right = mRight;
903         ConstraintAnchor bottom = mBottom;
904 
905         int paddingLeft = getPaddingLeft();
906         int paddingTop = getPaddingTop();
907         int paddingRight = getPaddingRight();
908         int paddingBottom = getPaddingBottom();
909 
910         int maxWidth = 0;
911         int maxHeight = 0;
912 
913         boolean needInternalMeasure =
914                 getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT
915                         || getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
916 
917         if (nbMatchConstraintsWidgets > 0 && needInternalMeasure) {
918             // we have to remeasure them.
919             for (int i = 0; i < listCount; i++) {
920                 WidgetsList current = mChainList.get(i);
921                 if (orientation == HORIZONTAL) {
922                     current.measureMatchConstraints(max - current.getWidth());
923                 } else {
924                     current.measureMatchConstraints(max - current.getHeight());
925                 }
926             }
927         }
928 
929         for (int i = 0; i < listCount; i++) {
930             WidgetsList current = mChainList.get(i);
931             if (orientation == HORIZONTAL) {
932                 if (i < listCount - 1) {
933                     WidgetsList next = mChainList.get(i + 1);
934                     bottom = next.mBiggest.mTop;
935                     paddingBottom = 0;
936                 } else {
937                     bottom = mBottom;
938                     paddingBottom = getPaddingBottom();
939                 }
940                 ConstraintAnchor currentBottom = current.mBiggest.mBottom;
941                 current.setup(orientation, left, top, right, bottom,
942                         paddingLeft, paddingTop, paddingRight, paddingBottom, max);
943                 top = currentBottom;
944                 paddingTop = 0;
945                 maxWidth = Math.max(maxWidth, current.getWidth());
946                 maxHeight += current.getHeight();
947                 if (i > 0) {
948                     maxHeight += mVerticalGap;
949                 }
950             } else {
951                 if (i < listCount - 1) {
952                     WidgetsList next = mChainList.get(i + 1);
953                     right = next.mBiggest.mLeft;
954                     paddingRight = 0;
955                 } else {
956                     right = mRight;
957                     paddingRight = getPaddingRight();
958                 }
959                 ConstraintAnchor currentRight = current.mBiggest.mRight;
960                 current.setup(orientation, left, top, right, bottom,
961                         paddingLeft, paddingTop, paddingRight, paddingBottom, max);
962                 left = currentRight;
963                 paddingLeft = 0;
964                 maxWidth += current.getWidth();
965                 maxHeight = Math.max(maxHeight, current.getHeight());
966                 if (i > 0) {
967                     maxWidth += mHorizontalGap;
968                 }
969             }
970         }
971         measured[HORIZONTAL] = maxWidth;
972         measured[VERTICAL] = maxHeight;
973     }
974     /////////////////////////////////////////////////////////////////////////////////////////////
975     // Measure Chain Wrap new
976     /////////////////////////////////////////////////////////////////////////////////////////////
977 
978     /**
979      * Measure the virtual layout using a list of chains for the children in new "fixed way"
980      *
981      * @param widgets     list of widgets
982      * @param orientation the layout orientation (horizontal or vertical)
983      * @param max         the maximum available space
984      * @param measured    output parameters -- will contain the resulting measure
985      */
measureChainWrap_new(ConstraintWidget[] widgets, int count, int orientation, int max, int[] measured)986     private void measureChainWrap_new(ConstraintWidget[] widgets,
987             int count,
988             int orientation,
989             int max,
990             int[] measured) {
991         if (count == 0) {
992             return;
993         }
994 
995         mChainList.clear();
996         WidgetsList list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
997         mChainList.add(list);
998 
999         int nbMatchConstraintsWidgets = 0;
1000 
1001         if (orientation == HORIZONTAL) {
1002             int width = 0;
1003             int col = 0;
1004             for (int i = 0; i < count; i++) {
1005                 col++;
1006                 ConstraintWidget widget = widgets[i];
1007                 int w = getWidgetWidth(widget, max);
1008                 if (widget.getHorizontalDimensionBehaviour()
1009                         == DimensionBehaviour.MATCH_CONSTRAINT) {
1010                     nbMatchConstraintsWidgets++;
1011                 }
1012                 boolean doWrap = (width == max || (width + mHorizontalGap + w) > max)
1013                         && list.mBiggest != null;
1014                 if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (col > mMaxElementsWrap)) {
1015                     doWrap = true;
1016                 }
1017                 if (doWrap) {
1018                     col = 1;
1019                     width = w;
1020                     list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
1021                     list.setStartIndex(i);
1022                     mChainList.add(list);
1023                 } else {
1024                     if (i > 0) {
1025                         width += mHorizontalGap + w;
1026                     } else {
1027                         width = w;
1028                     }
1029                 }
1030                 list.add(widget);
1031             }
1032         } else {
1033             int height = 0;
1034             int row = 0;
1035             for (int i = 0; i < count; i++) {
1036                 row++;
1037                 ConstraintWidget widget = widgets[i];
1038                 int h = getWidgetHeight(widget, max);
1039                 if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
1040                     nbMatchConstraintsWidgets++;
1041                 }
1042                 boolean doWrap = (height == max || (height + mVerticalGap + h) > max)
1043                         && list.mBiggest != null;
1044                 if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (row > mMaxElementsWrap)) {
1045                     doWrap = true;
1046                 }
1047                 if (doWrap) {
1048                     row = 1;
1049                     height = h;
1050                     list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
1051                     list.setStartIndex(i);
1052                     mChainList.add(list);
1053                 } else {
1054                     if (i > 0) {
1055                         height += mVerticalGap + h;
1056                     } else {
1057                         height = h;
1058                     }
1059                 }
1060                 list.add(widget);
1061             }
1062         }
1063         final int listCount = mChainList.size();
1064 
1065         ConstraintAnchor left = mLeft;
1066         ConstraintAnchor top = mTop;
1067         ConstraintAnchor right = mRight;
1068         ConstraintAnchor bottom = mBottom;
1069 
1070         int paddingLeft = getPaddingLeft();
1071         int paddingTop = getPaddingTop();
1072         int paddingRight = getPaddingRight();
1073         int paddingBottom = getPaddingBottom();
1074 
1075         int maxWidth = 0;
1076         int maxHeight = 0;
1077 
1078         boolean needInternalMeasure =
1079                 getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT
1080                         || getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
1081 
1082         if (nbMatchConstraintsWidgets > 0 && needInternalMeasure) {
1083             // we have to remeasure them.
1084             for (int i = 0; i < listCount; i++) {
1085                 WidgetsList current = mChainList.get(i);
1086                 if (orientation == HORIZONTAL) {
1087                     current.measureMatchConstraints(max - current.getWidth());
1088                 } else {
1089                     current.measureMatchConstraints(max - current.getHeight());
1090                 }
1091             }
1092         }
1093 
1094         for (int i = 0; i < listCount; i++) {
1095             WidgetsList current = mChainList.get(i);
1096             if (orientation == HORIZONTAL) {
1097                 if (i < listCount - 1) {
1098                     WidgetsList next = mChainList.get(i + 1);
1099                     bottom = next.mBiggest.mTop;
1100                     paddingBottom = 0;
1101                 } else {
1102                     bottom = mBottom;
1103                     paddingBottom = getPaddingBottom();
1104                 }
1105                 ConstraintAnchor currentBottom = current.mBiggest.mBottom;
1106                 current.setup(orientation, left, top, right, bottom,
1107                         paddingLeft, paddingTop, paddingRight, paddingBottom, max);
1108                 top = currentBottom;
1109                 paddingTop = 0;
1110                 maxWidth = Math.max(maxWidth, current.getWidth());
1111                 maxHeight += current.getHeight();
1112                 if (i > 0) {
1113                     maxHeight += mVerticalGap;
1114                 }
1115             } else {
1116                 if (i < listCount - 1) {
1117                     WidgetsList next = mChainList.get(i + 1);
1118                     right = next.mBiggest.mLeft;
1119                     paddingRight = 0;
1120                 } else {
1121                     right = mRight;
1122                     paddingRight = getPaddingRight();
1123                 }
1124                 ConstraintAnchor currentRight = current.mBiggest.mRight;
1125                 current.setup(orientation, left, top, right, bottom,
1126                         paddingLeft, paddingTop, paddingRight, paddingBottom, max);
1127                 left = currentRight;
1128                 paddingLeft = 0;
1129                 maxWidth += current.getWidth();
1130                 maxHeight = Math.max(maxHeight, current.getHeight());
1131                 if (i > 0) {
1132                     maxWidth += mHorizontalGap;
1133                 }
1134             }
1135         }
1136         measured[HORIZONTAL] = maxWidth;
1137         measured[VERTICAL] = maxHeight;
1138     }
1139 
1140     /////////////////////////////////////////////////////////////////////////////////////////////
1141     // Measure No Wrap
1142     /////////////////////////////////////////////////////////////////////////////////////////////
1143 
1144     /**
1145      * Measure the virtual layout using a single chain for the children
1146      *
1147      * @param widgets     list of widgets
1148      * @param orientation the layout orientation (horizontal or vertical)
1149      * @param max         the maximum available space
1150      * @param measured    output parameters -- will contain the resulting measure
1151      */
measureNoWrap(ConstraintWidget[] widgets, int count, int orientation, int max, int[] measured)1152     private void measureNoWrap(ConstraintWidget[] widgets,
1153             int count,
1154             int orientation,
1155             int max,
1156             int[] measured) {
1157         if (count == 0) {
1158             return;
1159         }
1160         WidgetsList list = null;
1161         if (mChainList.size() == 0) {
1162             list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
1163             mChainList.add(list);
1164         } else {
1165             list = mChainList.get(0);
1166             list.clear();
1167             list.setup(orientation, mLeft, mTop, mRight, mBottom,
1168                     getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom(), max);
1169         }
1170 
1171         for (int i = 0; i < count; i++) {
1172             ConstraintWidget widget = widgets[i];
1173             list.add(widget);
1174         }
1175 
1176         measured[HORIZONTAL] = list.getWidth();
1177         measured[VERTICAL] = list.getHeight();
1178     }
1179 
1180     /////////////////////////////////////////////////////////////////////////////////////////////
1181     // Measure Aligned
1182     /////////////////////////////////////////////////////////////////////////////////////////////
1183 
1184     /**
1185      * Measure the virtual layout arranging the children in a regular grid
1186      *
1187      * @param widgets     list of widgets
1188      * @param orientation the layout orientation (horizontal or vertical)
1189      * @param max         the maximum available space
1190      * @param measured    output parameters -- will contain the resulting measure
1191      */
measureAligned(ConstraintWidget[] widgets, int count, int orientation, int max, int[] measured)1192     private void measureAligned(ConstraintWidget[] widgets,
1193             int count,
1194             int orientation,
1195             int max,
1196             int[] measured) {
1197         boolean done = false;
1198         int rows = 0;
1199         int cols = 0;
1200 
1201         if (orientation == HORIZONTAL) {
1202             cols = mMaxElementsWrap;
1203             if (cols <= 0) {
1204                 // let's initialize cols with an acceptable value
1205                 int w = 0;
1206                 cols = 0;
1207                 for (int i = 0; i < count; i++) {
1208                     if (i > 0) {
1209                         w += mHorizontalGap;
1210                     }
1211                     ConstraintWidget widget = widgets[i];
1212                     if (widget == null) {
1213                         continue;
1214                     }
1215                     w += getWidgetWidth(widget, max);
1216                     if (w > max) {
1217                         break;
1218                     }
1219                     cols++;
1220                 }
1221             }
1222         } else {
1223             rows = mMaxElementsWrap;
1224             if (rows <= 0) {
1225                 // let's initialize rows with an acceptable value
1226                 int h = 0;
1227                 rows = 0;
1228                 for (int i = 0; i < count; i++) {
1229                     if (i > 0) {
1230                         h += mVerticalGap;
1231                     }
1232                     ConstraintWidget widget = widgets[i];
1233                     if (widget == null) {
1234                         continue;
1235                     }
1236                     h += getWidgetHeight(widget, max);
1237                     if (h > max) {
1238                         break;
1239                     }
1240                     rows++;
1241                 }
1242             }
1243         }
1244 
1245         if (mAlignedDimensions == null) {
1246             mAlignedDimensions = new int[2];
1247         }
1248 
1249         if ((rows == 0 && orientation == VERTICAL)
1250                 || (cols == 0 && orientation == HORIZONTAL)) {
1251             done = true;
1252         }
1253 
1254         while (!done) {
1255             // get a num of rows (or cols)
1256             // get for each row and cols the chain of biggest elements
1257 
1258             if (orientation == HORIZONTAL) {
1259                 rows = (int) Math.ceil(count / (float) cols);
1260             } else {
1261                 cols = (int) Math.ceil(count / (float) rows);
1262             }
1263 
1264             if (mAlignedBiggestElementsInCols == null
1265                     || mAlignedBiggestElementsInCols.length < cols) {
1266                 mAlignedBiggestElementsInCols = new ConstraintWidget[cols];
1267             } else {
1268                 Arrays.fill(mAlignedBiggestElementsInCols, null);
1269             }
1270             if (mAlignedBiggestElementsInRows == null
1271                     || mAlignedBiggestElementsInRows.length < rows) {
1272                 mAlignedBiggestElementsInRows = new ConstraintWidget[rows];
1273             } else {
1274                 Arrays.fill(mAlignedBiggestElementsInRows, null);
1275             }
1276 
1277             for (int i = 0; i < cols; i++) {
1278                 for (int j = 0; j < rows; j++) {
1279                     int index = j * cols + i;
1280                     if (orientation == VERTICAL) {
1281                         index = i * rows + j;
1282                     }
1283                     if (index >= widgets.length) {
1284                         continue;
1285                     }
1286                     ConstraintWidget widget = widgets[index];
1287                     if (widget == null) {
1288                         continue;
1289                     }
1290                     int w = getWidgetWidth(widget, max);
1291                     if (mAlignedBiggestElementsInCols[i] == null
1292                             || mAlignedBiggestElementsInCols[i].getWidth() < w) {
1293                         mAlignedBiggestElementsInCols[i] = widget;
1294                     }
1295                     int h = getWidgetHeight(widget, max);
1296                     if (mAlignedBiggestElementsInRows[j] == null
1297                             || mAlignedBiggestElementsInRows[j].getHeight() < h) {
1298                         mAlignedBiggestElementsInRows[j] = widget;
1299                     }
1300                 }
1301             }
1302 
1303             int w = 0;
1304             for (int i = 0; i < cols; i++) {
1305                 ConstraintWidget widget = mAlignedBiggestElementsInCols[i];
1306                 if (widget != null) {
1307                     if (i > 0) {
1308                         w += mHorizontalGap;
1309                     }
1310                     w += getWidgetWidth(widget, max);
1311                 }
1312             }
1313             int h = 0;
1314             for (int j = 0; j < rows; j++) {
1315                 ConstraintWidget widget = mAlignedBiggestElementsInRows[j];
1316                 if (widget != null) {
1317                     if (j > 0) {
1318                         h += mVerticalGap;
1319                     }
1320                     h += getWidgetHeight(widget, max);
1321                 }
1322             }
1323             measured[HORIZONTAL] = w;
1324             measured[VERTICAL] = h;
1325 
1326             if (orientation == HORIZONTAL) {
1327                 if (w > max) {
1328                     if (cols > 1) {
1329                         cols--;
1330                     } else {
1331                         done = true;
1332                     }
1333                 } else {
1334                     done = true;
1335                 }
1336             } else { // VERTICAL
1337                 if (h > max) {
1338                     if (rows > 1) {
1339                         rows--;
1340                     } else {
1341                         done = true;
1342                     }
1343                 } else {
1344                     done = true;
1345                 }
1346             }
1347         }
1348         mAlignedDimensions[HORIZONTAL] = cols;
1349         mAlignedDimensions[VERTICAL] = rows;
1350     }
1351 
createAlignedConstraints(boolean isInRtl)1352     private void createAlignedConstraints(boolean isInRtl) {
1353         if (mAlignedDimensions == null
1354                 || mAlignedBiggestElementsInCols == null
1355                 || mAlignedBiggestElementsInRows == null) {
1356             return;
1357         }
1358 
1359         for (int i = 0; i < mDisplayedWidgetsCount; i++) {
1360             ConstraintWidget widget = mDisplayedWidgets[i];
1361             widget.resetAnchors();
1362         }
1363 
1364         int cols = mAlignedDimensions[HORIZONTAL];
1365         int rows = mAlignedDimensions[VERTICAL];
1366 
1367         ConstraintWidget previous = null;
1368         float horizontalBias = mHorizontalBias;
1369         for (int i = 0; i < cols; i++) {
1370             int index = i;
1371             if (isInRtl) {
1372                 index = cols - i - 1;
1373                 horizontalBias = 1 - mHorizontalBias;
1374             }
1375             ConstraintWidget widget = mAlignedBiggestElementsInCols[index];
1376             if (widget == null || widget.getVisibility() == GONE) {
1377                 continue;
1378             }
1379             if (i == 0) {
1380                 widget.connect(widget.mLeft, mLeft, getPaddingLeft());
1381                 widget.setHorizontalChainStyle(mHorizontalStyle);
1382                 widget.setHorizontalBiasPercent(horizontalBias);
1383             }
1384             if (i == cols - 1) {
1385                 widget.connect(widget.mRight, mRight, getPaddingRight());
1386             }
1387             if (i > 0 && previous != null) {
1388                 widget.connect(widget.mLeft, previous.mRight, mHorizontalGap);
1389                 previous.connect(previous.mRight, widget.mLeft, 0);
1390             }
1391             previous = widget;
1392         }
1393         for (int j = 0; j < rows; j++) {
1394             ConstraintWidget widget = mAlignedBiggestElementsInRows[j];
1395             if (widget == null || widget.getVisibility() == GONE) {
1396                 continue;
1397             }
1398             if (j == 0) {
1399                 widget.connect(widget.mTop, mTop, getPaddingTop());
1400                 widget.setVerticalChainStyle(mVerticalStyle);
1401                 widget.setVerticalBiasPercent(mVerticalBias);
1402             }
1403             if (j == rows - 1) {
1404                 widget.connect(widget.mBottom, mBottom, getPaddingBottom());
1405             }
1406             if (j > 0 && previous != null) {
1407                 widget.connect(widget.mTop, previous.mBottom, mVerticalGap);
1408                 previous.connect(previous.mBottom, widget.mTop, 0);
1409             }
1410             previous = widget;
1411         }
1412 
1413         for (int i = 0; i < cols; i++) {
1414             for (int j = 0; j < rows; j++) {
1415                 int index = j * cols + i;
1416                 if (mOrientation == VERTICAL) {
1417                     index = i * rows + j;
1418                 }
1419                 if (index >= mDisplayedWidgets.length) {
1420                     continue;
1421                 }
1422                 ConstraintWidget widget = mDisplayedWidgets[index];
1423                 if (widget == null || widget.getVisibility() == GONE) {
1424                     continue;
1425                 }
1426                 ConstraintWidget biggestInCol = mAlignedBiggestElementsInCols[i];
1427                 ConstraintWidget biggestInRow = mAlignedBiggestElementsInRows[j];
1428                 if (widget != biggestInCol) {
1429                     widget.connect(widget.mLeft, biggestInCol.mLeft, 0);
1430                     widget.connect(widget.mRight, biggestInCol.mRight, 0);
1431                 }
1432                 if (widget != biggestInRow) {
1433                     widget.connect(widget.mTop, biggestInRow.mTop, 0);
1434                     widget.connect(widget.mBottom, biggestInRow.mBottom, 0);
1435                 }
1436             }
1437         }
1438     }
1439 
1440     /////////////////////////////////////////////////////////////////////////////////////////////
1441     // Add constraints to solver
1442     /////////////////////////////////////////////////////////////////////////////////////////////
1443 
1444     /**
1445      * Add this widget to the solver
1446      *
1447      * @param system   the solver we want to add the widget to
1448      * @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on
1449      */
1450     @Override
addToSolver(LinearSystem system, boolean optimize)1451     public void addToSolver(LinearSystem system, boolean optimize) {
1452         super.addToSolver(system, optimize);
1453 
1454         boolean isInRtl = getParent() != null && ((ConstraintWidgetContainer) getParent()).isRtl();
1455         switch (mWrapMode) {
1456             case WRAP_CHAIN: {
1457                 final int count = mChainList.size();
1458                 for (int i = 0; i < count; i++) {
1459                     WidgetsList list = mChainList.get(i);
1460                     list.createConstraints(isInRtl, i, i == count - 1);
1461                 }
1462             }
1463             break;
1464             case WRAP_NONE: {
1465                 if (mChainList.size() > 0) {
1466                     WidgetsList list = mChainList.get(0);
1467                     list.createConstraints(isInRtl, 0, true);
1468                 }
1469             }
1470             break;
1471             case WRAP_ALIGNED: {
1472                 createAlignedConstraints(isInRtl);
1473             }
1474             break;
1475             case WRAP_CHAIN_NEW: {
1476                 final int count = mChainList.size();
1477                 for (int i = 0; i < count; i++) {
1478                     WidgetsList list = mChainList.get(i);
1479                     list.createConstraints(isInRtl, i, i == count - 1);
1480                 }
1481             }
1482             break;
1483         }
1484         needsCallbackFromSolver(false);
1485     }
1486 }
1487