compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-beta01'
Artifact androidx.constraintlayout:constraintlayout-core:1.1.0-beta01 it located at Google repository (https://maven.google.com/)
Implements the Flow virtual layout.
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.constraintlayout.core.widgets;
import static androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.AT_MOST;
import static androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.EXACTLY;
import static androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.UNSPECIFIED;
import androidx.constraintlayout.core.LinearSystem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
/**
* Implements the Flow virtual layout.
*/
public class Flow extends VirtualLayout {
public static final int HORIZONTAL_ALIGN_START = 0;
public static final int HORIZONTAL_ALIGN_END = 1;
public static final int HORIZONTAL_ALIGN_CENTER = 2;
public static final int VERTICAL_ALIGN_TOP = 0;
public static final int VERTICAL_ALIGN_BOTTOM = 1;
public static final int VERTICAL_ALIGN_CENTER = 2;
public static final int VERTICAL_ALIGN_BASELINE = 3;
public static final int WRAP_NONE = 0;
public static final int WRAP_CHAIN = 1;
public static final int WRAP_ALIGNED = 2;
public static final int WRAP_CHAIN_NEW = 3;
private int mHorizontalStyle = UNKNOWN;
private int mVerticalStyle = UNKNOWN;
private int mFirstHorizontalStyle = UNKNOWN;
private int mFirstVerticalStyle = UNKNOWN;
private int mLastHorizontalStyle = UNKNOWN;
private int mLastVerticalStyle = UNKNOWN;
private float mHorizontalBias = 0.5f;
private float mVerticalBias = 0.5f;
private float mFirstHorizontalBias = 0.5f;
private float mFirstVerticalBias = 0.5f;
private float mLastHorizontalBias = 0.5f;
private float mLastVerticalBias = 0.5f;
private int mHorizontalGap = 0;
private int mVerticalGap = 0;
private int mHorizontalAlign = HORIZONTAL_ALIGN_CENTER;
private int mVerticalAlign = VERTICAL_ALIGN_CENTER;
private int mWrapMode = WRAP_NONE;
private int mMaxElementsWrap = UNKNOWN;
private int mOrientation = HORIZONTAL;
private ArrayList<WidgetsList> mChainList = new ArrayList<>();
// Aligned management
private ConstraintWidget[] mAlignedBiggestElementsInRows = null;
private ConstraintWidget[] mAlignedBiggestElementsInCols = null;
private int[] mAlignedDimensions = null;
private ConstraintWidget[] mDisplayedWidgets;
private int mDisplayedWidgetsCount = 0;
@Override
public void copy(ConstraintWidget src, HashMap<ConstraintWidget, ConstraintWidget> map) {
super.copy(src, map);
Flow srcFLow = (Flow) src;
mHorizontalStyle = srcFLow.mHorizontalStyle;
mVerticalStyle = srcFLow.mVerticalStyle;
mFirstHorizontalStyle = srcFLow.mFirstHorizontalStyle;
mFirstVerticalStyle = srcFLow.mFirstVerticalStyle;
mLastHorizontalStyle = srcFLow.mLastHorizontalStyle;
mLastVerticalStyle = srcFLow.mLastVerticalStyle;
mHorizontalBias = srcFLow.mHorizontalBias;
mVerticalBias = srcFLow.mVerticalBias;
mFirstHorizontalBias = srcFLow.mFirstHorizontalBias;
mFirstVerticalBias = srcFLow.mFirstVerticalBias;
mLastHorizontalBias = srcFLow.mLastHorizontalBias;
mLastVerticalBias = srcFLow.mLastVerticalBias;
mHorizontalGap = srcFLow.mHorizontalGap;
mVerticalGap = srcFLow.mVerticalGap;
mHorizontalAlign = srcFLow.mHorizontalAlign;
mVerticalAlign = srcFLow.mVerticalAlign;
mWrapMode = srcFLow.mWrapMode;
mMaxElementsWrap = srcFLow.mMaxElementsWrap;
mOrientation = srcFLow.mOrientation;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Accessors
/////////////////////////////////////////////////////////////////////////////////////////////
public void setOrientation(int value) {
mOrientation = value;
}
public void setFirstHorizontalStyle(int value) {
mFirstHorizontalStyle = value;
}
public void setFirstVerticalStyle(int value) {
mFirstVerticalStyle = value;
}
public void setLastHorizontalStyle(int value) {
mLastHorizontalStyle = value;
}
public void setLastVerticalStyle(int value) {
mLastVerticalStyle = value;
}
public void setHorizontalStyle(int value) {
mHorizontalStyle = value;
}
public void setVerticalStyle(int value) {
mVerticalStyle = value;
}
public void setHorizontalBias(float value) {
mHorizontalBias = value;
}
public void setVerticalBias(float value) {
mVerticalBias = value;
}
public void setFirstHorizontalBias(float value) {
mFirstHorizontalBias = value;
}
public void setFirstVerticalBias(float value) {
mFirstVerticalBias = value;
}
public void setLastHorizontalBias(float value) {
mLastHorizontalBias = value;
}
public void setLastVerticalBias(float value) {
mLastVerticalBias = value;
}
public void setHorizontalAlign(int value) {
mHorizontalAlign = value;
}
public void setVerticalAlign(int value) {
mVerticalAlign = value;
}
public void setWrapMode(int value) {
mWrapMode = value;
}
public void setHorizontalGap(int value) {
mHorizontalGap = value;
}
public void setVerticalGap(int value) {
mVerticalGap = value;
}
public void setMaxElementsWrap(int value) {
mMaxElementsWrap = value;
}
public float getMaxElementsWrap() {
return mMaxElementsWrap;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Utility methods
/////////////////////////////////////////////////////////////////////////////////////////////
private int getWidgetWidth(ConstraintWidget widget, int max) {
if (widget == null) {
return 0;
}
if (widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
return 0;
} else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_PERCENT) {
int value = (int) (widget.mMatchConstraintPercentWidth * max);
if (value != widget.getWidth()) {
widget.setMeasureRequested(true);
measure(widget, DimensionBehaviour.FIXED, value,
widget.getVerticalDimensionBehaviour(), widget.getHeight());
}
return value;
} else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
return widget.getWidth();
} else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
return (int) (widget.getHeight() * widget.mDimensionRatio + 0.5f);
}
}
return widget.getWidth();
}
private int getWidgetHeight(ConstraintWidget widget, int max) {
if (widget == null) {
return 0;
}
if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
return 0;
} else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_PERCENT) {
int value = (int) (widget.mMatchConstraintPercentHeight * max);
if (value != widget.getHeight()) {
widget.setMeasureRequested(true);
measure(widget, widget.getHorizontalDimensionBehaviour(),
widget.getWidth(), DimensionBehaviour.FIXED, value);
}
return value;
} else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
return widget.getHeight();
} else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) {
return (int) (widget.getWidth() * widget.mDimensionRatio + 0.5f);
}
}
return widget.getHeight();
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Measure
/////////////////////////////////////////////////////////////////////////////////////////////
// @TODO: add description
@Override
public void measure(int widthMode, int widthSize, int heightMode, int heightSize) {
if (mWidgetsCount > 0 && !measureChildren()) {
setMeasure(0, 0);
needsCallbackFromSolver(false);
return;
}
@SuppressWarnings("unused") int width = 0;
@SuppressWarnings("unused") int height = 0;
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int[] measured = new int[2];
int max = widthSize - paddingLeft - paddingRight;
if (mOrientation == VERTICAL) {
max = heightSize - paddingTop - paddingBottom;
}
if (mOrientation == HORIZONTAL) {
if (mHorizontalStyle == UNKNOWN) {
mHorizontalStyle = CHAIN_SPREAD;
}
if (mVerticalStyle == UNKNOWN) {
mVerticalStyle = CHAIN_SPREAD;
}
} else {
if (mHorizontalStyle == UNKNOWN) {
mHorizontalStyle = CHAIN_SPREAD;
}
if (mVerticalStyle == UNKNOWN) {
mVerticalStyle = CHAIN_SPREAD;
}
}
ConstraintWidget[] widgets = mWidgets;
int gone = 0;
for (int i = 0; i < mWidgetsCount; i++) {
ConstraintWidget widget = mWidgets[i];
if (widget.getVisibility() == GONE) {
gone++;
}
}
int count = mWidgetsCount;
if (gone > 0) {
widgets = new ConstraintWidget[mWidgetsCount - gone];
int j = 0;
for (int i = 0; i < mWidgetsCount; i++) {
ConstraintWidget widget = mWidgets[i];
if (widget.getVisibility() != GONE) {
widgets[j] = widget;
j++;
}
}
count = j;
}
mDisplayedWidgets = widgets;
mDisplayedWidgetsCount = count;
switch (mWrapMode) {
case WRAP_ALIGNED: {
measureAligned(widgets, count, mOrientation, max, measured);
}
break;
case WRAP_CHAIN: {
measureChainWrap(widgets, count, mOrientation, max, measured);
}
break;
case WRAP_NONE: {
measureNoWrap(widgets, count, mOrientation, max, measured);
}
break;
case WRAP_CHAIN_NEW: {
measureChainWrap_new(widgets, count, mOrientation, max, measured);
}
break;
}
width = measured[HORIZONTAL] + paddingLeft + paddingRight;
height = measured[VERTICAL] + paddingTop + paddingBottom;
int measuredWidth = 0;
int measuredHeight = 0;
if (widthMode == EXACTLY) {
measuredWidth = widthSize;
} else if (widthMode == AT_MOST) {
measuredWidth = Math.min(width, widthSize);
} else if (widthMode == UNSPECIFIED) {
measuredWidth = width;
}
if (heightMode == EXACTLY) {
measuredHeight = heightSize;
} else if (heightMode == AT_MOST) {
measuredHeight = Math.min(height, heightSize);
} else if (heightMode == UNSPECIFIED) {
measuredHeight = height;
}
setMeasure(measuredWidth, measuredHeight);
setWidth(measuredWidth);
setHeight(measuredHeight);
needsCallbackFromSolver(mWidgetsCount > 0);
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Utility class representing a single chain
/////////////////////////////////////////////////////////////////////////////////////////////
private class WidgetsList {
private int mOrientation = HORIZONTAL;
private ConstraintWidget mBiggest = null;
int mBiggestDimension = 0;
private ConstraintAnchor mLeft;
private ConstraintAnchor mTop;
private ConstraintAnchor mRight;
private ConstraintAnchor mBottom;
private int mPaddingLeft = 0;
private int mPaddingTop = 0;
private int mPaddingRight = 0;
private int mPaddingBottom = 0;
private int mWidth = 0;
private int mHeight = 0;
private int mStartIndex = 0;
private int mCount = 0;
private int mNbMatchConstraintsWidgets = 0;
private int mMax = 0;
WidgetsList(int orientation,
ConstraintAnchor left, ConstraintAnchor top,
ConstraintAnchor right, ConstraintAnchor bottom,
int max) {
mOrientation = orientation;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mPaddingLeft = getPaddingLeft();
mPaddingTop = getPaddingTop();
mPaddingRight = getPaddingRight();
mPaddingBottom = getPaddingBottom();
mMax = max;
}
public void setup(int orientation, ConstraintAnchor left, ConstraintAnchor top,
ConstraintAnchor right, ConstraintAnchor bottom,
int paddingLeft, int paddingTop, int paddingRight, int paddingBottom,
int max) {
mOrientation = orientation;
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mPaddingLeft = paddingLeft;
mPaddingTop = paddingTop;
mPaddingRight = paddingRight;
mPaddingBottom = paddingBottom;
mMax = max;
}
public void clear() {
mBiggestDimension = 0;
mBiggest = null;
mWidth = 0;
mHeight = 0;
mStartIndex = 0;
mCount = 0;
mNbMatchConstraintsWidgets = 0;
}
public void setStartIndex(int value) {
mStartIndex = value;
}
public int getWidth() {
if (mOrientation == HORIZONTAL) {
return mWidth - mHorizontalGap;
}
return mWidth;
}
public int getHeight() {
if (mOrientation == VERTICAL) {
return mHeight - mVerticalGap;
}
return mHeight;
}
public void add(ConstraintWidget widget) {
if (mOrientation == HORIZONTAL) {
int width = getWidgetWidth(widget, mMax);
if (widget.getHorizontalDimensionBehaviour()
== DimensionBehaviour.MATCH_CONSTRAINT) {
mNbMatchConstraintsWidgets++;
width = 0;
}
int gap = mHorizontalGap;
if (widget.getVisibility() == GONE) {
gap = 0;
}
mWidth += width + gap;
int height = getWidgetHeight(widget, mMax);
if (mBiggest == null || mBiggestDimension < height) {
mBiggest = widget;
mBiggestDimension = height;
mHeight = height;
}
} else {
int width = getWidgetWidth(widget, mMax);
int height = getWidgetHeight(widget, mMax);
if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
mNbMatchConstraintsWidgets++;
height = 0;
}
int gap = mVerticalGap;
if (widget.getVisibility() == GONE) {
gap = 0;
}
mHeight += height + gap;
if (mBiggest == null || mBiggestDimension < width) {
mBiggest = widget;
mBiggestDimension = width;
mWidth = width;
}
}
mCount++;
}
public void createConstraints(boolean isInRtl, int chainIndex, boolean isLastChain) {
final int count = mCount;
for (int i = 0; i < count; i++) {
if (mStartIndex + i >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
if (widget != null) {
widget.resetAnchors();
}
}
if (count == 0 || mBiggest == null) {
return;
}
boolean singleChain = isLastChain && chainIndex == 0;
int firstVisible = -1;
int lastVisible = -1;
for (int i = 0; i < count; i++) {
int index = i;
if (isInRtl) {
index = count - 1 - i;
}
if (mStartIndex + index >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + index];
if (widget != null && widget.getVisibility() == VISIBLE) {
if (firstVisible == -1) {
firstVisible = i;
}
lastVisible = i;
}
}
ConstraintWidget previous = null;
if (mOrientation == HORIZONTAL) {
ConstraintWidget verticalWidget = mBiggest;
verticalWidget.setVerticalChainStyle(mVerticalStyle);
int padding = mPaddingTop;
if (chainIndex > 0) {
padding += mVerticalGap;
}
verticalWidget.mTop.connect(mTop, padding);
if (isLastChain) {
verticalWidget.mBottom.connect(mBottom, mPaddingBottom);
}
if (chainIndex > 0) {
ConstraintAnchor bottom = mTop.mOwner.mBottom;
bottom.connect(verticalWidget.mTop, 0);
}
ConstraintWidget baselineVerticalWidget = verticalWidget;
if (mVerticalAlign == VERTICAL_ALIGN_BASELINE && !verticalWidget.hasBaseline()) {
for (int i = 0; i < count; i++) {
int index = i;
if (isInRtl) {
index = count - 1 - i;
}
if (mStartIndex + index >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + index];
if (widget.hasBaseline()) {
baselineVerticalWidget = widget;
break;
}
}
}
for (int i = 0; i < count; i++) {
int index = i;
if (isInRtl) {
index = count - 1 - i;
}
if (mStartIndex + index >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + index];
if (widget == null) {
continue;
}
if (i == 0) {
widget.connect(widget.mLeft, mLeft, mPaddingLeft);
}
// ChainHead is always based on index, not i.
// E.g. RTL would have head at the right most widget.
if (index == 0) {
int style = mHorizontalStyle;
float bias = isInRtl ? (1 - mHorizontalBias) : mHorizontalBias;
if (mStartIndex == 0 && mFirstHorizontalStyle != UNKNOWN) {
style = mFirstHorizontalStyle;
bias = isInRtl ? (1 - mFirstHorizontalBias) : mFirstHorizontalBias;
} else if (isLastChain && mLastHorizontalStyle != UNKNOWN) {
style = mLastHorizontalStyle;
bias = isInRtl ? (1 - mLastHorizontalBias) : mLastHorizontalBias;
}
widget.setHorizontalChainStyle(style);
widget.setHorizontalBiasPercent(bias);
}
if (i == count - 1) {
widget.connect(widget.mRight, mRight, mPaddingRight);
}
if (previous != null) {
widget.mLeft.connect(previous.mRight, mHorizontalGap);
if (i == firstVisible) {
widget.mLeft.setGoneMargin(mPaddingLeft);
}
previous.mRight.connect(widget.mLeft, 0);
if (i == lastVisible + 1) {
previous.mRight.setGoneMargin(mPaddingRight);
}
}
if (widget != verticalWidget) {
if (mVerticalAlign == VERTICAL_ALIGN_BASELINE
&& baselineVerticalWidget.hasBaseline()
&& widget != baselineVerticalWidget
&& widget.hasBaseline()) {
widget.mBaseline.connect(baselineVerticalWidget.mBaseline, 0);
} else {
switch (mVerticalAlign) {
case VERTICAL_ALIGN_TOP: {
widget.mTop.connect(verticalWidget.mTop, 0);
}
break;
case VERTICAL_ALIGN_BOTTOM: {
widget.mBottom.connect(verticalWidget.mBottom, 0);
}
break;
case VERTICAL_ALIGN_CENTER:
default: {
if (singleChain) {
widget.mTop.connect(mTop, mPaddingTop);
widget.mBottom.connect(mBottom, mPaddingBottom);
} else {
widget.mTop.connect(verticalWidget.mTop, 0);
widget.mBottom.connect(verticalWidget.mBottom, 0);
}
}
}
}
}
previous = widget;
}
} else {
ConstraintWidget horizontalWidget = mBiggest;
horizontalWidget.setHorizontalChainStyle(mHorizontalStyle);
int padding = mPaddingLeft;
if (chainIndex > 0) {
padding += mHorizontalGap;
}
if (isInRtl) {
horizontalWidget.mRight.connect(mRight, padding);
if (isLastChain) {
horizontalWidget.mLeft.connect(mLeft, mPaddingRight);
}
if (chainIndex > 0) {
ConstraintAnchor left = mRight.mOwner.mLeft;
left.connect(horizontalWidget.mRight, 0);
}
} else {
horizontalWidget.mLeft.connect(mLeft, padding);
if (isLastChain) {
horizontalWidget.mRight.connect(mRight, mPaddingRight);
}
if (chainIndex > 0) {
ConstraintAnchor right = mLeft.mOwner.mRight;
right.connect(horizontalWidget.mLeft, 0);
}
}
for (int i = 0; i < count; i++) {
if (mStartIndex + i >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
if (widget == null) {
continue;
}
if (i == 0) {
widget.connect(widget.mTop, mTop, mPaddingTop);
int style = mVerticalStyle;
float bias = mVerticalBias;
if (mStartIndex == 0 && mFirstVerticalStyle != UNKNOWN) {
style = mFirstVerticalStyle;
bias = mFirstVerticalBias;
} else if (isLastChain && mLastVerticalStyle != UNKNOWN) {
style = mLastVerticalStyle;
bias = mLastVerticalBias;
}
widget.setVerticalChainStyle(style);
widget.setVerticalBiasPercent(bias);
}
if (i == count - 1) {
widget.connect(widget.mBottom, mBottom, mPaddingBottom);
}
if (previous != null) {
widget.mTop.connect(previous.mBottom, mVerticalGap);
if (i == firstVisible) {
widget.mTop.setGoneMargin(mPaddingTop);
}
previous.mBottom.connect(widget.mTop, 0);
if (i == lastVisible + 1) {
previous.mBottom.setGoneMargin(mPaddingBottom);
}
}
if (widget != horizontalWidget) {
if (isInRtl) {
switch (mHorizontalAlign) {
case HORIZONTAL_ALIGN_START: {
widget.mRight.connect(horizontalWidget.mRight, 0);
}
break;
case HORIZONTAL_ALIGN_CENTER: {
widget.mLeft.connect(horizontalWidget.mLeft, 0);
widget.mRight.connect(horizontalWidget.mRight, 0);
}
break;
case HORIZONTAL_ALIGN_END: {
widget.mLeft.connect(horizontalWidget.mLeft, 0);
}
break;
}
} else {
switch (mHorizontalAlign) {
case HORIZONTAL_ALIGN_START: {
widget.mLeft.connect(horizontalWidget.mLeft, 0);
}
break;
case HORIZONTAL_ALIGN_CENTER: {
if (singleChain) {
widget.mLeft.connect(mLeft, mPaddingLeft);
widget.mRight.connect(mRight, mPaddingRight);
} else {
widget.mLeft.connect(horizontalWidget.mLeft, 0);
widget.mRight.connect(horizontalWidget.mRight, 0);
}
}
break;
case HORIZONTAL_ALIGN_END: {
widget.mRight.connect(horizontalWidget.mRight, 0);
}
break;
}
}
}
previous = widget;
}
}
}
public void measureMatchConstraints(int availableSpace) {
if (mNbMatchConstraintsWidgets == 0) {
return;
}
final int count = mCount;
// that's completely incorrect and only works for spread with no weights?
int widgetSize = availableSpace / mNbMatchConstraintsWidgets;
for (int i = 0; i < count; i++) {
if (mStartIndex + i >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
if (mOrientation == HORIZONTAL) {
if (widget != null && widget.getHorizontalDimensionBehaviour()
== DimensionBehaviour.MATCH_CONSTRAINT) {
if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
measure(widget, DimensionBehaviour.FIXED, widgetSize,
widget.getVerticalDimensionBehaviour(), widget.getHeight());
}
}
} else {
if (widget != null && widget.getVerticalDimensionBehaviour()
== DimensionBehaviour.MATCH_CONSTRAINT) {
if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
measure(widget, widget.getHorizontalDimensionBehaviour(),
widget.getWidth(), DimensionBehaviour.FIXED, widgetSize);
}
}
}
}
recomputeDimensions();
}
private void recomputeDimensions() {
mWidth = 0;
mHeight = 0;
mBiggest = null;
mBiggestDimension = 0;
final int count = mCount;
for (int i = 0; i < count; i++) {
if (mStartIndex + i >= mDisplayedWidgetsCount) {
break;
}
ConstraintWidget widget = mDisplayedWidgets[mStartIndex + i];
if (mOrientation == HORIZONTAL) {
int width = widget.getWidth();
int gap = mHorizontalGap;
if (widget.getVisibility() == GONE) {
gap = 0;
}
mWidth += width + gap;
int height = getWidgetHeight(widget, mMax);
if (mBiggest == null || mBiggestDimension < height) {
mBiggest = widget;
mBiggestDimension = height;
mHeight = height;
}
} else {
int width = getWidgetWidth(widget, mMax);
int height = getWidgetHeight(widget, mMax);
int gap = mVerticalGap;
if (widget.getVisibility() == GONE) {
gap = 0;
}
mHeight += height + gap;
if (mBiggest == null || mBiggestDimension < width) {
mBiggest = widget;
mBiggestDimension = width;
mWidth = width;
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Measure Chain Wrap
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Measure the virtual layout using a list of chains for the children
*
* @param widgets list of widgets
* @param orientation the layout orientation (horizontal or vertical)
* @param max the maximum available space
* @param measured output parameters -- will contain the resulting measure
*/
private void measureChainWrap(ConstraintWidget[] widgets,
int count,
int orientation,
int max,
int[] measured) {
if (count == 0) {
return;
}
mChainList.clear();
WidgetsList list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
mChainList.add(list);
int nbMatchConstraintsWidgets = 0;
if (orientation == HORIZONTAL) {
int width = 0;
for (int i = 0; i < count; i++) {
ConstraintWidget widget = widgets[i];
int w = getWidgetWidth(widget, max);
if (widget.getHorizontalDimensionBehaviour()
== DimensionBehaviour.MATCH_CONSTRAINT) {
nbMatchConstraintsWidgets++;
}
boolean doWrap = (width == max || (width + mHorizontalGap + w) > max)
&& list.mBiggest != null;
if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (i % mMaxElementsWrap == 0)) {
doWrap = true;
}
if (doWrap) {
width = w;
list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
list.setStartIndex(i);
mChainList.add(list);
} else {
if (i > 0) {
width += mHorizontalGap + w;
} else {
width = w;
}
}
list.add(widget);
}
} else {
int height = 0;
for (int i = 0; i < count; i++) {
ConstraintWidget widget = widgets[i];
int h = getWidgetHeight(widget, max);
if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
nbMatchConstraintsWidgets++;
}
boolean doWrap = (height == max || (height + mVerticalGap + h) > max)
&& list.mBiggest != null;
if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (i % mMaxElementsWrap == 0)) {
doWrap = true;
}
if (doWrap) {
height = h;
list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
list.setStartIndex(i);
mChainList.add(list);
} else {
if (i > 0) {
height += mVerticalGap + h;
} else {
height = h;
}
}
list.add(widget);
}
}
final int listCount = mChainList.size();
ConstraintAnchor left = mLeft;
ConstraintAnchor top = mTop;
ConstraintAnchor right = mRight;
ConstraintAnchor bottom = mBottom;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int maxWidth = 0;
int maxHeight = 0;
boolean needInternalMeasure =
getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT
|| getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
if (nbMatchConstraintsWidgets > 0 && needInternalMeasure) {
// we have to remeasure them.
for (int i = 0; i < listCount; i++) {
WidgetsList current = mChainList.get(i);
if (orientation == HORIZONTAL) {
current.measureMatchConstraints(max - current.getWidth());
} else {
current.measureMatchConstraints(max - current.getHeight());
}
}
}
for (int i = 0; i < listCount; i++) {
WidgetsList current = mChainList.get(i);
if (orientation == HORIZONTAL) {
if (i < listCount - 1) {
WidgetsList next = mChainList.get(i + 1);
bottom = next.mBiggest.mTop;
paddingBottom = 0;
} else {
bottom = mBottom;
paddingBottom = getPaddingBottom();
}
ConstraintAnchor currentBottom = current.mBiggest.mBottom;
current.setup(orientation, left, top, right, bottom,
paddingLeft, paddingTop, paddingRight, paddingBottom, max);
top = currentBottom;
paddingTop = 0;
maxWidth = Math.max(maxWidth, current.getWidth());
maxHeight += current.getHeight();
if (i > 0) {
maxHeight += mVerticalGap;
}
} else {
if (i < listCount - 1) {
WidgetsList next = mChainList.get(i + 1);
right = next.mBiggest.mLeft;
paddingRight = 0;
} else {
right = mRight;
paddingRight = getPaddingRight();
}
ConstraintAnchor currentRight = current.mBiggest.mRight;
current.setup(orientation, left, top, right, bottom,
paddingLeft, paddingTop, paddingRight, paddingBottom, max);
left = currentRight;
paddingLeft = 0;
maxWidth += current.getWidth();
maxHeight = Math.max(maxHeight, current.getHeight());
if (i > 0) {
maxWidth += mHorizontalGap;
}
}
}
measured[HORIZONTAL] = maxWidth;
measured[VERTICAL] = maxHeight;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Measure Chain Wrap new
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Measure the virtual layout using a list of chains for the children in new "fixed way"
*
* @param widgets list of widgets
* @param orientation the layout orientation (horizontal or vertical)
* @param max the maximum available space
* @param measured output parameters -- will contain the resulting measure
*/
private void measureChainWrap_new(ConstraintWidget[] widgets,
int count,
int orientation,
int max,
int[] measured) {
if (count == 0) {
return;
}
mChainList.clear();
WidgetsList list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
mChainList.add(list);
int nbMatchConstraintsWidgets = 0;
if (orientation == HORIZONTAL) {
int width = 0;
int col = 0;
for (int i = 0; i < count; i++) {
col++;
ConstraintWidget widget = widgets[i];
int w = getWidgetWidth(widget, max);
if (widget.getHorizontalDimensionBehaviour()
== DimensionBehaviour.MATCH_CONSTRAINT) {
nbMatchConstraintsWidgets++;
}
boolean doWrap = (width == max || (width + mHorizontalGap + w) > max)
&& list.mBiggest != null;
if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (col > mMaxElementsWrap)) {
doWrap = true;
}
if (doWrap) {
col = 1;
width = w;
list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
list.setStartIndex(i);
mChainList.add(list);
} else {
if (i > 0) {
width += mHorizontalGap + w;
} else {
width = w;
}
}
list.add(widget);
}
} else {
int height = 0;
int row = 0;
for (int i = 0; i < count; i++) {
row++;
ConstraintWidget widget = widgets[i];
int h = getWidgetHeight(widget, max);
if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
nbMatchConstraintsWidgets++;
}
boolean doWrap = (height == max || (height + mVerticalGap + h) > max)
&& list.mBiggest != null;
if (!doWrap && i > 0 && mMaxElementsWrap > 0 && (row > mMaxElementsWrap)) {
doWrap = true;
}
if (doWrap) {
row = 1;
height = h;
list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
list.setStartIndex(i);
mChainList.add(list);
} else {
if (i > 0) {
height += mVerticalGap + h;
} else {
height = h;
}
}
list.add(widget);
}
}
final int listCount = mChainList.size();
ConstraintAnchor left = mLeft;
ConstraintAnchor top = mTop;
ConstraintAnchor right = mRight;
ConstraintAnchor bottom = mBottom;
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
int maxWidth = 0;
int maxHeight = 0;
boolean needInternalMeasure =
getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT
|| getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
if (nbMatchConstraintsWidgets > 0 && needInternalMeasure) {
// we have to remeasure them.
for (int i = 0; i < listCount; i++) {
WidgetsList current = mChainList.get(i);
if (orientation == HORIZONTAL) {
current.measureMatchConstraints(max - current.getWidth());
} else {
current.measureMatchConstraints(max - current.getHeight());
}
}
}
for (int i = 0; i < listCount; i++) {
WidgetsList current = mChainList.get(i);
if (orientation == HORIZONTAL) {
if (i < listCount - 1) {
WidgetsList next = mChainList.get(i + 1);
bottom = next.mBiggest.mTop;
paddingBottom = 0;
} else {
bottom = mBottom;
paddingBottom = getPaddingBottom();
}
ConstraintAnchor currentBottom = current.mBiggest.mBottom;
current.setup(orientation, left, top, right, bottom,
paddingLeft, paddingTop, paddingRight, paddingBottom, max);
top = currentBottom;
paddingTop = 0;
maxWidth = Math.max(maxWidth, current.getWidth());
maxHeight += current.getHeight();
if (i > 0) {
maxHeight += mVerticalGap;
}
} else {
if (i < listCount - 1) {
WidgetsList next = mChainList.get(i + 1);
right = next.mBiggest.mLeft;
paddingRight = 0;
} else {
right = mRight;
paddingRight = getPaddingRight();
}
ConstraintAnchor currentRight = current.mBiggest.mRight;
current.setup(orientation, left, top, right, bottom,
paddingLeft, paddingTop, paddingRight, paddingBottom, max);
left = currentRight;
paddingLeft = 0;
maxWidth += current.getWidth();
maxHeight = Math.max(maxHeight, current.getHeight());
if (i > 0) {
maxWidth += mHorizontalGap;
}
}
}
measured[HORIZONTAL] = maxWidth;
measured[VERTICAL] = maxHeight;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Measure No Wrap
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Measure the virtual layout using a single chain for the children
*
* @param widgets list of widgets
* @param orientation the layout orientation (horizontal or vertical)
* @param max the maximum available space
* @param measured output parameters -- will contain the resulting measure
*/
private void measureNoWrap(ConstraintWidget[] widgets,
int count,
int orientation,
int max,
int[] measured) {
if (count == 0) {
return;
}
WidgetsList list = null;
if (mChainList.size() == 0) {
list = new WidgetsList(orientation, mLeft, mTop, mRight, mBottom, max);
mChainList.add(list);
} else {
list = mChainList.get(0);
list.clear();
list.setup(orientation, mLeft, mTop, mRight, mBottom,
getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom(), max);
}
for (int i = 0; i < count; i++) {
ConstraintWidget widget = widgets[i];
list.add(widget);
}
measured[HORIZONTAL] = list.getWidth();
measured[VERTICAL] = list.getHeight();
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Measure Aligned
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Measure the virtual layout arranging the children in a regular grid
*
* @param widgets list of widgets
* @param orientation the layout orientation (horizontal or vertical)
* @param max the maximum available space
* @param measured output parameters -- will contain the resulting measure
*/
private void measureAligned(ConstraintWidget[] widgets,
int count,
int orientation,
int max,
int[] measured) {
boolean done = false;
int rows = 0;
int cols = 0;
if (orientation == HORIZONTAL) {
cols = mMaxElementsWrap;
if (cols <= 0) {
// let's initialize cols with an acceptable value
int w = 0;
cols = 0;
for (int i = 0; i < count; i++) {
if (i > 0) {
w += mHorizontalGap;
}
ConstraintWidget widget = widgets[i];
if (widget == null) {
continue;
}
w += getWidgetWidth(widget, max);
if (w > max) {
break;
}
cols++;
}
}
} else {
rows = mMaxElementsWrap;
if (rows <= 0) {
// let's initialize rows with an acceptable value
int h = 0;
rows = 0;
for (int i = 0; i < count; i++) {
if (i > 0) {
h += mVerticalGap;
}
ConstraintWidget widget = widgets[i];
if (widget == null) {
continue;
}
h += getWidgetHeight(widget, max);
if (h > max) {
break;
}
rows++;
}
}
}
if (mAlignedDimensions == null) {
mAlignedDimensions = new int[2];
}
if ((rows == 0 && orientation == VERTICAL)
|| (cols == 0 && orientation == HORIZONTAL)) {
done = true;
}
while (!done) {
// get a num of rows (or cols)
// get for each row and cols the chain of biggest elements
if (orientation == HORIZONTAL) {
rows = (int) Math.ceil(count / (float) cols);
} else {
cols = (int) Math.ceil(count / (float) rows);
}
if (mAlignedBiggestElementsInCols == null
|| mAlignedBiggestElementsInCols.length < cols) {
mAlignedBiggestElementsInCols = new ConstraintWidget[cols];
} else {
Arrays.fill(mAlignedBiggestElementsInCols, null);
}
if (mAlignedBiggestElementsInRows == null
|| mAlignedBiggestElementsInRows.length < rows) {
mAlignedBiggestElementsInRows = new ConstraintWidget[rows];
} else {
Arrays.fill(mAlignedBiggestElementsInRows, null);
}
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
int index = j * cols + i;
if (orientation == VERTICAL) {
index = i * rows + j;
}
if (index >= widgets.length) {
continue;
}
ConstraintWidget widget = widgets[index];
if (widget == null) {
continue;
}
int w = getWidgetWidth(widget, max);
if (mAlignedBiggestElementsInCols[i] == null
|| mAlignedBiggestElementsInCols[i].getWidth() < w) {
mAlignedBiggestElementsInCols[i] = widget;
}
int h = getWidgetHeight(widget, max);
if (mAlignedBiggestElementsInRows[j] == null
|| mAlignedBiggestElementsInRows[j].getHeight() < h) {
mAlignedBiggestElementsInRows[j] = widget;
}
}
}
int w = 0;
for (int i = 0; i < cols; i++) {
ConstraintWidget widget = mAlignedBiggestElementsInCols[i];
if (widget != null) {
if (i > 0) {
w += mHorizontalGap;
}
w += getWidgetWidth(widget, max);
}
}
int h = 0;
for (int j = 0; j < rows; j++) {
ConstraintWidget widget = mAlignedBiggestElementsInRows[j];
if (widget != null) {
if (j > 0) {
h += mVerticalGap;
}
h += getWidgetHeight(widget, max);
}
}
measured[HORIZONTAL] = w;
measured[VERTICAL] = h;
if (orientation == HORIZONTAL) {
if (w > max) {
if (cols > 1) {
cols--;
} else {
done = true;
}
} else {
done = true;
}
} else { // VERTICAL
if (h > max) {
if (rows > 1) {
rows--;
} else {
done = true;
}
} else {
done = true;
}
}
}
mAlignedDimensions[HORIZONTAL] = cols;
mAlignedDimensions[VERTICAL] = rows;
}
private void createAlignedConstraints(boolean isInRtl) {
if (mAlignedDimensions == null
|| mAlignedBiggestElementsInCols == null
|| mAlignedBiggestElementsInRows == null) {
return;
}
for (int i = 0; i < mDisplayedWidgetsCount; i++) {
ConstraintWidget widget = mDisplayedWidgets[i];
widget.resetAnchors();
}
int cols = mAlignedDimensions[HORIZONTAL];
int rows = mAlignedDimensions[VERTICAL];
ConstraintWidget previous = null;
float horizontalBias = mHorizontalBias;
for (int i = 0; i < cols; i++) {
int index = i;
if (isInRtl) {
index = cols - i - 1;
horizontalBias = 1 - mHorizontalBias;
}
ConstraintWidget widget = mAlignedBiggestElementsInCols[index];
if (widget == null || widget.getVisibility() == GONE) {
continue;
}
if (i == 0) {
widget.connect(widget.mLeft, mLeft, getPaddingLeft());
widget.setHorizontalChainStyle(mHorizontalStyle);
widget.setHorizontalBiasPercent(horizontalBias);
}
if (i == cols - 1) {
widget.connect(widget.mRight, mRight, getPaddingRight());
}
if (i > 0 && previous != null) {
widget.connect(widget.mLeft, previous.mRight, mHorizontalGap);
previous.connect(previous.mRight, widget.mLeft, 0);
}
previous = widget;
}
for (int j = 0; j < rows; j++) {
ConstraintWidget widget = mAlignedBiggestElementsInRows[j];
if (widget == null || widget.getVisibility() == GONE) {
continue;
}
if (j == 0) {
widget.connect(widget.mTop, mTop, getPaddingTop());
widget.setVerticalChainStyle(mVerticalStyle);
widget.setVerticalBiasPercent(mVerticalBias);
}
if (j == rows - 1) {
widget.connect(widget.mBottom, mBottom, getPaddingBottom());
}
if (j > 0 && previous != null) {
widget.connect(widget.mTop, previous.mBottom, mVerticalGap);
previous.connect(previous.mBottom, widget.mTop, 0);
}
previous = widget;
}
for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
int index = j * cols + i;
if (mOrientation == VERTICAL) {
index = i * rows + j;
}
if (index >= mDisplayedWidgets.length) {
continue;
}
ConstraintWidget widget = mDisplayedWidgets[index];
if (widget == null || widget.getVisibility() == GONE) {
continue;
}
ConstraintWidget biggestInCol = mAlignedBiggestElementsInCols[i];
ConstraintWidget biggestInRow = mAlignedBiggestElementsInRows[j];
if (widget != biggestInCol) {
widget.connect(widget.mLeft, biggestInCol.mLeft, 0);
widget.connect(widget.mRight, biggestInCol.mRight, 0);
}
if (widget != biggestInRow) {
widget.connect(widget.mTop, biggestInRow.mTop, 0);
widget.connect(widget.mBottom, biggestInRow.mBottom, 0);
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Add constraints to solver
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* Add this widget to the solver
*
* @param system the solver we want to add the widget to
* @param optimize true if {@link Optimizer#OPTIMIZATION_GRAPH} is on
*/
@Override
public void addToSolver(LinearSystem system, boolean optimize) {
super.addToSolver(system, optimize);
boolean isInRtl = getParent() != null && ((ConstraintWidgetContainer) getParent()).isRtl();
switch (mWrapMode) {
case WRAP_CHAIN: {
final int count = mChainList.size();
for (int i = 0; i < count; i++) {
WidgetsList list = mChainList.get(i);
list.createConstraints(isInRtl, i, i == count - 1);
}
}
break;
case WRAP_NONE: {
if (mChainList.size() > 0) {
WidgetsList list = mChainList.get(0);
list.createConstraints(isInRtl, 0, true);
}
}
break;
case WRAP_ALIGNED: {
createAlignedConstraints(isInRtl);
}
break;
case WRAP_CHAIN_NEW: {
final int count = mChainList.size();
for (int i = 0; i < count; i++) {
WidgetsList list = mChainList.get(i);
list.createConstraints(isInRtl, i, i == count - 1);
}
}
break;
}
needsCallbackFromSolver(false);
}
}