summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsaac Chai <ichai@google.com>2018-10-02 13:43:09 -0700
committerIsaac Chai <ichai@google.com>2018-10-02 13:43:09 -0700
commitc0de6383b1db33a235451a7b0f806a0b2d1fae6a (patch)
tree54b11b1b87d1082cdd32945caf85fb30cc3f8c63
parentc3ea82fc0bdbaa65f61066816c35147c9b26a9bf (diff)
downloadsherpa-mirror-goog-studio-master-dev.tar.gz
Bug: N/A Test: Done in another repository Change-Id: I53b124f88dae4b0e1c1b3f6eef93d7c0d95db4d0
-rw-r--r--constraintlayout/src/main/java/android/support/constraint/Barrier.java14
-rw-r--r--constraintlayout/src/main/java/android/support/constraint/ConstraintHelper.java6
-rw-r--r--constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java54
-rw-r--r--constraintlayout/src/main/java/android/support/constraint/ConstraintSet.java115
-rw-r--r--constraintlayout/src/main/res/values/attrs.xml12
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java550
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/Barrier.java3
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/Chain.java20
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java28
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java219
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java312
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetGroup.java245
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java95
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/ResolutionAnchor.java4
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/WidgetContainer.java12
-rw-r--r--solver/src/test/java/android/support/constraint/solver/AnalyzerTest.java518
-rw-r--r--solver/src/test/java/android/support/constraint/solver/ChainTest.java115
-rw-r--r--solver/src/test/java/android/support/constraint/solver/MatchConstraintTest.java10
-rw-r--r--solver/src/test/java/android/support/constraint/solver/OptimizationsTest.java4
19 files changed, 2111 insertions, 225 deletions
diff --git a/constraintlayout/src/main/java/android/support/constraint/Barrier.java b/constraintlayout/src/main/java/android/support/constraint/Barrier.java
index e3e28b1..65eac8d 100644
--- a/constraintlayout/src/main/java/android/support/constraint/Barrier.java
+++ b/constraintlayout/src/main/java/android/support/constraint/Barrier.java
@@ -107,8 +107,8 @@ public class Barrier extends ConstraintHelper {
*/
public static final int END = START + 1;
- private int mIndicatedType = LEFT;
- private int mResolvedType = LEFT;
+ private int mIndicatedType;
+ private int mResolvedType;
private android.support.constraint.solver.widgets.Barrier mBarrier;
public Barrier(Context context) {
@@ -173,8 +173,8 @@ public class Barrier extends ConstraintHelper {
}
/**
- * @hide
* @param attrs
+ * @hide
*/
@Override
protected void init(AttributeSet attrs) {
@@ -196,4 +196,12 @@ public class Barrier extends ConstraintHelper {
validateParams();
}
+ public void setAllowsGoneWidget(boolean supportGone) {
+ mBarrier.setAllowsGoneWidget(supportGone);
+ }
+
+ public boolean allowsGoneWidget() {
+ return mBarrier.allowsGoneWidget();
+ }
+
}
diff --git a/constraintlayout/src/main/java/android/support/constraint/ConstraintHelper.java b/constraintlayout/src/main/java/android/support/constraint/ConstraintHelper.java
index d496899..790b658 100644
--- a/constraintlayout/src/main/java/android/support/constraint/ConstraintHelper.java
+++ b/constraintlayout/src/main/java/android/support/constraint/ConstraintHelper.java
@@ -41,7 +41,7 @@ public abstract class ConstraintHelper extends View {
/**
* @hide
*/
- protected int mCount = 0;
+ protected int mCount;
/**
* @hide
@@ -50,7 +50,7 @@ public abstract class ConstraintHelper extends View {
/**
* @hide
*/
- protected android.support.constraint.solver.widgets.Helper mHelperWidget = null;
+ protected android.support.constraint.solver.widgets.Helper mHelperWidget;
/**
* @hide
*/
@@ -235,7 +235,7 @@ public abstract class ConstraintHelper extends View {
mHelperWidget.removeAllIds();
for (int i = 0; i < mCount; i++) {
int id = mIds[i];
- View view = container.findViewById(id);
+ View view = container.getViewById(id);
if (view != null) {
mHelperWidget.add(container.getViewWidget(view));
}
diff --git a/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java b/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java
index 0d01e33..0b080dc 100644
--- a/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java
+++ b/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java
@@ -27,6 +27,12 @@ import android.os.Build;
import android.support.constraint.solver.LinearSystem;
import android.support.constraint.solver.Metrics;
import android.support.constraint.solver.widgets.*;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.constraint.solver.widgets.ConstraintAnchor;
+import android.support.constraint.solver.widgets.ConstraintWidget;
+import android.support.constraint.solver.widgets.ConstraintWidgetContainer;
import android.support.constraint.solver.widgets.Guideline;
import android.util.AttributeSet;
import android.util.Log;
@@ -484,7 +490,7 @@ public class ConstraintLayout extends ViewGroup {
private static final boolean CACHE_MEASURED_DIMENSION = false;
/** @hide */
- public static final String VERSION = "ConstraintLayout-1.1.2";
+ public static final String VERSION = "ConstraintLayout-1.1.3";
private static final String TAG = "ConstraintLayout";
private static final boolean USE_CONSTRAINTS_HELPER = true;
@@ -1126,6 +1132,12 @@ public class ConstraintLayout extends ViewGroup {
return mLayoutWidget;
} else {
View view = mChildrenByIds.get(id);
+ if (view == null) {
+ view = findViewById(id);
+ if (view != null && view != this && view.getParent() == this) {
+ onViewAdded(view);
+ }
+ }
if (view == this) {
return mLayoutWidget;
}
@@ -1542,9 +1554,12 @@ public class ConstraintLayout extends ViewGroup {
setSelfDimensionBehaviour(widthMeasureSpec, heightMeasureSpec);
int startingWidth = mLayoutWidget.getWidth();
int startingHeight = mLayoutWidget.getHeight();
+
+ boolean runAnalyzer = false;
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
updateHierarchy();
+ runAnalyzer = true;
}
final boolean optimiseDimensions = (mOptimizationLevel & Optimizer.OPTIMIZATION_DIMENSIONS)
@@ -1564,6 +1579,43 @@ public class ConstraintLayout extends ViewGroup {
return;
}
+ if (getChildCount() > 0 && runAnalyzer) {
+ Analyzer.determineGroups(mLayoutWidget);
+ }
+ if (mLayoutWidget.mGroupsWrapOptimized) {
+ if (mLayoutWidget.mHorizontalWrapOptimized && widthMode == MeasureSpec.AT_MOST) {
+ if (mLayoutWidget.mWrapFixedWidth < widthSize) {
+ mLayoutWidget.setWidth(mLayoutWidget.mWrapFixedWidth);
+ }
+ mLayoutWidget
+ .setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED);
+ }
+ if (mLayoutWidget.mVerticalWrapOptimized && heightMode == MeasureSpec.AT_MOST) {
+ if (mLayoutWidget.mWrapFixedHeight < heightSize) {
+ mLayoutWidget.setHeight(mLayoutWidget.mWrapFixedHeight);
+ }
+ mLayoutWidget
+ .setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED);
+ }
+ }
+ // Reposition widgets dependent of layout dimension when necessary.
+ if ((mOptimizationLevel & Optimizer.OPTIMIZATION_GROUPS) == Optimizer.OPTIMIZATION_GROUPS) {
+ int width = mLayoutWidget.getWidth();
+ int height = mLayoutWidget.getHeight();
+ if (mLastMeasureWidth != width && widthMode == MeasureSpec.EXACTLY) {
+ Analyzer.setPosition(mLayoutWidget.mWidgetGroups, HORIZONTAL, width);
+ }
+ if (mLastMeasureHeight != height && heightMode == MeasureSpec.EXACTLY) {
+ Analyzer.setPosition(mLayoutWidget.mWidgetGroups, VERTICAL, height);
+ }
+ if (mLayoutWidget.mHorizontalWrapOptimized && mLayoutWidget.mWrapFixedWidth > widthSize) {
+ Analyzer.setPosition(mLayoutWidget.mWidgetGroups, HORIZONTAL, widthSize);
+ }
+ if (mLayoutWidget.mVerticalWrapOptimized && mLayoutWidget.mWrapFixedHeight > heightSize) {
+ Analyzer.setPosition(mLayoutWidget.mWidgetGroups, VERTICAL, heightSize);
+ }
+ }
+
// let's solve the linear system.
if (getChildCount() > 0) {
solveLinearSystem("First pass");
diff --git a/constraintlayout/src/main/java/android/support/constraint/ConstraintSet.java b/constraintlayout/src/main/java/android/support/constraint/ConstraintSet.java
index 2e942d3..b54cd80 100644
--- a/constraintlayout/src/main/java/android/support/constraint/ConstraintSet.java
+++ b/constraintlayout/src/main/java/android/support/constraint/ConstraintSet.java
@@ -33,6 +33,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
@@ -260,7 +261,14 @@ public class ConstraintSet {
private static final int CIRCLE = 61;
private static final int CIRCLE_RADIUS = 62;
private static final int CIRCLE_ANGLE = 63;
- private static final int UNUSED = 64;
+
+ private static final int WIDTH_PERCENT = 69;
+ private static final int HEIGHT_PERCENT = 70;
+ private static final int CHAIN_USE_RTL = 71;
+ private static final int BARRIER_DIRECTION = 72;
+ private static final int CONSTRAINT_REFERENCED_IDS = 73;
+ private static final int BARRIER_ALLOWS_GONE_WIDGETS = 74;
+ private static final int UNUSED = 75;
static {
mapToConstant.append(R.styleable.ConstraintSet_layout_constraintLeft_toLeftOf, LEFT_TO_LEFT);
@@ -333,6 +341,18 @@ public class ConstraintSet {
mapToConstant.append(R.styleable.ConstraintSet_layout_constraintCircleRadius, CIRCLE_RADIUS);
mapToConstant.append(R.styleable.ConstraintSet_layout_constraintCircleAngle, CIRCLE_ANGLE);
mapToConstant.append(R.styleable.ConstraintSet_android_id, VIEW_ID);
+
+ mapToConstant.append(R.styleable.ConstraintSet_layout_constraintWidth_percent, WIDTH_PERCENT);
+ mapToConstant.append(R.styleable.ConstraintSet_layout_constraintHeight_percent, HEIGHT_PERCENT);
+
+ mapToConstant.append(R.styleable.ConstraintSet_chainUseRtl, CHAIN_USE_RTL);
+ mapToConstant.append(R.styleable.ConstraintSet_barrierDirection, BARRIER_DIRECTION);
+ mapToConstant.append(R.styleable.ConstraintSet_constraint_referenced_ids, CONSTRAINT_REFERENCED_IDS);
+ mapToConstant.append(R.styleable.ConstraintSet_barrierAllowsGoneWidgets, BARRIER_ALLOWS_GONE_WIDGETS);
+ }
+
+ public Constraint getParameters(int mId) {
+ return get(mId);
}
private static class Constraint {
@@ -412,9 +432,11 @@ public class ConstraintSet {
public int heightMin = UNSET;
public float widthPercent = 1;
public float heightPercent = 1;
+ public boolean mBarrierAllowsGoneWidgets = false;
public int mBarrierDirection = UNSET;
public int mHelperType = UNSET;
public int [] mReferenceIds;
+ public String mReferenceIdString;
public Constraint clone() {
Constraint clone = new Constraint();
@@ -496,6 +518,7 @@ public class ConstraintSet {
clone.circleConstraint = circleConstraint;
clone.circleRadius = circleRadius;
clone.circleAngle = circleAngle;
+ clone.mBarrierAllowsGoneWidgets = mBarrierAllowsGoneWidgets;
return clone;
}
@@ -640,6 +663,8 @@ public class ConstraintSet {
param.guideEnd = guideEnd;
param.width = mWidth;
param.height = mHeight;
+
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
param.setMarginStart(startMargin);
param.setMarginEnd(endMargin);
@@ -718,6 +743,12 @@ public class ConstraintSet {
}
}
}
+ if (view instanceof Barrier) {
+ Barrier barrier = ((Barrier) view);
+ constraint.mBarrierAllowsGoneWidgets = barrier.allowsGoneWidget();
+ constraint.mReferenceIds = barrier.getReferencedIds();
+ constraint.mBarrierDirection = barrier.getType();
+ }
}
}
@@ -775,18 +806,24 @@ public class ConstraintSet {
if (mConstraints.containsKey(id)) {
used.remove(id);
Constraint constraint = mConstraints.get(id);
+ if (view instanceof Barrier) {
+ constraint.mHelperType = BARRIER_TYPE;
+ }
if (constraint.mHelperType != UNSET) {
switch (constraint.mHelperType) {
case BARRIER_TYPE:
Barrier barrier = (Barrier) view;
barrier.setId(id);
- barrier.setReferencedIds(constraint.mReferenceIds);
barrier.setType(constraint.mBarrierDirection);
- ConstraintLayout.LayoutParams param = constraintLayout
- .generateDefaultLayoutParams();
- constraint.applyTo(param);
+ barrier.setAllowsGoneWidget(constraint.mBarrierAllowsGoneWidgets);
+ if (constraint.mReferenceIds != null) {
+ barrier.setReferencedIds(constraint.mReferenceIds);
+ } else if (constraint.mReferenceIdString != null) {
+ constraint.mReferenceIds = convertReferenceString(barrier,
+ constraint.mReferenceIdString);
+ barrier.setReferencedIds(constraint.mReferenceIds);
+ }
break;
-
}
}
ConstraintLayout.LayoutParams param = (ConstraintLayout.LayoutParams) view
@@ -825,14 +862,20 @@ public class ConstraintSet {
case BARRIER_TYPE:
Barrier barrier = new Barrier(constraintLayout.getContext());
barrier.setId(id);
- barrier.setReferencedIds(constraint.mReferenceIds);
+ if (constraint.mReferenceIds != null) {
+ barrier.setReferencedIds(constraint.mReferenceIds);
+ } else if (constraint.mReferenceIdString != null) {
+ constraint.mReferenceIds = convertReferenceString(barrier,
+ constraint.mReferenceIdString);
+ barrier.setReferencedIds(constraint.mReferenceIds);
+ }
barrier.setType(constraint.mBarrierDirection);
ConstraintLayout.LayoutParams param = constraintLayout
.generateDefaultLayoutParams();
+ barrier.validateParams();
constraint.applyTo(param);
constraintLayout.addView(barrier, param);
break;
-
}
}
if (constraint.mIsGuideline) {
@@ -2366,6 +2409,25 @@ public class ConstraintSet {
case DIMENSION_RATIO:
c.dimensionRatio = a.getString(attr);
break;
+ case WIDTH_PERCENT:
+ c.widthPercent = a.getFloat(attr, 1);
+ break;
+ case HEIGHT_PERCENT:
+ c.heightPercent = a.getFloat(attr, 1);
+ break;
+ case CHAIN_USE_RTL:
+ Log.e(TAG, "CURRENTLY UNSUPPORTED"); // TODO add support or remove
+ // TODO add support or remove c.mChainUseRtl = a.getBoolean(attr,c.mChainUseRtl);
+ break;
+ case BARRIER_DIRECTION:
+ c.mBarrierDirection = a.getInt(attr,c.mBarrierDirection);
+ break;
+ case CONSTRAINT_REFERENCED_IDS:
+ c.mReferenceIdString = a.getString(attr);
+ break;
+ case BARRIER_ALLOWS_GONE_WIDGETS:
+ c.mBarrierAllowsGoneWidgets = a.getBoolean(attr,c.mBarrierAllowsGoneWidgets);
+ break;
case UNUSED:
Log.w(TAG,
"unused attribute 0x" + Integer.toHexString(attr) + " " + mapToConstant.get(attr));
@@ -2377,4 +2439,41 @@ public class ConstraintSet {
}
}
+ private int[] convertReferenceString(View view, String referenceIdString) {
+ String[] split = referenceIdString.split(",");
+ Context context = view.getContext();
+ int[]tags = new int[split.length];
+ int count = 0;
+ for (int i = 0; i < split.length; i++) {
+ String idString = split[i];
+ idString = idString.trim();
+ int tag = 0;
+ try {
+ Class res = R.id.class;
+ Field field = res.getField(idString);
+ tag = field.getInt(null);
+ }
+ catch (Exception e) {
+ // Do nothing
+ }
+ if (tag == 0) {
+ tag = context.getResources().getIdentifier(idString, "id",
+ context.getPackageName());
+ }
+
+ if (tag == 0 && view.isInEditMode() && view.getParent() instanceof ConstraintLayout) {
+ ConstraintLayout constraintLayout = (ConstraintLayout) view.getParent();
+ Object value = constraintLayout.getDesignInformation(0, idString);
+ if (value != null && value instanceof Integer) {
+ tag = (Integer) value;
+ }
+ }
+ tags[count++] = tag;
+ }
+ if (count!=split.length) {
+ tags = Arrays.copyOf(tags,count);
+ }
+ return tags;
+ }
+
}
diff --git a/constraintlayout/src/main/res/values/attrs.xml b/constraintlayout/src/main/res/values/attrs.xml
index d9d25dc..6940600 100644
--- a/constraintlayout/src/main/res/values/attrs.xml
+++ b/constraintlayout/src/main/res/values/attrs.xml
@@ -141,11 +141,12 @@
<!-- Used to set the type of optimization we apply. This is a mask. -->
<attr name="layout_optimizationLevel">
<flag name="none" value="0"/>
- <flag name="standard" value="3" /> <!-- for now only direct & barriers -->
+ <flag name="standard" value="7" /> <!-- direct, barriers, chains -->
<flag name="direct" value="1"/>
<flag name="barrier" value="2"/>
<flag name="chains" value="4"/>
<flag name="dimensions" value="8"/>
+ <flag name="groups" value="32"/>
</attr>
<!-- Specify the style of match constraint -->
@@ -340,7 +341,14 @@
<attr name="layout_constraintVertical_chainStyle" />
<attr name="layout_editor_absoluteX" />
<attr name="layout_editor_absoluteY"/>
-
+ <attr name="barrierDirection" />
+ <attr name="constraint_referenced_ids" />
+ <attr name="android:maxHeight" />
+ <attr name="android:maxWidth" />
+ <attr name="android:minHeight" />
+ <attr name="android:minWidth" />
+ <attr name="barrierAllowsGoneWidgets" />
+ <attr name="chainUseRtl" />
</declare-styleable>
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java b/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java
new file mode 100644
index 0000000..1f48990
--- /dev/null
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2018 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 android.support.constraint.solver.widgets;
+
+import android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to do widget constraints analysis.
+ * <p>
+ * Identify groups of widgets independent from each other.
+ * TODO: Identify Chains here instead.
+ */
+public class Analyzer {
+
+ private Analyzer() {
+ }
+
+ /**
+ * Find groups of constrained widgets.
+ * <p>
+ * Used to simplify the resolution process to layout the widgets when using optimizations.
+ * Wrap_content layouts require measuring the final size, groups are identified when
+ * the layout can be measured.
+ *
+ * @param layoutWidget Layout to analyze.
+ */
+ public static void determineGroups(ConstraintWidgetContainer layoutWidget) {
+ if ((layoutWidget.getOptimizationLevel() & Optimizer.OPTIMIZATION_GROUPS) != Optimizer.OPTIMIZATION_GROUPS) {
+ singleGroup(layoutWidget);
+ return;
+ }
+ layoutWidget.mSkipSolver = true;
+ layoutWidget.mGroupsWrapOptimized = false;
+ layoutWidget.mHorizontalWrapOptimized = false;
+ layoutWidget.mVerticalWrapOptimized = false;
+ final List<ConstraintWidget> widgets = layoutWidget.mChildren;
+ final List<ConstraintWidgetGroup> widgetGroups = layoutWidget.mWidgetGroups;
+ boolean horizontalWrapContent = layoutWidget.getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ boolean verticalWrapContent = layoutWidget.getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ boolean hasWrapContent = horizontalWrapContent || verticalWrapContent;
+ widgetGroups.clear();
+
+ for (ConstraintWidget widget : widgets) {
+ widget.mBelongingGroup = null;
+ widget.mGroupsToSolver = false;
+ widget.resetResolutionNodes();
+ }
+ for (ConstraintWidget widget : widgets) {
+ if (widget.mBelongingGroup == null) {
+ if (!determineGroups(widget, widgetGroups, hasWrapContent)) {
+ singleGroup(layoutWidget);
+ layoutWidget.mSkipSolver = false;
+ return;
+ }
+ }
+ }
+ int measuredWidth = 0;
+ int measuredHeight = 0;
+ // Resolve solvable widgets.
+ for (ConstraintWidgetGroup group : widgetGroups) {
+ measuredWidth = Math.max(measuredWidth,
+ getMaxDimension(group, ConstraintWidget.HORIZONTAL));
+ measuredHeight = Math.max(measuredHeight,
+ getMaxDimension(group, ConstraintWidget.VERTICAL));
+ }
+ // Change container to fixed and set resolved dimensions.
+ if (horizontalWrapContent) {
+ layoutWidget.setHorizontalDimensionBehaviour(DimensionBehaviour.FIXED);
+ layoutWidget.setWidth(measuredWidth);
+ layoutWidget.mGroupsWrapOptimized = true;
+ layoutWidget.mHorizontalWrapOptimized = true;
+ layoutWidget.mWrapFixedWidth = measuredWidth;
+ }
+ if (verticalWrapContent) {
+ layoutWidget.setVerticalDimensionBehaviour(DimensionBehaviour.FIXED);
+ layoutWidget.setHeight(measuredHeight);
+ layoutWidget.mGroupsWrapOptimized = true;
+ layoutWidget.mVerticalWrapOptimized = true;
+ layoutWidget.mWrapFixedHeight = measuredHeight;
+ }
+ setPosition(widgetGroups, ConstraintWidget.HORIZONTAL, layoutWidget.getWidth());
+ setPosition(widgetGroups, ConstraintWidget.VERTICAL, layoutWidget.getHeight());
+ }
+
+ /**
+ * @param widget Widget being traversed.
+ * @param widgetGroups Starting list to contain the widgets in this group.
+ * @param hasWrapContent Indicating if any dimension of the parent is in wrap_content.
+ * @return False if the group can't be optimized in any way.
+ */
+ private static boolean determineGroups(ConstraintWidget widget,
+ List<ConstraintWidgetGroup> widgetGroups, boolean hasWrapContent) {
+ ConstraintWidgetGroup traverseList = new ConstraintWidgetGroup(new ArrayList<ConstraintWidget>(), true);
+ widgetGroups.add(traverseList);
+ return traverse(widget, traverseList, widgetGroups, hasWrapContent);
+ }
+
+ /**
+ * Recursive function to traverse constrained widgets.
+ * The objective is to maintain in a single list all the widgets that can be reached through
+ * their constraints except for their parent.
+ *
+ * @param widget Widget being traversed.
+ * @param upperGroup List being passed down, originally by {@link #determineGroups(ConstraintWidget, List, boolean)}.
+ * @param widgetGroups List of widget groups identified.
+ * @param hasWrapContent Indicates if the layout has any dimension as wrap_content.
+ * @return If the group analysis failed or can't be done.
+ */
+ private static boolean traverse(ConstraintWidget widget, ConstraintWidgetGroup upperGroup,
+ List<ConstraintWidgetGroup> widgetGroups, boolean hasWrapContent) {
+ if (widget == null) {
+ return true;
+ }
+ widget.mOptimizerMeasured = false;
+ ConstraintWidgetContainer layoutWidget = (ConstraintWidgetContainer) widget.getParent();
+ if (widget.mBelongingGroup == null) {
+ // If it hasn't been assigned to a group.
+ widget.mOptimizerMeasurable = true;
+ upperGroup.mConstrainedGroup.add(widget);
+ widget.mBelongingGroup = upperGroup;
+ // Determine if group is measurable.
+ if (widget.mLeft.mTarget == null
+ && widget.mRight.mTarget == null
+ && widget.mTop.mTarget == null
+ && widget.mBottom.mTarget == null
+ && widget.mBaseline.mTarget == null
+ && widget.mCenter.mTarget == null) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ }
+ // Check if it has vertical bias.
+ if (widget.mTop.mTarget != null && widget.mBottom.mTarget != null) {
+ // Allow if it has no wrap content in that dimension an constrained to the parent.
+ boolean wrap = layoutWidget.getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ if (hasWrapContent) {
+ invalidate(layoutWidget, widget, upperGroup);
+ return false;
+ } else if (!(widget.mTop.mTarget.mOwner == widget.getParent()
+ && widget.mBottom.mTarget.mOwner == widget.getParent())) {
+ invalidate(layoutWidget, widget, upperGroup);
+ }
+ }
+ // Check if it has horizontal bias.
+ if (widget.mLeft.mTarget != null && widget.mRight.mTarget != null) {
+ // Allow if it has no wrap content in that dimension an constrained to the parent.
+ boolean wrap = layoutWidget.getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ if (hasWrapContent) {
+ invalidate(layoutWidget, widget, upperGroup);
+ return false;
+ } else if (!(widget.mLeft.mTarget.mOwner == widget.getParent()
+ && widget.mRight.mTarget.mOwner == widget.getParent())) {
+ invalidate(layoutWidget, widget, upperGroup);
+ }
+ }
+ if ((widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT
+ ^ widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT)
+ && widget.mDimensionRatio != 0.0f) {
+ // Calculate dimension.
+ resolveDimensionRatio(widget);
+ } else if (!(widget.getHorizontalDimensionBehaviour() != DimensionBehaviour.MATCH_CONSTRAINT
+ && widget.getVerticalDimensionBehaviour() != DimensionBehaviour.MATCH_CONSTRAINT)) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ }
+ // Is Horizontal start
+ if (((widget.mLeft.mTarget == null && widget.mRight.mTarget == null)
+ || (widget.mLeft.mTarget != null && widget.mLeft.mTarget.mOwner == widget.mParent && widget.mRight.mTarget == null)
+ || (widget.mRight.mTarget != null && widget.mRight.mTarget.mOwner == widget.mParent && widget.mLeft.mTarget == null)
+ || (widget.mLeft.mTarget != null && widget.mLeft.mTarget.mOwner == widget.mParent
+ && widget.mRight.mTarget != null && widget.mRight.mTarget.mOwner == widget.mParent))
+ && (widget.mCenter.mTarget == null)) {
+ if (!(widget instanceof Guideline) && !(widget instanceof Helper)) {
+ upperGroup.mStartHorizontalWidgets.add(widget);
+ }
+
+ }
+ // Is Vertical start
+ if (((widget.mTop.mTarget == null && widget.mBottom.mTarget == null)
+ || (widget.mTop.mTarget != null && widget.mTop.mTarget.mOwner == widget.mParent && widget.mBottom.mTarget == null)
+ || (widget.mBottom.mTarget != null && widget.mBottom.mTarget.mOwner == widget.mParent && widget.mTop.mTarget == null)
+ || (widget.mTop.mTarget != null && widget.mTop.mTarget.mOwner == widget.mParent
+ && widget.mBottom.mTarget != null && widget.mBottom.mTarget.mOwner == widget.mParent))
+ && (widget.mCenter.mTarget == null && widget.mBaseline.mTarget == null)) {
+ if (!(widget instanceof Guideline) && !(widget instanceof Helper)) {
+ upperGroup.mStartVerticalWidgets.add(widget);
+ }
+ }
+ } else {
+ // If it has, join the list and re-assign. Remove joint list from mWidgetGroups (if its a different list)
+ if (widget.mBelongingGroup != upperGroup) {
+ upperGroup.mConstrainedGroup.addAll(widget.mBelongingGroup.mConstrainedGroup);
+ upperGroup.mStartHorizontalWidgets.addAll(widget.mBelongingGroup.mStartHorizontalWidgets);
+ upperGroup.mStartVerticalWidgets.addAll(widget.mBelongingGroup.mStartVerticalWidgets);
+ if (widget.mBelongingGroup.mSkipSolver == false) {
+ upperGroup.mSkipSolver = false;
+ }
+ widgetGroups.remove(widget.mBelongingGroup);
+ for (ConstraintWidget auxWidget : widget.mBelongingGroup.mConstrainedGroup) {
+ auxWidget.mBelongingGroup = upperGroup;
+ }
+ }
+ return true;
+ }
+ // Proceed to traverse widgets, start with HelperWidgets since they contain multiple widgets.
+ if (widget instanceof Helper) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ final Helper hWidget = (Helper) widget;
+ for (int widgetsCount = 0; widgetsCount < hWidget.mWidgetsCount; widgetsCount++) {
+ if (!traverse(hWidget.mWidgets[widgetsCount], upperGroup, widgetGroups, hasWrapContent)) {
+ return false;
+ }
+ }
+ }
+ // We traverse every anchor, for wrap_content we ignore center (circular constraints).
+ final int anchorsSize = widget.mListAnchors.length;
+ for (int i = 0; i < anchorsSize; i++) {
+ final ConstraintAnchor anchor = widget.mListAnchors[i];
+ if (anchor.mTarget != null && anchor.mTarget.mOwner != widget.getParent()) {
+ if (anchor.mType == ConstraintAnchor.Type.CENTER) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ } else {
+ setConnection(anchor);
+ }
+ if (!traverse(anchor.mTarget.mOwner, upperGroup, widgetGroups, hasWrapContent)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static void invalidate(ConstraintWidgetContainer layoutWidget, ConstraintWidget widget, ConstraintWidgetGroup group) {
+ group.mSkipSolver = false;
+ layoutWidget.mSkipSolver = false;
+ widget.mOptimizerMeasurable = false;
+ }
+
+ /**
+ * Obtain the max length of a {@link ConstraintWidgetGroup} on a specific orientation.
+ * Length is saved on the group for future use as well.
+ *
+ * @param group Group of widgets being measured.
+ * @param orientation Orientation being measured.
+ * @return Max dimension in the group.
+ */
+ private static int getMaxDimension(ConstraintWidgetGroup group, int orientation) {
+ int dimension = 0;
+ int offset = orientation * 2;
+ List<ConstraintWidget> startWidgets = group.getStartWidgets(orientation);
+ final int size = startWidgets.size();
+ for (int i = 0; i < size; i++) {
+ ConstraintWidget widget = startWidgets.get(i);
+ boolean topLeftFlow = widget.mListAnchors[offset + 1].mTarget == null
+ || (widget.mListAnchors[offset].mTarget != null
+ && widget.mListAnchors[offset + 1].mTarget != null);
+ dimension = Math.max(dimension, getMaxDimensionTraversal(widget, orientation, topLeftFlow, 0));
+ }
+
+ group.mGroupDimensions[orientation] = dimension;
+ return dimension;
+ }
+
+ /**
+ * Traverse from a widget at the start of a tree (a widget constrained to any side of their parent),
+ * find the maximum length of the tree.
+ * Avoids cases when a widget's dimension shouldn't be considered.
+ *
+ * @param widget Widget being traversed.
+ * @param orientation Dimension being measured (HORIZONTAL/VERTICAL).
+ * @param topLeftFlow Indicates if the tree starts at the top or left of the container.
+ * @param depth How far the widget is from the start of the tree.
+ * @return Max dimension from the widget being traversed.
+ */
+ private static int getMaxDimensionTraversal(ConstraintWidget widget, int orientation, boolean topLeftFlow, int depth) {
+ // Start and end offset used to point to the correct anchors according to the flow
+ // of the widget at the start of the tree.
+ if (!widget.mOptimizerMeasurable) {
+ return 0;
+ }
+ int startOffset;
+ int endOffset;
+ int dimension = 0;
+ int dimensionPre = 0;
+ int dimensionPost = 0;
+ final int flow;
+ final int baselinePreDistance;
+ final int baselinePostDistance;
+ // If it has baseline, the dimensions change, despite maintaining the flow.
+ final boolean hasBaseline = widget.mBaseline.mTarget != null && orientation == ConstraintWidget.VERTICAL;
+
+ if (topLeftFlow) {
+ baselinePreDistance = widget.getBaselineDistance();
+ baselinePostDistance = widget.getHeight() - widget.getBaselineDistance();
+ startOffset = orientation * 2;
+ endOffset = startOffset + 1;
+ } else {
+ baselinePreDistance = widget.getHeight() - widget.getBaselineDistance();
+ baselinePostDistance = widget.getBaselineDistance();
+ endOffset = orientation * 2;
+ startOffset = endOffset + 1;
+ }
+
+ // Define the correct flow of direction. left -> right or left <- right.
+ // If the flow is going opposite from the startWidget, lengths and margin subtract.
+ if (widget.mListAnchors[endOffset].mTarget != null && widget.mListAnchors[startOffset].mTarget == null) {
+ flow = -1;
+ int aux = startOffset;
+ startOffset = endOffset;
+ endOffset = aux;
+ } else {
+ flow = 1;
+ }
+
+ if (hasBaseline) {
+ depth -= baselinePreDistance;
+ }
+ // Get position from horizontal/vertical bias.
+ dimension = widget.mListAnchors[startOffset].getMargin() * flow + getParentBiasOffset(widget, orientation);
+ int downDepth = dimension + depth;
+ int postTemp = ((orientation == ConstraintWidget.HORIZONTAL) ? widget.getWidth() : widget.getHeight()) * flow;
+ for (ResolutionNode targetNode : widget.mListAnchors[startOffset].getResolutionNode().dependents) {
+ final ResolutionAnchor anchor = (ResolutionAnchor) targetNode;
+ dimensionPre = Math.max(dimensionPre, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, downDepth));
+ }
+ for (ResolutionNode targetNode : widget.mListAnchors[endOffset].getResolutionNode().dependents) {
+ final ResolutionAnchor anchor = (ResolutionAnchor) targetNode;
+ dimensionPost = Math.max(dimensionPost, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, postTemp + downDepth));
+ }
+ if (hasBaseline) {
+ dimensionPre -= baselinePreDistance;
+ dimensionPost += baselinePostDistance;
+ } else {
+ dimensionPost += ((orientation == ConstraintWidget.HORIZONTAL) ? widget.getWidth() : widget.getHeight()) * flow;
+ }
+
+ // Baseline, only add distance from baseline to bottom instead of entire height.
+ int dimensionBaseline = 0;
+ if (orientation == ConstraintWidget.VERTICAL) {
+ for (ResolutionNode targetNode : widget.mBaseline.getResolutionNode().dependents) {
+ final ResolutionAnchor anchor = (ResolutionAnchor) targetNode;
+ if (flow == 1) {
+ dimensionBaseline = Math.max(dimensionBaseline, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, baselinePreDistance + downDepth));
+ } else {
+ dimensionBaseline = Math.max(dimensionBaseline, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, (baselinePostDistance * flow) + downDepth));
+ }
+ }
+ if (widget.mBaseline.getResolutionNode().dependents.size() > 0 && !hasBaseline) {
+ if (flow == 1) {
+ dimensionBaseline += baselinePreDistance;
+ } else {
+ dimensionBaseline -= baselinePostDistance;
+ }
+ }
+ }
+
+ int distanceBeforeWidget = dimension;
+ dimension += Math.max(dimensionPre, Math.max(dimensionPost, dimensionBaseline));
+ int leftTop = depth + distanceBeforeWidget;
+ int end = leftTop + postTemp;
+ if (flow == -1) {
+ int aux = end;
+ end = leftTop;
+ leftTop = aux;
+ }
+ if (topLeftFlow) {
+ Optimizer.setOptimizedWidget(widget, orientation, leftTop);
+ widget.setFrame(leftTop, end, orientation);
+ } else {
+ widget.mBelongingGroup.addWidgetsToSet(widget, orientation);
+ widget.setRelativePositioning(leftTop, orientation);
+ }
+ // Assuming widgets with only one dimension on Match_constraint would be measurable.
+ if (widget.getDimensionBehaviour(orientation) == DimensionBehaviour.MATCH_CONSTRAINT
+ && widget.mDimensionRatio != 0.0f) {
+ widget.mBelongingGroup.addWidgetsToSet(widget, orientation);
+ }
+ // Assuming is not measurable when the parent is on wrap_content.
+ if (widget.mListAnchors[startOffset].mTarget != null
+ && widget.mListAnchors[endOffset].mTarget != null) {
+ final ConstraintWidget parent = widget.getParent();
+ if (widget.mListAnchors[startOffset].mTarget.mOwner == parent
+ && widget.mListAnchors[endOffset].mTarget.mOwner == parent) {
+ widget.mBelongingGroup.addWidgetsToSet(widget, orientation);
+ }
+ }
+ return dimension;
+ }
+
+ private static void setConnection(ConstraintAnchor originAnchor) {
+ ResolutionNode originNode = originAnchor.getResolutionNode();
+ if (originAnchor.mTarget != null && originAnchor.mTarget.mTarget != originAnchor) {
+ // Go to Owner and add the dependent.
+ originAnchor.mTarget.getResolutionNode().addDependent(originNode);
+ }
+ }
+
+ /**
+ * Used when the Analyzer cannot simplify in independent groups.
+ * This will make it so all widgets are included in the same group.
+ *
+ * @param layoutWidget ConstrainedWidgetContainer being analyzed.
+ */
+ private static void singleGroup(ConstraintWidgetContainer layoutWidget) {
+ layoutWidget.mWidgetGroups.clear();
+ layoutWidget.mWidgetGroups.add(0, new ConstraintWidgetGroup(layoutWidget.mChildren));
+ }
+
+ /**
+ * Update widgets positions.
+ * Necessary for widgets dependent on the right/bottom side of the Container.
+ *
+ * @param groups Groups of widgets being updated.
+ * @param orientation Dimension to update on the widgets.
+ * @param containerLength Length of the widget container.
+ */
+ public static void setPosition(List<ConstraintWidgetGroup> groups, int orientation, int containerLength) {
+ final int groupsSize = groups.size();
+ for (int i = 0; i < groupsSize; i++) {
+ ConstraintWidgetGroup group = groups.get(i);
+ for (ConstraintWidget widget : group.getWidgetsToSet(orientation)) {
+ // We can only update those that we can measure.
+ if (widget.mOptimizerMeasurable) {
+ updateSizeDependentWidgets(widget, orientation, containerLength);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the final layout position of widgets that depend on the size of the container.
+ * Exception for dimension-ratio as a work-around.
+ *
+ * @param widget Widget being updated.
+ * @param orientation Orientation being updated.
+ * @param containerLength The final container dimension in the orientation.
+ */
+ private static void updateSizeDependentWidgets(ConstraintWidget widget, int orientation, int containerLength) {
+ final int end;
+ final int start;
+ final int offset = orientation * 2;
+ ConstraintAnchor startAnchor = widget.mListAnchors[offset];
+ ConstraintAnchor endAnchor = widget.mListAnchors[offset + 1];
+ boolean hasBias = startAnchor.mTarget != null && endAnchor.mTarget != null;
+ if (hasBias) {
+ start = getParentBiasOffset(widget, orientation) + startAnchor.getMargin();
+ Optimizer.setOptimizedWidget(widget, orientation, start);
+ return;
+ }
+ /*
+ * ConstraintLayout::internalMeasureChildren() workaround (it would reset the widget's
+ * dimension even if it was set beforehand).
+ * It is assumed that the left/top anchor has been resolved. Since only the dimension is being reset.
+ */
+ if (widget.mDimensionRatio != 0.0f && widget.getDimensionBehaviour(orientation) == DimensionBehaviour.MATCH_CONSTRAINT) {
+ int length = resolveDimensionRatio(widget);
+ start = (int) widget.mListAnchors[offset].getResolutionNode().resolvedOffset;
+ end = start + length;
+ endAnchor.getResolutionNode().resolvedTarget = startAnchor.getResolutionNode();
+ endAnchor.getResolutionNode().resolvedOffset = length;
+ endAnchor.getResolutionNode().state = ResolutionNode.RESOLVED;
+ widget.setFrame(start, end, orientation);
+ return;
+ }
+ end = containerLength - widget.getRelativePositioning(orientation);
+ start = end - widget.getLength(orientation);
+ widget.setFrame(start, end, orientation);
+ Optimizer.setOptimizedWidget(widget, orientation, start);
+ }
+
+ /**
+ * Get the offset of a widget with bias exclusively with the parent.
+ * Offset is the distance from the left/top side of the parent to the start of the widget.
+ *
+ * @param orientation Orientation for the offset.
+ * @return The distance from the root based on the bias (does not include margin distance). 0 if it can't be calculated.
+ */
+ private static int getParentBiasOffset(ConstraintWidget widget, int orientation) {
+ int offset = orientation * 2;
+ ConstraintAnchor startAnchor = widget.mListAnchors[offset];
+ ConstraintAnchor endAnchor = widget.mListAnchors[offset + 1];
+ if (startAnchor.mTarget != null && startAnchor.mTarget.mOwner == widget.mParent
+ && endAnchor.mTarget != null && endAnchor.mTarget.mOwner == widget.mParent) {
+ int length = 0;
+ int widgetDimension = 0;
+ float bias = 0.0f;
+ length = widget.mParent.getLength(orientation);
+ bias = (orientation == ConstraintWidget.HORIZONTAL) ? widget.mHorizontalBiasPercent :
+ widget.mVerticalBiasPercent;
+ widgetDimension = widget.getLength(orientation);
+ length = length - startAnchor.getMargin() - endAnchor.getMargin();
+ length = length - widgetDimension;
+ length = ((int) ((float) length * bias));
+ return length;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Calculate the widget's dimension based on dimension ratio.
+ *
+ * @return The dimension calculated.
+ */
+ private static int resolveDimensionRatio(ConstraintWidget widget) {
+ int length = ConstraintWidget.UNKNOWN;
+ if (widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
+ if (widget.mDimensionRatioSide == ConstraintWidget.HORIZONTAL) {
+ length = (int) ((float) widget.getHeight() * widget.mDimensionRatio);
+ } else {
+ length = (int) ((float) widget.getHeight() / widget.mDimensionRatio);
+ }
+ widget.setWidth(length);
+ } else if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
+ if (widget.mDimensionRatioSide == ConstraintWidget.VERTICAL) {
+ length = (int) ((float) widget.getWidth() * widget.mDimensionRatio);
+ } else {
+ length = (int) ((float) widget.getWidth() / widget.mDimensionRatio);
+ }
+ widget.setHeight(length);
+ }
+ return length;
+ }
+}
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Barrier.java b/solver/src/main/java/android/support/constraint/solver/widgets/Barrier.java
index f6a09c2..16c8910 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/Barrier.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/Barrier.java
@@ -20,6 +20,7 @@ import android.support.constraint.solver.LinearSystem;
import android.support.constraint.solver.SolverVariable;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* A Barrier takes multiple widgets
@@ -47,6 +48,8 @@ public class Barrier extends Helper {
public void setAllowsGoneWidget(boolean allowsGoneWidget) { mAllowsGoneWidget = allowsGoneWidget; }
+ public boolean allowsGoneWidget() { return mAllowsGoneWidget; }
+
@Override
public void resetResolutionNodes() {
super.resetResolutionNodes();
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java b/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java
index 94a1324..e73dea0 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java
@@ -242,7 +242,7 @@ class Chain {
if (DEBUG) {
widget = firstVisibleWidget;
while (widget != null) {
- next = widget.mListNextVisibleWidget[orientation];
+ next = widget.mNextChainWidget[orientation];
widget.mListAnchors[offset].mSolverVariable.setName("" + widget.getDebugName() + ".left");
widget.mListAnchors[offset + 1].mSolverVariable.setName("" + widget.getDebugName() + ".right");
widget = next;
@@ -278,7 +278,10 @@ class Chain {
ConstraintWidget previousVisibleWidget = firstVisibleWidget;
boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0 && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount);
while (widget != null) {
- next = widget.mListNextVisibleWidget[orientation];
+ next = widget.mNextChainWidget[orientation];
+ while (next != null && next.getVisibility() == GONE) {
+ next = next.mNextChainWidget[orientation];
+ }
if (next != null || widget == lastVisibleWidget) {
ConstraintAnchor beginAnchor = widget.mListAnchors[offset];
SolverVariable begin = beginAnchor.mSolverVariable;
@@ -331,7 +334,9 @@ class Chain {
strength);
}
}
- previousVisibleWidget = widget;
+ if (widget.getVisibility() != GONE) {
+ previousVisibleWidget = widget;
+ }
widget = next;
}
} else if (isChainSpreadInside && firstVisibleWidget != null) {
@@ -340,7 +345,10 @@ class Chain {
ConstraintWidget previousVisibleWidget = firstVisibleWidget;
boolean applyFixedEquality = chainHead.mWidgetsMatchCount > 0 && (chainHead.mWidgetsCount == chainHead.mWidgetsMatchCount);
while (widget != null) {
- next = widget.mListNextVisibleWidget[orientation];
+ next = widget.mNextChainWidget[orientation];
+ while (next != null && next.getVisibility() == GONE) {
+ next = next.mNextChainWidget[orientation];
+ }
if (widget != firstVisibleWidget && widget != lastVisibleWidget && next != null) {
if (next == lastVisibleWidget) {
next = null;
@@ -383,7 +391,9 @@ class Chain {
strength);
}
}
- previousVisibleWidget = widget;
+ if (widget.getVisibility() != GONE) {
+ previousVisibleWidget = widget;
+ }
widget = next;
}
ConstraintAnchor begin = firstVisibleWidget.mListAnchors[offset];
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java b/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java
index df18d4a..8c5bd21 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java
@@ -77,6 +77,7 @@ public class ChainHead {
private void defineChainProperties(){
int offset = mOrientation * 2;
+ ConstraintWidget lastVisited = mFirst;
// TraverseChain
ConstraintWidget widget = mFirst;
@@ -84,23 +85,20 @@ public class ChainHead {
boolean done = false;
while (!done) {
mWidgetsCount++;
- widget.mListNextVisibleWidget[mOrientation] = null;
+ widget.mNextChainWidget[mOrientation] = null;
widget.mListNextMatchConstraintsWidget[mOrientation] = null;
- if(widget.getVisibility() != ConstraintWidget.GONE) {
+ if (widget.getVisibility() != ConstraintWidget.GONE) {
// Visible widgets linked list.
if (mFirstVisibleWidget == null) {
mFirstVisibleWidget = widget;
}
- if(mLastVisibleWidget != null){
- mLastVisibleWidget.mListNextVisibleWidget[mOrientation] = widget;
- }
mLastVisibleWidget = widget;
// Match constraint linked list.
- if(widget.mListDimensionBehaviors[mOrientation] == DimensionBehaviour.MATCH_CONSTRAINT
- && (widget.mResolvedMatchConstraintDefault[mOrientation] == MATCH_CONSTRAINT_SPREAD
- || widget.mResolvedMatchConstraintDefault[mOrientation] == MATCH_CONSTRAINT_RATIO
- || widget.mResolvedMatchConstraintDefault[mOrientation] == MATCH_CONSTRAINT_PERCENT)) {
+ if (widget.mListDimensionBehaviors[mOrientation] == DimensionBehaviour.MATCH_CONSTRAINT
+ && (widget.mResolvedMatchConstraintDefault[mOrientation] == MATCH_CONSTRAINT_SPREAD
+ || widget.mResolvedMatchConstraintDefault[mOrientation] == MATCH_CONSTRAINT_RATIO
+ || widget.mResolvedMatchConstraintDefault[mOrientation] == MATCH_CONSTRAINT_PERCENT)) {
mWidgetsMatchCount++;
float weight = widget.mWeight[mOrientation];
if (weight > 0) {
@@ -119,15 +117,19 @@ public class ChainHead {
mWeightedMatchConstraintsWidgets.add(widget);
}
- if(mFirstMatchConstraintWidget == null){
+ if (mFirstMatchConstraintWidget == null) {
mFirstMatchConstraintWidget = widget;
}
- if(mLastMatchConstraintWidget != null){
+ if (mLastMatchConstraintWidget != null) {
mLastMatchConstraintWidget.mListNextMatchConstraintsWidget[mOrientation] = widget;
}
mLastMatchConstraintWidget = widget;
}
}
+ if (lastVisited != widget) {
+ lastVisited.mNextChainWidget[mOrientation] = widget;
+ }
+ lastVisited = widget;
// go to the next widget
ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget;
@@ -148,9 +150,9 @@ public class ChainHead {
}
mLast = widget;
- if(mOrientation == ConstraintWidget.HORIZONTAL && mIsRtl) {
+ if (mOrientation == ConstraintWidget.HORIZONTAL && mIsRtl) {
mHead = mLast;
- }else{
+ } else {
mHead = mFirst;
}
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java
index 271be33..d6c0cec 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java
@@ -89,6 +89,11 @@ public class ConstraintWidget {
int mResolvedDimensionRatioSide = UNKNOWN;
float mResolvedDimensionRatio = 1.0f;
+ /**
+ * Contains itself and any other widget its connected to.
+ */
+ ConstraintWidgetGroup mBelongingGroup = null;
+
private int mMaxDimension[] = {Integer.MAX_VALUE, Integer.MAX_VALUE};
private float mCircleConstraintAngle = 0;
@@ -104,8 +109,8 @@ public class ConstraintWidget {
mMaxDimension[HORIZONTAL] = maxWidth;
}
- public void setMaxHeight(int maxWidth) {
- mMaxDimension[VERTICAL] = maxWidth;
+ public void setMaxHeight(int maxHeight) {
+ mMaxDimension[VERTICAL] = maxHeight;
}
public boolean isSpreadWidth() {
@@ -175,6 +180,8 @@ public class ConstraintWidget {
// Origin of the widget
protected int mX = 0;
protected int mY = 0;
+ int mRelX = 0;
+ int mRelY = 0;
// Current draw position in container's coordinate
private int mDrawX = 0;
@@ -227,6 +234,9 @@ public class ConstraintWidget {
boolean mBottomHasCentered;
boolean mHorizontalWrapVisited;
boolean mVerticalWrapVisited;
+ boolean mOptimizerMeasurable = false;
+ boolean mOptimizerMeasured = false;
+ boolean mGroupsToSolver = false;
// Chain support
int mHorizontalChainStyle = CHAIN_SPREAD;
@@ -237,7 +247,7 @@ public class ConstraintWidget {
float[] mWeight = { UNKNOWN, UNKNOWN};
protected ConstraintWidget[] mListNextMatchConstraintsWidget = {null, null};
- protected ConstraintWidget[] mListNextVisibleWidget = {null, null};
+ protected ConstraintWidget[] mNextChainWidget = {null, null};
ConstraintWidget mHorizontalNextWidget = null;
ConstraintWidget mVerticalNextWidget = null;
@@ -307,6 +317,10 @@ public class ConstraintWidget {
if (mResolutionHeight != null) {
mResolutionHeight.reset();
}
+ mBelongingGroup = null;
+ mOptimizerMeasurable = false;
+ mOptimizerMeasured = false;
+ mGroupsToSolver = false;
}
/*-----------------------------------------------------------------------*/
@@ -829,6 +843,22 @@ public class ConstraintWidget {
}
/**
+ * Get a dimension of the widget in a particular orientation.
+ *
+ * @param orientation
+ * @return The dimension of the specified orientation.
+ */
+ public int getLength(int orientation) {
+ if (orientation == HORIZONTAL) {
+ return getWidth();
+ } else if (orientation == VERTICAL) {
+ return getHeight();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
* Return the x position of the widget, relative to the root
*
* @return x position
@@ -967,6 +997,23 @@ public class ConstraintWidget {
}
/**
+ * Return the percentage bias that is used when two opposite connections exist of the same
+ * strength in a particular orientation.
+ *
+ * @param orientation Orientation {@link #HORIZONTAL}/{@link #VERTICAL}.
+ * @return Respective percentage bias.
+ */
+ public float getBiasPercent(int orientation) {
+ if (orientation == HORIZONTAL) {
+ return mHorizontalBiasPercent;
+ } else if (orientation == VERTICAL) {
+ return mVerticalBiasPercent;
+ } else {
+ return UNKNOWN;
+ }
+ }
+
+ /**
* Return true if this widget has a baseline
*
* @return true if the widget has a baseline, false otherwise
@@ -1182,6 +1229,20 @@ public class ConstraintWidget {
}
/**
+ * Set the dimension of a widget in a particular orientation.
+ *
+ * @param length Size of the dimension.
+ * @param orientation
+ */
+ public void setLength(int length, int orientation) {
+ if (orientation == HORIZONTAL) {
+ setWidth(length);
+ } else if (orientation == VERTICAL) {
+ setHeight(length);
+ }
+ }
+
+ /**
* Set the horizontal style when MATCH_CONSTRAINT is set
*
* @param horizontalMatchStyle MATCH_CONSTRAINT_SPREAD or MATCH_CONSTRAINT_WRAP
@@ -1430,6 +1491,23 @@ public class ConstraintWidget {
if (LinearSystem.FULL_DEBUG) {
System.out.println("update from solver " + mDebugName + " " + mX + ":" + mY + " - " + mWidth + " x " + mHeight);
}
+ mOptimizerMeasured = true;
+ }
+
+ /**
+ * Set the position+dimension of the widget based on starting/ending positions on one dimension.
+ *
+ * @param start Left/Top side position of the widget.
+ * @param end Right/Bottom side position of the widget.
+ * @param orientation Orientation being set (HORIZONTAL/VERTICAL).
+ */
+ public void setFrame(int start, int end, int orientation) {
+ if (orientation == HORIZONTAL) {
+ setHorizontalDimension(start, end);
+ } else if (orientation == VERTICAL) {
+ setVerticalDimension(start, end);
+ }
+ mOptimizerMeasured = true;
}
/**
@@ -1461,6 +1539,36 @@ public class ConstraintWidget {
}
/**
+ * Get the left/top position of the widget relative to the outer side of the container (right/bottom).
+ *
+ * @param orientation
+ * @return The relative position of the widget.
+ */
+ int getRelativePositioning(int orientation) {
+ if (orientation == HORIZONTAL) {
+ return mRelX;
+ } else if (orientation == VERTICAL) {
+ return mRelY;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Set the left/top position of the widget relative to the outer side of the container (right/bottom).
+ *
+ * @param offset Offset of the relative position.
+ * @param orientation Orientation of the offset being set.
+ */
+ void setRelativePositioning(int offset, int orientation) {
+ if (orientation == HORIZONTAL) {
+ mRelX = offset;
+ } else if (orientation == VERTICAL) {
+ mRelY = offset;
+ }
+ }
+
+ /**
* Set the baseline distance relative to the top of the widget
*
* @param baseline the distance of the baseline relative to the widget's top
@@ -2071,6 +2179,22 @@ public class ConstraintWidget {
}
/**
+ * Get the widget's {@link DimensionBehaviour} in an specific orientation.
+ *
+ * @param orientation
+ * @return The {@link DimensionBehaviour} of the widget.
+ */
+ public DimensionBehaviour getDimensionBehaviour(int orientation) {
+ if (orientation == HORIZONTAL) {
+ return getHorizontalDimensionBehaviour();
+ } else if (orientation == VERTICAL) {
+ return getVerticalDimensionBehaviour();
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Set the widget's behaviour for the horizontal dimension
*
* @param behaviour the horizontal dimension's behaviour
@@ -2179,6 +2303,21 @@ public class ConstraintWidget {
return found;
}
+ /**
+ * Determine if the widget is the first element of a chain in a given orientation.
+ *
+ * @param orientation Either {@link #HORIZONTAL} or {@link #VERTICAL}
+ * @return if the widget is the head of a chain
+ */
+ private boolean isChainHead(int orientation) {
+ int offset = orientation * 2;
+ return (mListAnchors[offset].mTarget != null
+ && mListAnchors[offset].mTarget.mTarget != mListAnchors[offset])
+ && (mListAnchors[offset + 1].mTarget != null
+ && mListAnchors[offset + 1].mTarget.mTarget == mListAnchors[offset + 1]);
+ }
+
+
/*-----------------------------------------------------------------------*/
// Constraints
/*-----------------------------------------------------------------------*/
@@ -2210,33 +2349,30 @@ public class ConstraintWidget {
horizontalParentWrapContent = mParent != null ? mParent.mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT : false;
verticalParentWrapContent = mParent != null ? mParent.mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT : false;
- // Add this widget to an horizontal chain if dual connections are found
- if((mLeft.mTarget != null && mLeft.mTarget.mTarget != mLeft) &&
- mRight.mTarget != null && mRight.mTarget.mTarget == mRight){
+ // Add this widget to a horizontal chain if it is the Head of it.
+ if (isChainHead(HORIZONTAL)) {
((ConstraintWidgetContainer) mParent).addChain(this, HORIZONTAL);
- }
- if ((mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft)
- || (mRight.mTarget != null && mRight.mTarget.mTarget == mRight)) {
inHorizontalChain = true;
+ } else {
+ inHorizontalChain = isInHorizontalChain();
}
- // Add this widget to an vertical chain if dual connections are found
- if((mTop.mTarget != null && mTop.mTarget.mTarget != mTop) &&
- mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom){
+
+ // Add this widget to a vertical chain if it is the Head of it.
+ if (isChainHead(VERTICAL)) {
((ConstraintWidgetContainer) mParent).addChain(this, VERTICAL);
- }
- if ((mTop.mTarget != null && mTop.mTarget.mTarget == mTop)
- || (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom)) {
inVerticalChain = true;
+ } else {
+ inVerticalChain = isInVerticalChain();
}
if (horizontalParentWrapContent && mVisibility != GONE
- && mLeft.mTarget == null && mRight.mTarget == null) {
+ && mLeft.mTarget == null && mRight.mTarget == null) {
SolverVariable parentRight = system.createObjectVariable(mParent.mRight);
system.addGreaterThan(parentRight, right, 0, SolverVariable.STRENGTH_LOW);
}
if (verticalParentWrapContent && mVisibility != GONE
- && mTop.mTarget == null && mBottom.mTarget == null && mBaseline == null) {
+ && mTop.mTarget == null && mBottom.mTarget == null && mBaseline == null) {
SolverVariable parentBottom = system.createObjectVariable(mParent.mBottom);
system.addGreaterThan(parentBottom, bottom, 0, SolverVariable.STRENGTH_LOW);
}
@@ -2252,8 +2388,10 @@ public class ConstraintWidget {
}
// Dimensions can be either fixed (a given value) or dependent on the solver if set to MATCH_CONSTRAINT
- boolean horizontalDimensionFixed = mListDimensionBehaviors[DIMENSION_HORIZONTAL] != DimensionBehaviour.MATCH_CONSTRAINT;
- boolean verticalDimensionFixed = mListDimensionBehaviors[DIMENSION_VERTICAL] != DimensionBehaviour.MATCH_CONSTRAINT;
+ boolean horizontalDimensionFixed =
+ mListDimensionBehaviors[DIMENSION_HORIZONTAL] != DimensionBehaviour.MATCH_CONSTRAINT;
+ boolean verticalDimensionFixed =
+ mListDimensionBehaviors[DIMENSION_VERTICAL] != DimensionBehaviour.MATCH_CONSTRAINT;
// We evaluate the dimension ratio here as the connections can change.
// TODO: have a validation pass after connection instead
@@ -2562,19 +2700,11 @@ public class ConstraintWidget {
}
if (matchMinDimension > 0) {
- if (parentWrapContent) {
- system.addGreaterThan(end, begin, matchMinDimension, SolverVariable.STRENGTH_FIXED);
- } else {
- system.addGreaterThan(end, begin, matchMinDimension, SolverVariable.STRENGTH_FIXED);
- }
+ system.addGreaterThan(end, begin, matchMinDimension, SolverVariable.STRENGTH_FIXED);
dimension = Math.max(dimension, matchMinDimension);
}
if (matchMaxDimension > 0) {
- if (parentWrapContent) {
- system.addLowerThan(end, begin, matchMaxDimension, SolverVariable.STRENGTH_LOW);
- } else {
- system.addLowerThan(end, begin, matchMaxDimension, SolverVariable.STRENGTH_FIXED);
- }
+ system.addLowerThan(end, begin, matchMaxDimension, SolverVariable.STRENGTH_FIXED);
dimension = Math.min(dimension, matchMaxDimension);
}
if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) {
@@ -2684,21 +2814,36 @@ public class ConstraintWidget {
} else {
applyCentering = true;
- if (parentWrapContent) {
- system.addGreaterThan(begin, beginTarget, beginAnchor.getMargin(), SolverVariable.STRENGTH_EQUALITY);
- system.addLowerThan(end, endTarget, -endAnchor.getMargin(), SolverVariable.STRENGTH_EQUALITY);
- }
}
+ int startStrength = SolverVariable.STRENGTH_EQUALITY;
+ int endStrength = SolverVariable.STRENGTH_EQUALITY;
+ boolean applyStartConstraint = parentWrapContent;
+ boolean applyEndConstraint = parentWrapContent;
if (applyCentering) {
system.addCentering(begin, beginTarget, beginAnchor.getMargin(),
bias, endTarget, end, endAnchor.getMargin(), centeringStrength); //SolverVariable.STRENGTH_EQUALITY);
+ boolean isBeginAnchorBarrier = beginAnchor.mTarget.mOwner instanceof Barrier;
+ boolean isEndAnchorBarrier = endAnchor.mTarget.mOwner instanceof Barrier;
+
+ if (isBeginAnchorBarrier && !isEndAnchorBarrier) {
+ endStrength = SolverVariable.STRENGTH_FIXED;
+ applyEndConstraint = true;
+ } else if (!isBeginAnchorBarrier && isEndAnchorBarrier) {
+ startStrength = SolverVariable.STRENGTH_FIXED;
+ applyStartConstraint = true;
+ }
}
-
if (applyBoundsCheck) {
- // Al >= Tl & Ar <= Tr
- system.addGreaterThan(begin, beginTarget, beginAnchor.getMargin(), SolverVariable.STRENGTH_FIXED);
- system.addLowerThan(end, endTarget, -endAnchor.getMargin(), SolverVariable.STRENGTH_FIXED);
+ startStrength = SolverVariable.STRENGTH_FIXED;
+ endStrength = SolverVariable.STRENGTH_FIXED;
+ }
+
+ if ((!variableSize && applyStartConstraint) || applyBoundsCheck) {
+ system.addGreaterThan(begin, beginTarget, beginAnchor.getMargin(), startStrength);
+ }
+ if ((!variableSize && applyEndConstraint) || applyBoundsCheck) {
+ system.addLowerThan(end, endTarget, -endAnchor.getMargin(), endStrength);
}
if (parentWrapContent) {
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java
index 42ab6f1..aadad1b 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java
@@ -21,6 +21,7 @@ import android.support.constraint.solver.Metrics;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import static android.support.constraint.solver.LinearSystem.FULL_DEBUG;
import static android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
@@ -58,7 +59,15 @@ public class ConstraintWidgetContainer extends WidgetContainer {
ChainHead[] mVerticalChainsArray = new ChainHead[4];
ChainHead[] mHorizontalChainsArray = new ChainHead[4];
+ public List<ConstraintWidgetGroup> mWidgetGroups = new ArrayList<>();
+ public boolean mGroupsWrapOptimized = false;
+ public boolean mHorizontalWrapOptimized = false;
+ public boolean mVerticalWrapOptimized = false;
+ public int mWrapFixedWidth = 0;
+ public int mWrapFixedHeight = 0;
+
private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD;
+ public boolean mSkipSolver = false;
private boolean mWidthMeasuredTooSmall = false;
private boolean mHeightMeasuredTooSmall = false;
@@ -139,6 +148,8 @@ public class ConstraintWidgetContainer extends WidgetContainer {
mPaddingRight = 0;
mPaddingTop = 0;
mPaddingBottom = 0;
+ mWidgetGroups.clear();
+ mSkipSolver = false;
super.reset();
}
@@ -313,7 +324,9 @@ public class ConstraintWidgetContainer extends WidgetContainer {
if (!optimizeFor(Optimizer.OPTIMIZATION_DIMENSIONS)) {
optimizeReset();
}
- optimize();
+ if (!optimizeFor(Optimizer.OPTIMIZATION_GROUPS)) {
+ optimize();
+ }
mSystem.graphOptimizer = true;
} else {
mSystem.graphOptimizer = false;
@@ -331,154 +344,197 @@ public class ConstraintWidgetContainer extends WidgetContainer {
// Reset the chains before iterating on our children
resetChains();
- // Before we solve our system, we should call layout() on any
- // of our children that is a container.
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
- ConstraintWidget widget = mChildren.get(i);
- if (widget instanceof WidgetContainer) {
- ((WidgetContainer) widget).layout();
- }
+ if (mWidgetGroups.size() == 0){
+ mWidgetGroups.clear();
+ mWidgetGroups.add(0, new ConstraintWidgetGroup(mChildren));
}
- // Now let's solve our system as usual
- boolean needsSolving = true;
int countSolve = 0;
- while (needsSolving) {
- countSolve++;
- try {
- mSystem.reset();
- if (DEBUG) {
- setDebugSolverName(mSystem, getDebugName());
- for (int i = 0; i < count; i++) {
- ConstraintWidget widget = mChildren.get(i);
- if (widget.getDebugName() != null) {
- widget.setDebugSolverName(mSystem, widget.getDebugName());
+ final int groupSize = mWidgetGroups.size();
+ final List<ConstraintWidget> allChildren = mChildren;
+ boolean hasWrapContent = getHorizontalDimensionBehaviour() == WRAP_CONTENT || getVerticalDimensionBehaviour() == WRAP_CONTENT;
+
+ for (int groupIndex = 0; groupIndex < groupSize && !mSkipSolver; groupIndex++) {
+ if (mWidgetGroups.get(groupIndex).mSkipSolver) {
+ continue;
+ }
+ if (optimizeFor(Optimizer.OPTIMIZATION_GROUPS)) {
+ if (getHorizontalDimensionBehaviour() == DimensionBehaviour.FIXED && getVerticalDimensionBehaviour() == DimensionBehaviour.FIXED) {
+ mChildren = (ArrayList<ConstraintWidget>) mWidgetGroups.get(groupIndex).getWidgetsToSolve();
+ } else {
+ mChildren = (ArrayList<ConstraintWidget>) mWidgetGroups.get(groupIndex).mConstrainedGroup;
+ }
+ }
+ resetChains();
+ final int count = mChildren.size();
+ countSolve = 0;
+
+ // Before we solve our system, we should call layout() on any
+ // of our children that is a container.
+ for (int i = 0; i < count; i++) {
+ ConstraintWidget widget = mChildren.get(i);
+ if (widget instanceof WidgetContainer) {
+ ((WidgetContainer) widget).layout();
+ }
+ }
+
+ // Now let's solve our system as usual
+ boolean needsSolving = true;
+ while (needsSolving) {
+ countSolve++;
+ try {
+ mSystem.reset();
+ resetChains();
+ if (DEBUG) {
+ setDebugSolverName(mSystem, getDebugName());
+ for (int i = 0; i < count; i++) {
+ ConstraintWidget widget = mChildren.get(i);
+ if (widget.getDebugName() != null) {
+ widget.setDebugSolverName(mSystem, widget.getDebugName());
+ }
+ }
+ } else {
+ createObjectVariables(mSystem);
+ for (int i = 0; i < count; i++) {
+ ConstraintWidget widget = mChildren.get(i);
+ widget.createObjectVariables(mSystem);
}
}
+ needsSolving = addChildrenToSolver(mSystem);
+ if (needsSolving) {
+ mSystem.minimize();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.out.println("EXCEPTION : " + e);
+ }
+ if (needsSolving) {
+ updateChildrenFromSolver(mSystem, Optimizer.flags);
} else {
- createObjectVariables(mSystem);
+ updateFromSolver(mSystem);
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
- widget.createObjectVariables(mSystem);
+ if (widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL]
+ == DimensionBehaviour.MATCH_CONSTRAINT
+ && widget.getWidth() < widget.getWrapWidth()) {
+ Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
+ break;
+ }
+ if (widget.mListDimensionBehaviors[DIMENSION_VERTICAL]
+ == DimensionBehaviour.MATCH_CONSTRAINT
+ && widget.getHeight() < widget.getWrapHeight()) {
+ Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
+ break;
+ }
}
}
- needsSolving = addChildrenToSolver(mSystem);
- if (needsSolving) {
- mSystem.minimize();
- }
- } catch (Exception e) {
- e.printStackTrace();
- System.out.println("EXCEPTION : " + e);
- }
- if (needsSolving) {
- updateChildrenFromSolver(mSystem, Optimizer.flags);
- } else {
- updateFromSolver(mSystem);
- for (int i = 0; i < count; i++) {
- ConstraintWidget widget = mChildren.get(i);
- if (widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.MATCH_CONSTRAINT
- && widget.getWidth() < widget.getWrapWidth()) {
- Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
- break;
+ needsSolving = false;
+
+ if (hasWrapContent && countSolve < MAX_ITERATIONS
+ && Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS]) {
+ // let's get the new bounds
+ int maxX = 0;
+ int maxY = 0;
+ for (int i = 0; i < count; i++) {
+ ConstraintWidget widget = mChildren.get(i);
+ maxX = Math.max(maxX, widget.mX + widget.getWidth());
+ maxY = Math.max(maxY, widget.mY + widget.getHeight());
}
- if (widget.mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.MATCH_CONSTRAINT
- && widget.getHeight() < widget.getWrapHeight()) {
- Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
- break;
+ maxX = Math.max(mMinWidth, maxX);
+ maxY = Math.max(mMinHeight, maxY);
+ if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
+ if (getWidth() < maxX) {
+ if (DEBUG_LAYOUT) {
+ System.out.println(
+ "layout override width from " + getWidth() + " vs " + maxX);
+ }
+ setWidth(maxX);
+ mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT; // force using the solver
+ wrap_override = true;
+ needsSolving = true;
+ }
+ }
+ if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
+ if (getHeight() < maxY) {
+ if (DEBUG_LAYOUT) {
+ System.out.println(
+ "layout override height from " + getHeight() + " vs " + maxY);
+ }
+ setHeight(maxY);
+ mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT; // force using the solver
+ wrap_override = true;
+ needsSolving = true;
+ }
}
}
- }
- needsSolving = false;
-
- if (countSolve < MAX_ITERATIONS && Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS]) {
- // let's get the new bounds
- int maxX = 0;
- int maxY = 0;
- for (int i = 0; i < count; i++) {
- ConstraintWidget widget = mChildren.get(i);
- maxX = Math.max(maxX, widget.mX + widget.getWidth());
- maxY = Math.max(maxY, widget.mY + widget.getHeight());
- }
- maxX = Math.max(mMinWidth, maxX);
- maxY = Math.max(mMinHeight, maxY);
- if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
- if (getWidth() < maxX) {
+ if (true) {
+ int width = Math.max(mMinWidth, getWidth());
+ if (width > getWidth()) {
if (DEBUG_LAYOUT) {
- System.out.println("layout override width from " + getWidth() + " vs " + maxX);
+ System.out.println(
+ "layout override 2, width from " + getWidth() + " vs " + width);
}
- setWidth(maxX);
- mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT; // force using the solver
+ setWidth(width);
+ mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
wrap_override = true;
needsSolving = true;
}
- }
- if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
- if (getHeight() < maxY) {
+ int height = Math.max(mMinHeight, getHeight());
+ if (height > getHeight()) {
if (DEBUG_LAYOUT) {
- System.out.println("layout override height from " + getHeight() + " vs " + maxY);
+ System.out.println(
+ "layout override 2, height from " + getHeight() + " vs " + height);
}
- setHeight(maxY);
- mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT; // force using the solver
+ setHeight(height);
+ mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
wrap_override = true;
needsSolving = true;
}
- }
- }
- if (true) {
- int width = Math.max(mMinWidth, getWidth());
- if (width > getWidth()) {
- if (DEBUG_LAYOUT) {
- System.out.println("layout override 2, width from " + getWidth() + " vs " + width);
- }
- setWidth(width);
- mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
- wrap_override = true;
- needsSolving = true;
- }
- int height = Math.max(mMinHeight, getHeight());
- if (height > getHeight()) {
- if (DEBUG_LAYOUT) {
- System.out.println("layout override 2, height from " + getHeight() + " vs " + height);
- }
- setHeight(height);
- mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
- wrap_override = true;
- needsSolving = true;
- }
-
- if (!wrap_override) {
- if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT && prew > 0) {
- if (getWidth() > prew) {
- if (DEBUG_LAYOUT) {
- System.out.println("layout override 3, width from " + getWidth() + " vs " + prew);
+ if (!wrap_override) {
+ if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT
+ && prew > 0) {
+ if (getWidth() > prew) {
+ if (DEBUG_LAYOUT) {
+ System.out.println(
+ "layout override 3, width from " + getWidth() + " vs "
+ + prew);
+ }
+ mWidthMeasuredTooSmall = true;
+ wrap_override = true;
+ mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
+ setWidth(prew);
+ needsSolving = true;
}
- mWidthMeasuredTooSmall = true;
- wrap_override = true;
- mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
- setWidth(prew);
- needsSolving = true;
}
- }
- if (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT && preh > 0) {
- if (getHeight() > preh) {
- if (DEBUG_LAYOUT) {
- System.out.println("layout override 3, height from " + getHeight() + " vs " + preh);
+ if (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT
+ && preh > 0) {
+ if (getHeight() > preh) {
+ if (DEBUG_LAYOUT) {
+ System.out.println(
+ "layout override 3, height from " + getHeight() + " vs "
+ + preh);
+ }
+ mHeightMeasuredTooSmall = true;
+ wrap_override = true;
+ mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
+ setHeight(preh);
+ needsSolving = true;
}
- mHeightMeasuredTooSmall = true;
- wrap_override = true;
- mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
- setHeight(preh);
- needsSolving = true;
}
}
}
}
+ if (DEBUG_LAYOUT) {
+ System.out.println(
+ "Solved system in " + countSolve + " iterations (" + getWidth() + " x "
+ + getHeight() + ")");
+ }
+ // Update UnresolvedWidgets that did not need solver.
+ mWidgetGroups.get(groupIndex).updateUnresolvedWidgets();
}
- if (DEBUG_LAYOUT) {
- System.out.println("Solved system in " + countSolve + " iterations (" + getWidth() + " x " + getHeight() + ")");
- }
+ mChildren = (ArrayList<ConstraintWidget>)allChildren;
+
if (mParent != null && USE_SNAPSHOT) {
int width = Math.max(mMinWidth, getWidth());
int height = Math.max(mMinHeight, getHeight());
@@ -498,7 +554,9 @@ public class ConstraintWidgetContainer extends WidgetContainer {
if (DEBUG_GRAPH) {
for (int i = 0; i < mChildren.size(); i++) {
ConstraintWidget widget = mChildren.get(i);
- System.out.println("final child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode()
+ System.out.println(
+ "final child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft
+ .getResolutionNode()
+ ", " + widget.mTop.getResolutionNode()
+ ", " + widget.mRight.getResolutionNode()
+ ", " + widget.mBottom.getResolutionNode());
@@ -684,7 +742,8 @@ public class ConstraintWidgetContainer extends WidgetContainer {
*/
private void addHorizontalChain(ConstraintWidget widget) {
if (mHorizontalChainsSize + 1 >= mHorizontalChainsArray.length) {
- mHorizontalChainsArray = Arrays.copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2);
+ mHorizontalChainsArray = Arrays
+ .copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2);
}
mHorizontalChainsArray[mHorizontalChainsSize] = new ChainHead(widget, HORIZONTAL, isRtl());
mHorizontalChainsSize++;
@@ -698,10 +757,23 @@ public class ConstraintWidgetContainer extends WidgetContainer {
*/
private void addVerticalChain(ConstraintWidget widget) {
if (mVerticalChainsSize + 1 >= mVerticalChainsArray.length) {
- mVerticalChainsArray = Arrays.copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2);
+ mVerticalChainsArray = Arrays
+ .copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2);
}
mVerticalChainsArray[mVerticalChainsSize] = new ChainHead(widget, VERTICAL, isRtl());
mVerticalChainsSize++;
}
+ /*-----------------------------------------------------------------------*/
+ // Widgets
+ /*-----------------------------------------------------------------------*/
+
+ /**
+ * {@link #mWidgetGroups} getter.
+ *
+ * @return The list of independently constrained widget groups.
+ */
+ public List<ConstraintWidgetGroup> getWidgetGroups() {
+ return mWidgetGroups;
+ }
}
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetGroup.java b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetGroup.java
new file mode 100644
index 0000000..6781dcc
--- /dev/null
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetGroup.java
@@ -0,0 +1,245 @@
+ /*
+ * Copyright (C) 2018 The Android Open Source Project * Copyright (C) 201
+ *
+ * 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 android.support.constraint.solver.widgets;
+
+ import static android.support.constraint.solver.widgets.ConstraintWidget.HORIZONTAL;
+ import static android.support.constraint.solver.widgets.ConstraintWidget.UNKNOWN;
+ import static android.support.constraint.solver.widgets.ConstraintWidget.VERTICAL;
+
+ import java.util.ArrayList;
+ import java.util.HashSet;
+ import java.util.List;
+ import java.util.Set;
+
+ /**
+ * Class for groups of widgets constrained between each other in a ConstraintWidgetContainer.
+ * <p>
+ * Will possess the list of ConstraintWidget in the group.
+ * The mChains that exist in the group.
+ * Each group can be solved for individually.
+ */
+ public class ConstraintWidgetGroup {
+
+ public List<ConstraintWidget> mConstrainedGroup;
+ int mGroupWidth = UNKNOWN;
+ int mGroupHeight = UNKNOWN;
+ public boolean mSkipSolver = false;
+ public final int[] mGroupDimensions = {mGroupWidth, mGroupHeight};
+ /**
+ * Arrays to contain the widgets that determine the start of a group relative to their layout.
+ * A widget is at the start, if their left/top anchor is constrained to their parent.
+ * If the left/top constraint is null, is considered at the start if there are no widgets
+ * constrained to it from their right/bottom anchor.
+ */
+ List<ConstraintWidget> mStartHorizontalWidgets = new ArrayList<>();
+ List<ConstraintWidget> mStartVerticalWidgets = new ArrayList<>();
+ HashSet<ConstraintWidget> mWidgetsToSetHorizontal = new HashSet<>();
+ HashSet<ConstraintWidget> mWidgetsToSetVertical = new HashSet<>();
+ List<ConstraintWidget> mWidgetsToSolve = new ArrayList<>();
+ List<ConstraintWidget> mUnresolvedWidgets = new ArrayList<>();
+
+ ConstraintWidgetGroup(List<ConstraintWidget> widgets) {
+ this.mConstrainedGroup = widgets;
+ }
+
+ ConstraintWidgetGroup(List<ConstraintWidget> widgets, boolean skipSolver) {
+ this.mConstrainedGroup = widgets;
+ this.mSkipSolver = skipSolver;
+ }
+
+ public List<ConstraintWidget> getStartWidgets(int orientation) {
+ if (orientation == HORIZONTAL) {
+ return mStartHorizontalWidgets;
+ } else if (orientation == VERTICAL) {
+ return mStartVerticalWidgets;
+ }
+ return null;
+ }
+
+ Set<ConstraintWidget> getWidgetsToSet(int orientation) {
+ if (orientation == HORIZONTAL) {
+ return mWidgetsToSetHorizontal;
+ } else if (orientation == VERTICAL) {
+ return mWidgetsToSetVertical;
+ }
+ return null;
+ }
+
+ void addWidgetsToSet(ConstraintWidget widget, int orientation) {
+ if (orientation == HORIZONTAL) {
+ mWidgetsToSetHorizontal.add(widget);
+ } else if (orientation == VERTICAL) {
+ mWidgetsToSetVertical.add(widget);
+ }
+ }
+
+ /**
+ * Get a list of widgets that haven't been fully resolved and require the Linear Solver
+ * to resolve.
+ * Sets {@link #mUnresolvedWidgets} with the widgets that haven't been resolved, but don't
+ * require the Linear Solver.
+ *
+ * @return List of widgets to be solved.
+ */
+ List<ConstraintWidget> getWidgetsToSolve() {
+ if (!mWidgetsToSolve.isEmpty()) {
+ return mWidgetsToSolve;
+ }
+ final int size = mConstrainedGroup.size();
+ for (int i = 0; i < size; i++) {
+ ConstraintWidget widget = mConstrainedGroup.get(i);
+ if (!widget.mOptimizerMeasurable) {
+ getWidgetsToSolveTraversal((ArrayList<ConstraintWidget>)mWidgetsToSolve, widget);
+ }
+ }
+ mUnresolvedWidgets.clear();
+ mUnresolvedWidgets.addAll(mConstrainedGroup);
+ mUnresolvedWidgets.removeAll(mWidgetsToSolve);
+ return mWidgetsToSolve;
+ }
+
+ /**
+ * Helper method to find widgets to be solved.
+ *
+ * @param widgetsToSolve Current list of widgets to be solved.
+ * @param widget Widget being traversed.
+ */
+ private void getWidgetsToSolveTraversal(ArrayList<ConstraintWidget> widgetsToSolve, ConstraintWidget widget) {
+ if (widget.mGroupsToSolver) {
+ return;
+ }
+ widgetsToSolve.add(widget);
+ widget.mGroupsToSolver = true;
+ if (widget.isFullyResolved()) {
+ return;
+ }
+ if (widget instanceof Helper) {
+ Helper helper = (Helper) widget;
+ final int widgetCount = helper.mWidgetsCount;
+ for (int i = 0; i < widgetCount; i++) {
+ getWidgetsToSolveTraversal(widgetsToSolve, helper.mWidgets[i]);
+ }
+ }
+ // Propagate from every unmeasurable widget to the parent.
+ final int count = widget.mListAnchors.length;
+ for (int i = 0; i < count; i++) {
+ ConstraintAnchor targetAnchor = widget.mListAnchors[i].mTarget;
+ ConstraintWidget targetWidget = null;
+ if (targetAnchor != null) {
+ targetWidget = targetAnchor.mOwner;
+ } else {
+ continue;
+ }
+ // Traverse until we hit a resolved widget or the parent.
+ if (targetAnchor != null && (targetWidget != widget.getParent())) {
+ getWidgetsToSolveTraversal(widgetsToSolve, targetWidget);
+ }
+ }
+ }
+
+ /**
+ * After solving, update any widgets that depended on unmeasurable widgets.
+ */
+ void updateUnresolvedWidgets() {
+ final int size = mUnresolvedWidgets.size();
+ for (int i = 0; i < size; i++) {
+ ConstraintWidget widget = mUnresolvedWidgets.get(i);
+ // Needs start, end, orientation.
+ // Or left,right/top, bottom.
+ updateResolvedDimension(widget);
+ }
+ }
+
+ /**
+ * Update widget's dimension according to the widget it depends on.
+ *
+ * @param widget Widget to resolve dimension.
+ */
+ private void updateResolvedDimension(ConstraintWidget widget) {
+ int start = 0, end = 0;
+ if (widget.mOptimizerMeasurable) {
+ // No need to update dimension if it has been resolved.
+ if (widget.isFullyResolved()) {
+ return;
+ }
+ // Horizontal.
+ boolean rightSide = widget.mRight.mTarget != null;
+ ConstraintAnchor targetAnchor;
+ // Get measure if target is resolved, otherwise, resolve target.
+ if (rightSide) {
+ targetAnchor = widget.mRight.mTarget;
+ } else {
+ targetAnchor = widget.mLeft.mTarget;
+ }
+ if (targetAnchor != null) {
+ if (!targetAnchor.mOwner.mOptimizerMeasured) {
+ updateResolvedDimension(targetAnchor.mOwner);
+ }
+ if (targetAnchor.mType == ConstraintAnchor.Type.RIGHT) {
+ end = targetAnchor.mOwner.mX + targetAnchor.mOwner.getWidth();
+ } else if (targetAnchor.mType == ConstraintAnchor.Type.LEFT) {
+ end = targetAnchor.mOwner.mX;
+ }
+ }
+ if (rightSide) {
+ end -= widget.mRight.getMargin();
+ } else {
+ end += widget.mLeft.getMargin() + widget.getWidth();
+ }
+ start = end - widget.getWidth();
+ widget.setHorizontalDimension(start, end);
+ // Vertical.
+ if (widget.mBaseline.mTarget != null) {
+ targetAnchor = widget.mBaseline.mTarget;
+ if (!targetAnchor.mOwner.mOptimizerMeasured) {
+ updateResolvedDimension(targetAnchor.mOwner);
+ }
+ start = targetAnchor.mOwner.mY + targetAnchor.mOwner.mBaselineDistance
+ - widget.mBaselineDistance;
+ end = start + widget.mHeight;
+ widget.setVerticalDimension(start, end);
+ widget.mOptimizerMeasured = true;
+ return;
+ }
+ boolean bottomSide = widget.mBottom.mTarget != null;
+ // Get measure if target is resolved, otherwise, resolve target.
+ if (bottomSide) {
+ targetAnchor = widget.mBottom.mTarget;
+ } else {
+ targetAnchor = widget.mTop.mTarget;
+ }
+ if (targetAnchor != null) {
+ if (!targetAnchor.mOwner.mOptimizerMeasured) {
+ updateResolvedDimension(targetAnchor.mOwner);
+ }
+ if (targetAnchor.mType == ConstraintAnchor.Type.BOTTOM) {
+ end = targetAnchor.mOwner.mY + targetAnchor.mOwner.getHeight();
+ } else if (targetAnchor.mType == ConstraintAnchor.Type.TOP) {
+ end = targetAnchor.mOwner.mY;
+ }
+ }
+ if (bottomSide) {
+ end -= widget.mBottom.getMargin();
+ } else {
+ end += widget.mTop.getMargin() + widget.getHeight();
+ }
+ start = end - widget.getHeight();
+ widget.setVerticalDimension(start, end);
+ widget.mOptimizerMeasured = true;
+ }
+ }
+ }
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java b/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java
index a0a3fe6..48d0ff7 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java
@@ -33,9 +33,10 @@ public class Optimizer {
public static final int OPTIMIZATION_CHAIN = 1 << 2;
public static final int OPTIMIZATION_DIMENSIONS = 1 << 3;
public static final int OPTIMIZATION_RATIO = 1 << 4;
+ public static final int OPTIMIZATION_GROUPS = 1 << 5;
public static final int OPTIMIZATION_STANDARD = OPTIMIZATION_DIRECT
| OPTIMIZATION_BARRIER
- /* | OPTIMIZATION_CHAIN */
+ | OPTIMIZATION_CHAIN
/* | OPTIMIZATION_DIMENSIONS */
;
@@ -429,6 +430,9 @@ public class Optimizer {
if (widget != firstVisibleWidget) {
totalSize += widget.mListAnchors[offset].getMargin();
}
+ if (widget != lastVisibleWidget) {
+ totalSize += widget.mListAnchors[offset + 1].getMargin();
+ }
totalMargins += widget.mListAnchors[offset].getMargin();
totalMargins += widget.mListAnchors[offset + 1].getMargin();
}
@@ -452,6 +456,9 @@ public class Optimizer {
return false;
}
}
+ if (widget.mDimensionRatio != 0.0f) {
+ return false;
+ }
}
// go to the next widget
@@ -481,7 +488,7 @@ public class Optimizer {
// let's look at the endpoints
if (firstNode.target.state != ResolutionAnchor.RESOLVED
- && lastNode.target.state != ResolutionAnchor.RESOLVED) {
+ || lastNode.target.state != ResolutionAnchor.RESOLVED) {
// No resolved endpoints, let's exit
return false;
}
@@ -516,29 +523,26 @@ public class Optimizer {
}
distance += totalSize;
distance -= totalMargins;
- widget = firstVisibleWidget;
+ widget = first;
float position = firstOffset;
- if (isChainSpread) {
- distance -= (totalMargins - extraMargin);
- }
- if (isChainSpread) {
- position += widget.mListAnchors[offset + 1].getMargin();
- next = widget.mListNextVisibleWidget[orientation];
- if (next != null) {
- position += next.mListAnchors[offset].getMargin();
- }
- }
while (widget != null) {
if (system.sMetrics != null) {
system.sMetrics.nonresolvedWidgets--;
system.sMetrics.resolvedWidgets++;
system.sMetrics.chainConnectionResolved++;
}
- next = widget.mListNextVisibleWidget[orientation];
- if (next != null || widget == lastVisibleWidget) {
+ next = widget.mNextChainWidget[orientation];
+ if (next != null || widget == last) {
float dimension = distance / numMatchConstraints;
if (totalWeights > 0) {
- dimension = widget.mWeight[orientation] * distance / totalWeights;
+ if (widget.mWeight[orientation] == UNKNOWN) {
+ dimension = 0;
+ } else {
+ dimension = widget.mWeight[orientation] * distance / totalWeights;
+ }
+ }
+ if (widget.getVisibility() == GONE) {
+ dimension = 0;
}
position += widget.mListAnchors[offset].getMargin();
widget.mListAnchors[offset].getResolutionNode().resolve(firstNode.resolvedTarget,
@@ -555,23 +559,26 @@ public class Optimizer {
return true;
}
- if (distance < totalSize) {
- return false;
+ // If there is not enough space, the chain has to behave as a packed chain.
+ if (distance < 0) {
+ isChainSpread = false;
+ isChainSpreadInside = false;
+ isChainPacked = true;
}
if (isChainPacked) {
distance -= extraMargin;
// Now let's iterate on those widgets
- widget = firstVisibleWidget;
- distance = firstOffset + (distance * first.getHorizontalBiasPercent()); // start after the gap
+ widget = first;
+ distance = firstOffset + (distance * first.getBiasPercent(orientation)); // start after the gap
while (widget != null) {
if (system.sMetrics != null) {
system.sMetrics.nonresolvedWidgets--;
system.sMetrics.resolvedWidgets++;
system.sMetrics.chainConnectionResolved++;
}
- next = widget.mListNextVisibleWidget[orientation];
- if (next != null || widget == lastVisibleWidget) {
+ next = widget.mNextChainWidget[orientation];
+ if (next != null || widget == last) {
float dimension = 0;
if (orientation == HORIZONTAL) {
dimension = widget.getWidth();
@@ -596,7 +603,7 @@ public class Optimizer {
} else if (isChainSpreadInside) {
distance -= extraMargin;
}
- widget = firstVisibleWidget;
+ widget = first;
float gap = distance / (float) (numVisibleWidgets + 1);
if (isChainSpreadInside) {
if (numVisibleWidgets > 1) {
@@ -605,7 +612,10 @@ public class Optimizer {
gap = distance / 2f; // center
}
}
- distance = firstOffset + gap; // start after the gap
+ distance = firstOffset;
+ if (first.getVisibility() != GONE) {
+ distance += gap; // start after the gap
+ }
if (isChainSpreadInside && numVisibleWidgets > 1) {
distance = firstOffset + firstVisibleWidget.mListAnchors[offset].getMargin();
}
@@ -620,21 +630,27 @@ public class Optimizer {
system.sMetrics.resolvedWidgets++;
system.sMetrics.chainConnectionResolved++;
}
- next = widget.mListNextVisibleWidget[orientation];
- if (next != null || widget == lastVisibleWidget) {
+ next = widget.mNextChainWidget[orientation];
+ if (next != null || widget == last) {
float dimension = 0;
if (orientation == HORIZONTAL) {
dimension = widget.getWidth();
} else {
dimension = widget.getHeight();
}
+ if (widget != firstVisibleWidget) {
+ distance += widget.mListAnchors[offset].getMargin();
+ }
widget.mListAnchors[offset].getResolutionNode().resolve(firstNode.resolvedTarget,
distance);
widget.mListAnchors[offset + 1].getResolutionNode().resolve(firstNode.resolvedTarget,
distance + dimension);
widget.mListAnchors[offset].getResolutionNode().addResolvedValue(system);
widget.mListAnchors[offset + 1].getResolutionNode().addResolvedValue(system);
- distance += dimension + gap;
+ distance += dimension + widget.mListAnchors[offset + 1].getMargin();
+ if (next != null && next.getVisibility() != GONE) {
+ distance += gap;
+ }
}
widget = next;
}
@@ -642,4 +658,29 @@ public class Optimizer {
return true; // optimized!
}
+
+ //TODO: Might want to use ResolutionAnchor::resolve(target, offset).
+ /**
+ * Set a {@link ConstraintWidget} optimized position and dimension in an specific orientation.
+ *
+ * @param widget Widget to be optimized.
+ * @param orientation Orientation to set optimization (HORIZONTAL{0}/VERTICAL{1}).
+ * @param resolvedOffset The resolved offset of the widget with respect to the root.
+ */
+ static void setOptimizedWidget(ConstraintWidget widget, int orientation, int resolvedOffset) {
+ final int startOffset = orientation * 2;
+ final int endOffset = startOffset + 1;
+ // Left/top of widget.
+ widget.mListAnchors[startOffset].getResolutionNode().resolvedTarget =
+ widget.getParent().mLeft.getResolutionNode();
+ widget.mListAnchors[startOffset].getResolutionNode().resolvedOffset =
+ resolvedOffset;
+ widget.mListAnchors[startOffset].getResolutionNode().state = ResolutionNode.RESOLVED;
+ // Right/bottom of widget.
+ widget.mListAnchors[endOffset].getResolutionNode().resolvedTarget =
+ widget.mListAnchors[startOffset].getResolutionNode();
+ widget.mListAnchors[endOffset].getResolutionNode().resolvedOffset =
+ widget.getLength(orientation);
+ widget.mListAnchors[endOffset].getResolutionNode().state = ResolutionNode.RESOLVED;
+ }
}
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionAnchor.java b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionAnchor.java
index 67865aa..9e3379f 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionAnchor.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionAnchor.java
@@ -304,10 +304,10 @@ public class ResolutionAnchor extends ResolutionNode {
SolverVariable sv = myAnchor.getSolverVariable();
if (resolvedTarget == null) {
- system.addEquality(sv, (int) resolvedOffset);
+ system.addEquality(sv, (int) (resolvedOffset + 0.5f));
} else {
SolverVariable v = system.createObjectVariable(resolvedTarget.myAnchor);
- system.addEquality(sv, v, (int) resolvedOffset, SolverVariable.STRENGTH_FIXED);
+ system.addEquality(sv, v, (int) (resolvedOffset + 0.5f), SolverVariable.STRENGTH_FIXED);
}
}
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/WidgetContainer.java b/solver/src/main/java/android/support/constraint/solver/widgets/WidgetContainer.java
index c6b9796..3eb32ce 100644
--- a/solver/src/main/java/android/support/constraint/solver/widgets/WidgetContainer.java
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/WidgetContainer.java
@@ -78,6 +78,18 @@ public class WidgetContainer extends ConstraintWidget {
}
/**
+ * Add multiple child widgets.
+ *
+ * @param widgets to add
+ */
+ public void add(ConstraintWidget... widgets) {
+ final int count = widgets.length;
+ for (int i = 0; i < count; i++) {
+ add(widgets[i]);
+ }
+ }
+
+ /**
* Remove a child widget
*
* @param widget to remove
diff --git a/solver/src/test/java/android/support/constraint/solver/AnalyzerTest.java b/solver/src/test/java/android/support/constraint/solver/AnalyzerTest.java
new file mode 100644
index 0000000..db82c61
--- /dev/null
+++ b/solver/src/test/java/android/support/constraint/solver/AnalyzerTest.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2018 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 android.support.constraint.solver;
+
+import android.support.constraint.solver.widgets.Analyzer;
+import android.support.constraint.solver.widgets.ConstraintAnchor.Type;
+import android.support.constraint.solver.widgets.ConstraintWidget;
+import android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour;
+import android.support.constraint.solver.widgets.ConstraintWidgetContainer;
+import android.support.constraint.solver.widgets.Optimizer;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+public class AnalyzerTest {
+
+ @Test
+ public void basicAnalyzerTest() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.BOTTOM, root, Type.BOTTOM);
+ C.connect(Type.RIGHT, root, Type.RIGHT);
+ C.connect(Type.TOP, root, Type.TOP);
+
+ root.add(A, B, C);
+ root.layout();
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 3);
+ }
+
+ @Test
+ public void basicAnalyzerTest2() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ A.connect(Type.RIGHT, B, Type.RIGHT);
+ B.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.RIGHT, C, Type.LEFT);
+ C.connect(Type.RIGHT, root, Type.RIGHT);
+ C.connect(Type.TOP, root, Type.TOP);
+
+ root.add(A, B, C);
+ root.layout();
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 1);
+ }
+
+ @Test
+ public void extendedAnalyzerTest() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+ ConstraintWidget D = new ConstraintWidget(20, 20);
+ ConstraintWidget E = new ConstraintWidget(20, 20);
+ ConstraintWidget F = new ConstraintWidget(20, 20);
+ ConstraintWidget G = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+ D.setDebugSolverName(root.getSystem(), "D");
+ E.setDebugSolverName(root.getSystem(), "E");
+ F.setDebugSolverName(root.getSystem(), "F");
+ G.setDebugSolverName(root.getSystem(), "G");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.BOTTOM, root, Type.BOTTOM);
+ A.connect(Type.RIGHT, B, Type.LEFT);
+ B.connect(Type.LEFT, A, Type.RIGHT);
+ B.connect(Type.BOTTOM, root, Type.BOTTOM);
+ B.connect(Type.RIGHT, C, Type.LEFT);
+ C.connect(Type.LEFT, B, Type.RIGHT);
+ C.connect(Type.BOTTOM, root, Type.BOTTOM);
+ C.connect(Type.RIGHT, root, Type.RIGHT);
+
+ D.connect(Type.LEFT, root, Type.LEFT);
+ D.connect(Type.BOTTOM, A, Type.TOP);
+
+ E.connect(Type.RIGHT, root, Type.RIGHT);
+ E.connect(Type.BOTTOM, C, Type.TOP);
+ E.connect(Type.TOP, F, Type.BOTTOM);
+
+ F.connect(Type.LEFT, root, Type.LEFT);
+ F.connect(Type.BOTTOM, D, Type.TOP);
+
+ G.connect(Type.RIGHT, root, Type.RIGHT);
+ G.connect(Type.BOTTOM, root, Type.BOTTOM);
+
+ root.add(A, B, C, D, E, F, G);
+ root.layout();
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 2);
+ assertEquals(root.getWidgetGroups().get(0).mConstrainedGroup.size(), 6);
+ assertEquals(root.getWidgetGroups().get(1).mConstrainedGroup.size(), 1);
+
+ assertFalse(root.getWidgetGroups().get(0).mConstrainedGroup
+ .contains(root.getWidgetGroups().get(1).mConstrainedGroup.get(0)));
+ }
+
+ @Test
+ public void basicWrapContentGroup() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+ C.connect(Type.LEFT, root, Type.LEFT);
+ C.connect(Type.TOP, A, Type.TOP);
+
+ root.add(A, B, C);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ // Make sure the right number of groups is identified and have the right properties.
+ assertEquals(root.getWidgetGroups().size(), 1);
+ assertEquals(root.getWidgetGroups().get(0).mGroupDimensions[0], 20);
+ assertEquals(root.getWidgetGroups().get(0).mGroupDimensions[1], 40);
+ assertEquals(root.getWidgetGroups().get(0).getStartWidgets(0).size(), 3);
+ assertEquals(root.getWidgetGroups().get(0).getStartWidgets(1).size(), 1);
+ // The layout widget should now have the maximum width and height and be set as fixed.
+ assertEquals(root.getWidth(), 20);
+ assertEquals(root.getHeight(), 40);
+ assertEquals(root.getHorizontalDimensionBehaviour(), DimensionBehaviour.FIXED);
+ assertEquals(root.getVerticalDimensionBehaviour(), DimensionBehaviour.FIXED);
+ }
+
+ @Test
+ public void twoDirectionWrapContentGroup() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(10, 10);
+
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+ ConstraintWidget D = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+ D.setDebugSolverName(root.getSystem(), "D");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+
+ C.connect(Type.LEFT, root, Type.LEFT);
+ C.connect(Type.BOTTOM, root, Type.BOTTOM);
+ D.connect(Type.LEFT, root, Type.LEFT);
+ D.connect(Type.BOTTOM, C, Type.BOTTOM);
+
+ root.add(A, B, C, D);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 2);
+ assertEquals(root.getWidgetGroups().get(0).mGroupDimensions[0], 20);
+ assertEquals(root.getWidgetGroups().get(0).mGroupDimensions[1], 30);
+ assertEquals(root.getWidgetGroups().get(1).mGroupDimensions[0], 20);
+ assertEquals(root.getWidgetGroups().get(1).mGroupDimensions[1], 20);
+ assertEquals(root.getWidth(), 20);
+ assertEquals(root.getHeight(), 30);
+ }
+
+ @Test
+ public void horizontalVerticalWrapContentGroup() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(25, 22);
+ ConstraintWidget B = new ConstraintWidget(25, 22);
+
+ ConstraintWidget C = new ConstraintWidget(20, 10);
+ ConstraintWidget D = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+ D.setDebugSolverName(root.getSystem(), "D");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+
+ C.connect(Type.LEFT, root, Type.LEFT);
+ C.connect(Type.BOTTOM, root, Type.BOTTOM);
+ D.connect(Type.LEFT, C, Type.RIGHT);
+ D.connect(Type.BOTTOM, root, Type.BOTTOM);
+
+ root.add(A, B, C, D);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 2);
+ assertEquals(root.getWidgetGroups().get(0).mGroupDimensions[0], 25);
+ assertEquals(root.getWidgetGroups().get(0).mGroupDimensions[1], 44);
+ assertEquals(root.getWidgetGroups().get(1).mGroupDimensions[0], 40);
+ assertEquals(root.getWidgetGroups().get(1).mGroupDimensions[1], 20);
+ assertEquals(root.getWidth(), 40);
+ assertEquals(root.getHeight(), 44);
+ }
+
+ @Test
+ public void ignoreWidgetOnReverseFlowWrapContentGroup() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(30, 100);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+ // Widget C is going in a different direction: bottom to top, instead of top to bottom.
+ C.connect(Type.LEFT, root, Type.LEFT);
+ C.connect(Type.BOTTOM, B, Type.BOTTOM);
+
+ root.add(A, B, C);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 1);
+ assertEquals(root.getWidth(), 30);
+ assertEquals(root.getHeight(), 40);
+ }
+
+ @Test
+ public void maxSizeOnReverseDirectionWrapTest() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(30, 100);
+ ConstraintWidget D = new ConstraintWidget(20, 200);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+ D.setDebugSolverName(root.getSystem(), "D");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+ // Widget C is going in a different direction: bottom to top, instead of top to bottom.
+ C.connect(Type.LEFT, root, Type.LEFT);
+ C.connect(Type.BOTTOM, B, Type.BOTTOM);
+ D.connect(Type.LEFT, root, Type.LEFT);
+ D.connect(Type.TOP, C, Type.TOP);
+
+ root.add(A, B, C, D);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 1);
+ assertEquals(root.getWidth(), 30);
+ assertEquals(root.getHeight(), 140);
+ }
+
+ @Test
+ public void basicBaselineWrapTest() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 400, 400);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ A.setBaselineDistance(10);
+ B.setBaselineDistance(5);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, root, Type.LEFT);
+ B.connect(Type.BASELINE, A, Type.BASELINE);
+
+ root.add(A, B);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 1);
+ assertEquals(root.getWidth(), 20);
+ assertEquals(root.getHeight(), 25);
+ }
+
+ @Test
+ public void baselineToBaselineWrapTest() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 400, 400);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 80);
+ A.setBaselineDistance(5);
+ B.setBaselineDistance(10);
+ C.setBaselineDistance(15);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.BOTTOM, root, Type.BOTTOM);
+ B.connect(Type.LEFT, A, Type.RIGHT);
+ B.connect(Type.BASELINE, A, Type.BASELINE);
+ C.connect(Type.LEFT, B, Type.RIGHT);
+ C.connect(Type.BASELINE, A, Type.BASELINE);
+
+ root.add(A, B, C);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+
+ Analyzer.determineGroups(root);
+
+ assertEquals(root.getWidgetGroups().size(), 1);
+ assertEquals(root.getWidth(), 60);
+ assertEquals(root.getHeight(), 30);
+ }
+
+ @Test
+ public void skipSolverBasic() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, A, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+ C.connect(Type.LEFT, B, Type.LEFT);
+ C.connect(Type.TOP, B, Type.BOTTOM);
+
+ root.add(A, B, C);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.FIXED);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.FIXED);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ Analyzer.determineGroups(root);
+
+ assertTrue(root.mSkipSolver);
+ assertEquals(root.mWidgetGroups.size(), 1);
+ assertTrue(root.mWidgetGroups.get(0).mSkipSolver);
+ }
+
+ @Test
+ public void skipSolverWrap() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, A, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+ C.connect(Type.LEFT, B, Type.LEFT);
+ C.connect(Type.TOP, B, Type.BOTTOM);
+
+ root.add(A, B, C);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ Analyzer.determineGroups(root);
+
+ assertTrue(root.mSkipSolver);
+ assertEquals(root.mWidgetGroups.size(), 1);
+ assertTrue(root.mWidgetGroups.get(0).mSkipSolver);
+ }
+
+ @Test
+ public void skipSolverMixedDimBehaviour() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.LEFT, A, Type.LEFT);
+ B.connect(Type.TOP, A, Type.BOTTOM);
+ C.connect(Type.LEFT, B, Type.LEFT);
+ C.connect(Type.TOP, B, Type.BOTTOM);
+
+ root.add(A, B, C);
+ root.setHorizontalDimensionBehaviour(DimensionBehaviour.WRAP_CONTENT);
+ root.setVerticalDimensionBehaviour(DimensionBehaviour.FIXED);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ Analyzer.determineGroups(root);
+
+ assertTrue(root.mSkipSolver);
+ assertEquals(root.mWidgetGroups.size(), 1);
+ assertTrue(root.mWidgetGroups.get(0).mSkipSolver);
+ }
+
+ @Test
+ public void skipSolverOneGroup() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 800, 800);
+ ConstraintWidget A = new ConstraintWidget(20, 20);
+ ConstraintWidget B = new ConstraintWidget(20, 20);
+ ConstraintWidget C = new ConstraintWidget(20, 20);
+
+ root.setDebugSolverName(root.getSystem(), "root");
+ A.setDebugSolverName(root.getSystem(), "A");
+ B.setDebugSolverName(root.getSystem(), "B");
+ C.setDebugSolverName(root.getSystem(), "C");
+
+ A.connect(Type.LEFT, root, Type.LEFT);
+ A.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.RIGHT, root, Type.RIGHT);
+ B.connect(Type.TOP, root, Type.TOP);
+ B.connect(Type.BOTTOM, C, Type.TOP);
+ C.connect(Type.RIGHT, root, Type.RIGHT);
+ C.connect(Type.BOTTOM, root, Type.BOTTOM);
+
+ root.add(A, B, C);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_STANDARD | Optimizer.OPTIMIZATION_GROUPS);
+ Analyzer.determineGroups(root);
+
+ assertFalse(root.mSkipSolver);
+ assertEquals(root.mWidgetGroups.size(), 2);
+ assertTrue(root.mWidgetGroups.get(0).mSkipSolver);
+ assertFalse(root.mWidgetGroups.get(1).mSkipSolver);
+ }
+}
diff --git a/solver/src/test/java/android/support/constraint/solver/ChainTest.java b/solver/src/test/java/android/support/constraint/solver/ChainTest.java
index cf1dca6..520a83b 100644
--- a/solver/src/test/java/android/support/constraint/solver/ChainTest.java
+++ b/solver/src/test/java/android/support/constraint/solver/ChainTest.java
@@ -180,6 +180,7 @@ public class ChainTest {
B.setDebugName("B");
root.add(A);
root.add(B);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_NONE);
A.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT);
A.connect(ConstraintAnchor.Type.RIGHT, B, ConstraintAnchor.Type.LEFT);
B.connect(ConstraintAnchor.Type.LEFT, A, ConstraintAnchor.Type.RIGHT);
@@ -279,6 +280,120 @@ public class ChainTest {
assertEquals(A.getWidth() + B.getWidth(), root.getWidth());
}
+ /**
+ * testPackChain with current Chain Optimizations.
+ */
+ @Test
+ public void testPackChainOpt() {
+ ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 600, 600);
+ ConstraintWidget A = new ConstraintWidget(100, 20);
+ ConstraintWidget B = new ConstraintWidget(100, 20);
+ root.setDebugName("root");
+ A.setDebugName("A");
+ B.setDebugName("B");
+ root.add(A);
+ root.add(B);
+ root.setOptimizationLevel(Optimizer.OPTIMIZATION_DIRECT | Optimizer.OPTIMIZATION_BARRIER
+ | Optimizer.OPTIMIZATION_CHAIN);
+ A.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT);
+ A.connect(ConstraintAnchor.Type.RIGHT, B, ConstraintAnchor.Type.LEFT);
+ B.connect(ConstraintAnchor.Type.LEFT, A, ConstraintAnchor.Type.RIGHT);
+ B.connect(ConstraintAnchor.Type.RIGHT, root, ConstraintAnchor.Type.RIGHT);
+ A.setHorizontalChainStyle(ConstraintWidget.CHAIN_PACKED);
+ root.layout();
+ System.out.println("a) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), 100);
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight());
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ A.setVisibility(ConstraintWidget.GONE);
+ root.layout();
+ System.out.println("b) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 0);
+ assertEquals(B.getWidth(), 100);
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight());
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ B.setVisibility(ConstraintWidget.GONE);
+ root.layout();
+ System.out.println("c) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 0);
+ assertEquals(B.getWidth(), 0);
+ assertEquals(A.getLeft(), 300);
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ A.setVisibility(ConstraintWidget.VISIBLE);
+ A.setWidth(100);
+ root.layout();
+ System.out.println("d) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), 0);
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight());
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ A.setVisibility(ConstraintWidget.VISIBLE);
+ A.setWidth(100);
+ A.setHeight(20);
+ B.setVisibility(ConstraintWidget.VISIBLE);
+ B.setWidth(100);
+ B.setHeight(20);
+ B.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
+ B.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_WRAP, 0, 0, 1);
+ root.layout();
+ System.out.println("e) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), 100);
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight());
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ B.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_SPREAD, 0, 0, 1);
+ root.layout();
+ System.out.println("f) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), 500);
+ assertEquals(A.getLeft(), 0);
+ assertEquals(B.getLeft(), 100);
+ B.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_SPREAD, 0, 50, 1);
+ root.layout();
+ System.out.println("g) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), 50);
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight());
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ B.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_PERCENT, 0, 0, 0.3f);
+ root.layout();
+ System.out.println("h) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), (int) (0.3f * 600));
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight());
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ B.setDimensionRatio("16:9");
+ B.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_RATIO, 0, 0, 1);
+ root.layout();
+ System.out.println("i) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 100);
+ assertEquals(B.getWidth(), (int)(16f/9f*20), 1);
+ assertEquals(A.getLeft(), root.getWidth() - B.getRight(), 1);
+ assertEquals(B.getLeft(), A.getLeft() + A.getWidth());
+ A.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
+ A.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_SPREAD, 0, 0, 1);
+ B.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
+ B.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_SPREAD, 0, 0, 1);
+ B.setDimensionRatio(0, 0);
+ A.setVisibility(ConstraintWidget.VISIBLE);
+ A.setWidth(100);
+ A.setHeight(20);
+ B.setVisibility(ConstraintWidget.VISIBLE);
+ B.setWidth(100);
+ B.setHeight(20);
+ root.layout();
+ System.out.println("j) A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), B.getWidth());
+ assertEquals(A.getWidth() + B.getWidth(), root.getWidth());
+ A.setHorizontalWeight(1);
+ B.setHorizontalWeight(3);
+ root.layout();
+ System.out.println("k) A: " + A + " B: " + B);
+ assertEquals(A.getWidth() * 3, B.getWidth());
+ assertEquals(A.getWidth() + B.getWidth(), root.getWidth());
+ }
+
@Test
public void testSpreadChain() {
ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 600, 600);
diff --git a/solver/src/test/java/android/support/constraint/solver/MatchConstraintTest.java b/solver/src/test/java/android/support/constraint/solver/MatchConstraintTest.java
index 8780fee..f379825 100644
--- a/solver/src/test/java/android/support/constraint/solver/MatchConstraintTest.java
+++ b/solver/src/test/java/android/support/constraint/solver/MatchConstraintTest.java
@@ -46,11 +46,17 @@ public class MatchConstraintTest {
assertEquals(A.getWidth(), 150);
assertEquals(B.getWidth(), 100);
assertEquals(root.getWidth(), 150);
- root.setWidth(150);
B.setWidth(200);
root.setWidth(0);
root.layout();
- System.out.println("root: " + root + " A: " + A + " B: " + B);
+ System.out.println("a) root: " + root + " A: " + A + " B: " + B);
+ assertEquals(A.getWidth(), 150);
+ assertEquals(B.getWidth(), 200);
+ assertEquals(root.getWidth(), 200);
+ A.setHorizontalMatchStyle(ConstraintWidget.MATCH_CONSTRAINT_SPREAD, 150, 200, 1);
+ root.setWidth(0);
+ root.layout();
+ System.out.println("b) root: " + root + " A: " + A + " B: " + B);
assertEquals(A.getWidth(), 200);
assertEquals(B.getWidth(), 200);
assertEquals(root.getWidth(), 200);
diff --git a/solver/src/test/java/android/support/constraint/solver/OptimizationsTest.java b/solver/src/test/java/android/support/constraint/solver/OptimizationsTest.java
index 7b7abb7..2d07969 100644
--- a/solver/src/test/java/android/support/constraint/solver/OptimizationsTest.java
+++ b/solver/src/test/java/android/support/constraint/solver/OptimizationsTest.java
@@ -48,9 +48,9 @@ public class OptimizationsTest {
System.out.println("1) A: " + A);
assertEquals(A.getLeft(), 8);
- assertEquals(A.getTop(), 162);
+ assertEquals(A.getTop(), 163);
assertEquals(A.getRight(), 592);
- assertEquals(A.getBottom(), 172);
+ assertEquals(A.getBottom(), 173);
A.setVisibility(ConstraintWidget.GONE);
root.layout();