diff options
author | Nicolas Roard <nicolasroard@google.com> | 2018-04-05 18:14:03 -0700 |
---|---|---|
committer | Nicolas Roard <nicolasroard@google.com> | 2018-04-10 23:30:38 -0700 |
commit | fc2b3a677298c422b210dd23892d0d16e7a9de2a (patch) | |
tree | 84d31df9d8af6fdfc44b37560d18387186a8e276 | |
parent | 8b826acf465e2688a3a61c3d6f34632b25de7545 (diff) | |
download | sherpa-fc2b3a677298c422b210dd23892d0d16e7a9de2a.tar.gz |
Add dimensions optimization.
Limit the number of measures on children when possible.
Test: sherpa tests passing.
Fixes: 77503701
Change-Id: Ic154a725ff9c1722baec8037b94b5e1a7f9b858e
12 files changed, 1021 insertions, 418 deletions
diff --git a/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java b/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java index e17f689..24c44b4 100644 --- a/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java +++ b/constraintlayout/src/main/java/android/support/constraint/ConstraintLayout.java @@ -465,6 +465,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; * <li><b>direct</b> : optimize direct constraints</li> * <li><b>barrier</b> : optimize barrier constraints</li> * <li><b>chain</b> : optimize chain constraints (experimental)</li> + * <li><b>dimensions</b> : optimize dimensions measures (experimental), reducing the number of measures of match constraints elements</li> * </ul> * </p> * <p>This attribute is a mask, so you can decide to turn on or off specific optimizations by listing the ones you want. @@ -479,7 +480,7 @@ public class ConstraintLayout extends ViewGroup { static final boolean ALLOWS_EMBEDDED = false; /** @hide */ - public static final String VERSION="ConstraintLayout-1.1.0"; + public static final String VERSION = "ConstraintLayout-1.1.0"; private static final String TAG = "ConstraintLayout"; private static final boolean USE_CONSTRAINTS_HELPER = true; @@ -522,6 +523,7 @@ public class ConstraintLayout extends ViewGroup { * @hide */ public final static int DESIGN_INFO_ID = 0; + private Metrics mMetrics; /** * @hide @@ -712,7 +714,6 @@ public class ConstraintLayout extends ViewGroup { * The minimum width of this view. * * @return The minimum width of this view - * * @see #setMinWidth(int) */ public int getMinWidth() { @@ -723,7 +724,6 @@ public class ConstraintLayout extends ViewGroup { * The minimum height of this view. * * @return The minimum height of this view - * * @see #setMinHeight(int) */ public int getMinHeight() { @@ -771,7 +771,6 @@ public class ConstraintLayout extends ViewGroup { * The maximum height of this view. * * @return The maximum height of this view - * * @see #setMaxHeight(int) */ public int getMaxHeight() { @@ -1130,9 +1129,9 @@ public class ConstraintLayout extends ViewGroup { } /** - * @hide * @param view * @return + * @hide */ public final ConstraintWidget getViewWidget(View view) { if (view == this) { @@ -1165,13 +1164,13 @@ public class ConstraintLayout extends ViewGroup { // unless they are marked as MATCH_CONSTRAINT_WRAP boolean doMeasure = (params.horizontalDimensionFixed - || params.verticalDimensionFixed) - || (!params.horizontalDimensionFixed + || params.verticalDimensionFixed) + || (!params.horizontalDimensionFixed && (params.matchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) - || params.width == MATCH_PARENT) - || (!params.verticalDimensionFixed + || params.width == MATCH_PARENT) + || (!params.verticalDimensionFixed && (params.matchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP - || params.height == MATCH_PARENT)); + || params.height == MATCH_PARENT)); boolean didWrapMeasureWidth = false; boolean didWrapMeasureHeight = false; @@ -1209,6 +1208,9 @@ public class ConstraintLayout extends ViewGroup { heightPadding, height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (mMetrics != null) { + mMetrics.measures++; + } widget.setWidthWrapContent(width == WRAP_CONTENT); widget.setHeightWrapContent(height == WRAP_CONTENT); @@ -1233,6 +1235,10 @@ public class ConstraintLayout extends ViewGroup { } } } + } + + private void updatePostMeasures() { + final int widgetsCount = getChildCount(); for (int i = 0; i < widgetsCount; i++) { final View child = getChildAt(i); if (child instanceof Placeholder) { @@ -1251,12 +1257,226 @@ public class ConstraintLayout extends ViewGroup { /** * @hide + * Measures widgets in two steps, trying to solve the constraints partially. + * + * @param parentWidthSpec + * @param parentHeightSpec + */ + private void internalMeasureDimensions(int parentWidthSpec, int parentHeightSpec) { + int heightPadding = getPaddingTop() + getPaddingBottom(); + int widthPadding = getPaddingLeft() + getPaddingRight(); + + final int widgetsCount = getChildCount(); + for (int i = 0; i < widgetsCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + LayoutParams params = (LayoutParams) child.getLayoutParams(); + ConstraintWidget widget = params.widget; + if (params.isGuideline || params.isHelper) { + continue; + } + widget.setVisibility(child.getVisibility()); + + int width = params.width; + int height = params.height; + + if (width == MATCH_CONSTRAINT || height == MATCH_CONSTRAINT) { + widget.getResolutionWidth().invalidate(); + widget.getResolutionHeight().invalidate(); + continue; + } + + boolean didWrapMeasureWidth = false; + boolean didWrapMeasureHeight = false; + + final int childWidthMeasureSpec; + final int childHeightMeasureSpec; + if (width == WRAP_CONTENT) { + didWrapMeasureWidth = true; + } + childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec, + widthPadding, width); + if (height == WRAP_CONTENT) { + didWrapMeasureHeight = true; + } + childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec, + heightPadding, height); + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (mMetrics != null) { + mMetrics.measures++; + } + + widget.setWidthWrapContent(width == WRAP_CONTENT); + widget.setHeightWrapContent(height == WRAP_CONTENT); + width = child.getMeasuredWidth(); + height = child.getMeasuredHeight(); + + widget.setWidth(width); + widget.setHeight(height); + + if (didWrapMeasureWidth) { + widget.setWrapWidth(width); + } + if (didWrapMeasureHeight) { + widget.setWrapHeight(height); + } + + if (params.needsBaseline) { + int baseline = child.getBaseline(); + if (baseline != -1) { + widget.setBaselineDistance(baseline); + } + } + + if (params.horizontalDimensionFixed && params.verticalDimensionFixed) { + widget.getResolutionWidth().resolve(width); + widget.getResolutionHeight().resolve(height); + } + } + + // ok now let's try to analyse the graph, see if that solves the flexible dimensions + mLayoutWidget.solveGraph(); + + for (int i = 0; i < widgetsCount; i++) { + final View child = getChildAt(i); + if (child.getVisibility() == GONE) { + continue; + } + LayoutParams params = (LayoutParams) child.getLayoutParams(); + ConstraintWidget widget = params.widget; + if (params.isGuideline || params.isHelper) { + continue; + } + widget.setVisibility(child.getVisibility()); + + int width = params.width; + int height = params.height; + + if (!(width == MATCH_CONSTRAINT || height == MATCH_CONSTRAINT)) { + continue; + } + + ResolutionAnchor left = widget.getAnchor(ConstraintAnchor.Type.LEFT).getResolutionNode(); + ResolutionAnchor right = widget.getAnchor(ConstraintAnchor.Type.RIGHT).getResolutionNode(); + boolean bothHorizontal = widget.getAnchor(ConstraintAnchor.Type.LEFT).getTarget() != null + && widget.getAnchor(ConstraintAnchor.Type.RIGHT).getTarget() != null; + ResolutionAnchor top = widget.getAnchor(ConstraintAnchor.Type.TOP).getResolutionNode(); + ResolutionAnchor bottom = widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getResolutionNode(); + boolean bothVertical = widget.getAnchor(ConstraintAnchor.Type.TOP).getTarget() != null + && widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getTarget() != null; + + if (width == MATCH_CONSTRAINT && height == MATCH_CONSTRAINT && bothHorizontal && bothVertical) { + continue; + } + + boolean didWrapMeasureWidth = false; + boolean didWrapMeasureHeight = false; + boolean resolveWidth = mLayoutWidget.getHorizontalDimensionBehaviour() != ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; + boolean resolveHeight = mLayoutWidget.getVerticalDimensionBehaviour() != ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; + + final int childWidthMeasureSpec; + final int childHeightMeasureSpec; + + if (!resolveWidth) { + widget.getResolutionWidth().invalidate(); + } + if (!resolveHeight) { + widget.getResolutionHeight().invalidate(); + } + if (width == MATCH_CONSTRAINT) { + if (resolveWidth && widget.isSpreadWidth() && bothHorizontal && left.isResolved() && right.isResolved()) { + width = (int) (right.getResolvedValue() - left.getResolvedValue()); + widget.getResolutionWidth().resolve(width); + childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec, + widthPadding, width); + } else { + childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec, + widthPadding, LayoutParams.WRAP_CONTENT); + didWrapMeasureWidth = true; + resolveWidth = false; + } + } else if (width == MATCH_PARENT) { + childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec, + widthPadding, LayoutParams.MATCH_PARENT); + } else { + if (width == WRAP_CONTENT) { + didWrapMeasureWidth = true; + } + childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec, + widthPadding, width); + } + if (height == MATCH_CONSTRAINT) { + if (resolveHeight && widget.isSpreadHeight() && bothVertical && top.isResolved() && bottom.isResolved()) { + height = (int) (bottom.getResolvedValue() - top.getResolvedValue()); + widget.getResolutionHeight().resolve(height); + childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec, + heightPadding, height); + } else { + childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec, + heightPadding, LayoutParams.WRAP_CONTENT); + didWrapMeasureHeight = true; + resolveHeight = false; + } + } else if (height == MATCH_PARENT) { + childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec, + heightPadding, LayoutParams.MATCH_PARENT); + } else { + if (height == WRAP_CONTENT) { + didWrapMeasureHeight = true; + } + childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec, + heightPadding, height); + } + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (mMetrics != null) { + mMetrics.measures++; + } + + widget.setWidthWrapContent(width == WRAP_CONTENT); + widget.setHeightWrapContent(height == WRAP_CONTENT); + width = child.getMeasuredWidth(); + height = child.getMeasuredHeight(); + + widget.setWidth(width); + widget.setHeight(height); + + if (didWrapMeasureWidth) { + widget.setWrapWidth(width); + } + if (didWrapMeasureHeight) { + widget.setWrapHeight(height); + } + if (resolveWidth) { + widget.getResolutionWidth().resolve(width); + } else { + widget.getResolutionWidth().remove(); + } + if (resolveHeight) { + widget.getResolutionHeight().resolve(height); + } else { + widget.getResolutionHeight().remove(); + } + + if (params.needsBaseline) { + int baseline = child.getBaseline(); + if (baseline != -1) { + widget.setBaselineDistance(baseline); + } + } + } + } + + /** + * @hide * * Fills metrics object * * @param metrics */ public void fillMetrics(Metrics metrics) { + mMetrics = metrics; mLayoutWidget.fillMetrics(metrics); } @@ -1265,6 +1485,10 @@ public class ConstraintLayout extends ViewGroup { */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + long time = System.currentTimeMillis(); + int REMEASURES_A = 0; + int REMEASURES_B = 0; + if (DEBUG) { System.out.println("onMeasure width: " + MeasureSpec.toString(widthMeasureSpec) + " height: " + MeasureSpec.toString(heightMeasureSpec)); @@ -1315,7 +1539,17 @@ public class ConstraintLayout extends ViewGroup { mDirtyHierarchy = false; updateHierarchy(); } - internalMeasureChildren(widthMeasureSpec, heightMeasureSpec); + + final boolean optimiseDimensions = (mOptimizationLevel & Optimizer.OPTIMIZATION_DIMENSIONS) + == Optimizer.OPTIMIZATION_DIMENSIONS; + if (optimiseDimensions) { + mLayoutWidget.preOptimize(); + mLayoutWidget.optimizeForDimensions(startingWidth, startingHeight); + internalMeasureDimensions(widthMeasureSpec, heightMeasureSpec); + } else { + internalMeasureChildren(widthMeasureSpec, heightMeasureSpec); + } + updatePostMeasures(); //noinspection PointlessBooleanExpression if (ALLOWS_EMBEDDED && mLayoutWidget.getParent() != null) { @@ -1361,6 +1595,10 @@ public class ConstraintLayout extends ViewGroup { continue; } + if (optimiseDimensions && widget.getResolutionWidth().isResolved() + && widget.getResolutionHeight().isResolved()) { + continue; + } int widthSpec = 0; int heightSpec = 0; @@ -1377,12 +1615,20 @@ public class ConstraintLayout extends ViewGroup { // we need to re-measure the child... child.measure(widthSpec, heightSpec); + if (mMetrics != null) { + mMetrics.additionalMeasures++; + } + + REMEASURES_A++; int measuredWidth = child.getMeasuredWidth(); int measuredHeight = child.getMeasuredHeight(); if (measuredWidth != widget.getWidth()) { widget.setWidth(measuredWidth); + if (optimiseDimensions) { + widget.getResolutionWidth().resolve(measuredWidth); + } if (containerWrapWidth && widget.getRight() > minWidth) { int w = widget.getRight() + widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin(); @@ -1392,6 +1638,9 @@ public class ConstraintLayout extends ViewGroup { } if (measuredHeight != widget.getHeight()) { widget.setHeight(measuredHeight); + if (optimiseDimensions) { + widget.getResolutionHeight().resolve(measuredHeight); + } if (containerWrapHeight && widget.getBottom() > minHeight) { int h = widget.getBottom() + widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getMargin(); @@ -1414,6 +1663,9 @@ public class ConstraintLayout extends ViewGroup { if (needSolverPass) { mLayoutWidget.setWidth(startingWidth); mLayoutWidget.setHeight(startingHeight); + if (optimiseDimensions) { + mLayoutWidget.solveGraph(); + } solveLinearSystem("2nd pass"); needSolverPass = false; if (mLayoutWidget.getWidth() < minWidth) { @@ -1434,10 +1686,15 @@ public class ConstraintLayout extends ViewGroup { if (child == null) { continue; } - if (child.getWidth() != widget.getWidth() || child.getHeight() != widget.getHeight()) { + if (child.getMeasuredWidth() != widget.getWidth() || child.getMeasuredHeight() != widget.getHeight()) { int widthSpec = MeasureSpec.makeMeasureSpec(widget.getWidth(), MeasureSpec.EXACTLY); int heightSpec = MeasureSpec.makeMeasureSpec(widget.getHeight(), MeasureSpec.EXACTLY); child.measure(widthSpec, heightSpec); + if (mMetrics != null) { + mMetrics.additionalMeasures++; + } + + REMEASURES_B++; } } } @@ -1467,6 +1724,14 @@ public class ConstraintLayout extends ViewGroup { mLastMeasureWidth = androidLayoutWidth; mLastMeasureHeight = androidLayoutHeight; } + if (DEBUG) { + time = System.currentTimeMillis() - time; + System.out.println("" + this + " (" + getChildCount() + ") DONE onMeasure width: " + MeasureSpec.toString(widthMeasureSpec) + + " height: " + MeasureSpec.toString(heightMeasureSpec) + + "lasted " + time + + "remeasures (" + REMEASURES_A + "/" + REMEASURES_B + ") " + ); + } } private void setSelfDimensionBehaviour(int widthMeasureSpec, int heightMeasureSpec) { @@ -1533,6 +1798,9 @@ public class ConstraintLayout extends ViewGroup { System.out.println("solve <" + reason + ">"); } mLayoutWidget.layout(); + if (mMetrics != null) { + mMetrics.resolutions++; + } } /** diff --git a/constraintlayout/src/main/res/values/attrs.xml b/constraintlayout/src/main/res/values/attrs.xml index f278657..d9d25dc 100644 --- a/constraintlayout/src/main/res/values/attrs.xml +++ b/constraintlayout/src/main/res/values/attrs.xml @@ -145,6 +145,7 @@ <flag name="direct" value="1"/> <flag name="barrier" value="2"/> <flag name="chains" value="4"/> + <flag name="dimensions" value="8"/> </attr> <!-- Specify the style of match constraint --> diff --git a/solver/src/main/java/android/support/constraint/solver/Metrics.java b/solver/src/main/java/android/support/constraint/solver/Metrics.java index 002f54a..570ca44 100644 --- a/solver/src/main/java/android/support/constraint/solver/Metrics.java +++ b/solver/src/main/java/android/support/constraint/solver/Metrics.java @@ -23,6 +23,9 @@ import java.util.ArrayList; * Utility class to track metrics during the system resolution */ public class Metrics { + public long measures; + public long additionalMeasures; + public long resolutions; public long tableSizeIncrease; public long minimize; public long constraints; @@ -53,6 +56,9 @@ public class Metrics { public String toString() { return "\n*** Metrics ***\n" + + "measures: " + measures + "\n" + + "additionalMeasures: " + additionalMeasures + "\n" + + "resolutions passes: " + resolutions + "\n" + "table increases: " + tableSizeIncrease + "\n" + "maxTableSize: " + maxTableSize + "\n" + "maxVariables: " + maxVariables + "\n" @@ -82,6 +88,9 @@ public class Metrics { ; } public void reset() { + measures = 0; + additionalMeasures = 0; + resolutions = 0; tableSizeIncrease = 0; maxTableSize = 0; lastTableSize = 0; 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 6e42f14..f6a09c2 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,7 +20,6 @@ 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 @@ -33,7 +32,7 @@ public class Barrier extends Helper { public static final int BOTTOM = 3; private int mBarrierType = LEFT; - private ArrayList<ResolutionNode> mNodes = new ArrayList<>(4); + private ArrayList<ResolutionAnchor> mNodes = new ArrayList<>(4); private boolean mAllowsGoneWidget = true; @@ -56,9 +55,10 @@ public class Barrier extends Helper { /** * Graph analysis + * @param optimizationLevel */ @Override - public void analyze() { + public void analyze(int optimizationLevel) { if (mParent == null) { return; } @@ -66,7 +66,7 @@ public class Barrier extends Helper { return; } - ResolutionNode node; + ResolutionAnchor node; switch (mBarrierType) { case LEFT: node = mLeft.getResolutionNode(); @@ -83,7 +83,7 @@ public class Barrier extends Helper { default: return; } - node.setType(ResolutionNode.BARRIER_CONNECTION); + node.setType(ResolutionAnchor.BARRIER_CONNECTION); if (mBarrierType == LEFT || mBarrierType == RIGHT) { mTop.getResolutionNode().resolve(null, 0); @@ -99,7 +99,7 @@ public class Barrier extends Helper { if (!mAllowsGoneWidget && !widget.allowedInBarrier()) { continue; } - ResolutionNode depends = null; + ResolutionAnchor depends = null; switch (mBarrierType) { case LEFT: depends = widget.mLeft.getResolutionNode(); @@ -126,7 +126,7 @@ public class Barrier extends Helper { */ @Override public void resolve() { - ResolutionNode node = null; + ResolutionAnchor node = null; float value = 0; switch (mBarrierType) { case LEFT: { @@ -148,10 +148,10 @@ public class Barrier extends Helper { } final int count = mNodes.size(); - ResolutionNode resolvedTarget = null; + ResolutionAnchor resolvedTarget = null; for (int i = 0; i < count; i++) { - ResolutionNode n = mNodes.get(i); - if (n.state != ResolutionNode.RESOLVED) { + ResolutionAnchor n = mNodes.get(i); + if (n.state != ResolutionAnchor.RESOLVED) { return; } if (mBarrierType == LEFT || mBarrierType == TOP) { diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintAnchor.java b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintAnchor.java index 27f0f18..a8157bf 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintAnchor.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintAnchor.java @@ -16,7 +16,6 @@ package android.support.constraint.solver.widgets; import android.support.constraint.solver.Cache; -import android.support.constraint.solver.LinearSystem; import android.support.constraint.solver.SolverVariable; import java.util.ArrayList; @@ -49,14 +48,14 @@ public class ConstraintAnchor { /** * Resolution node, used by graph resolution */ - private ResolutionNode mResolutionNode = new ResolutionNode(this); + private ResolutionAnchor mResolutionAnchor = new ResolutionAnchor(this); /** * Resolution node accessor * @return the Resolution node for this ConstraintAnchor */ - public ResolutionNode getResolutionNode() { - return mResolutionNode; + public ResolutionAnchor getResolutionNode() { + return mResolutionAnchor; } /** @@ -179,7 +178,7 @@ public class ConstraintAnchor { mStrength = Strength.STRONG; mConnectionCreator = USER_CREATOR; mConnectionType = ConnectionType.RELAXED; - mResolutionNode.reset(); + mResolutionAnchor.reset(); } /** 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 12b1bee..dd6d92b 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 @@ -19,6 +19,7 @@ import android.support.constraint.solver.*; import java.util.ArrayList; +import static android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT; import static android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; /** @@ -68,6 +69,9 @@ public class ConstraintWidget { private static final int WRAP = -2; + ResolutionDimension mResolutionWidth; + ResolutionDimension mResolutionHeight; + int mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD; int mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD; int mMatchConstraintMinWidth = 0; @@ -101,6 +105,22 @@ public class ConstraintWidget { mMaxDimension[VERTICAL] = maxWidth; } + public boolean isSpreadWidth() { + return mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD + && mDimensionRatio == 0 + && mMatchConstraintMinWidth == 0 + && mMatchConstraintMaxWidth == 0 + && mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT; + } + + public boolean isSpreadHeight() { + return mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD + && mDimensionRatio == 0 + && mMatchConstraintMinHeight == 0 + && mMatchConstraintMaxHeight == 0 + && mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT; + } + /** * Define how the content of a widget should align, if the widget has children */ @@ -278,6 +298,12 @@ public class ConstraintWidget { mMatchConstraintMinHeight = 0; mResolvedDimensionRatioSide = UNKNOWN; mResolvedDimensionRatio = 1f; + if (mResolutionWidth != null) { + mResolutionWidth.reset(); + } + if (mResolutionHeight != null) { + mResolutionHeight.reset(); + } } /*-----------------------------------------------------------------------*/ @@ -304,16 +330,17 @@ public class ConstraintWidget { /** * Graph analysis + * @param optimizationLevel the current optimisation level */ - public void analyze() { - Optimizer.analyze(this); + public void analyze(int optimizationLevel) { + Optimizer.analyze(optimizationLevel,this); } /** * Try resolving the graph analysis */ public void resolve() { - // basic constraints resolution is done in ResolutionNode + // basic constraints resolution is done in ResolutionAnchor } /** @@ -322,15 +349,37 @@ public class ConstraintWidget { * @return true if the widget is fully resolved */ public boolean isFullyResolved() { - if (mLeft.getResolutionNode().state == ResolutionNode.RESOLVED - && mRight.getResolutionNode().state == ResolutionNode.RESOLVED - && mTop.getResolutionNode().state == ResolutionNode.RESOLVED - && mBottom.getResolutionNode().state == ResolutionNode.RESOLVED) { + if (mLeft.getResolutionNode().state == ResolutionAnchor.RESOLVED + && mRight.getResolutionNode().state == ResolutionAnchor.RESOLVED + && mTop.getResolutionNode().state == ResolutionAnchor.RESOLVED + && mBottom.getResolutionNode().state == ResolutionAnchor.RESOLVED) { return true; } return false; } + /** + * Return a ResolutionDimension for the width + * @return + */ + public ResolutionDimension getResolutionWidth() { + if (mResolutionWidth == null) { + mResolutionWidth = new ResolutionDimension(); + } + return mResolutionWidth; + } + + /** + * Return a ResolutionDimension for the height + * @return + */ + public ResolutionDimension getResolutionHeight() { + if (mResolutionHeight == null) { + mResolutionHeight = new ResolutionDimension(); + } + return mResolutionHeight; + } + /*-----------------------------------------------------------------------*/ // Creation /*-----------------------------------------------------------------------*/ @@ -2244,7 +2293,7 @@ public class ConstraintWidget { || mResolvedDimensionRatioSide == UNKNOWN); if (mBaselineDistance > 0) { - if (mBaseline.getResolutionNode().state == ResolutionNode.RESOLVED) { + if (mBaseline.getResolutionNode().state == ResolutionAnchor.RESOLVED) { mBaseline.getResolutionNode().addResolvedValue(system); } else { system.addEquality(baseline, top, getBaselineDistance(), SolverVariable.STRENGTH_FIXED); @@ -2382,8 +2431,8 @@ public class ConstraintWidget { SolverVariable endTarget = system.createObjectVariable(endAnchor.getTarget()); if (system.graphOptimizer) { - if (beginAnchor.getResolutionNode().state == ResolutionNode.RESOLVED - && endAnchor.getResolutionNode().state == ResolutionNode.RESOLVED) { + if (beginAnchor.getResolutionNode().state == ResolutionAnchor.RESOLVED + && endAnchor.getResolutionNode().state == ResolutionAnchor.RESOLVED) { if (system.getMetrics() != null) { system.getMetrics().resolvedWidgets++; } 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 2d962f6..15a2ce2 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 @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import static android.support.constraint.solver.LinearSystem.FULL_DEBUG; +import static android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT; /** * A container of ConstraintWidget that can layout its children @@ -173,17 +174,17 @@ public class ConstraintWidgetContainer extends WidgetContainer { if (widget instanceof ConstraintWidgetContainer) { DimensionBehaviour horizontalBehaviour = widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL]; DimensionBehaviour verticalBehaviour = widget.mListDimensionBehaviors[DIMENSION_VERTICAL]; - if (horizontalBehaviour == DimensionBehaviour.WRAP_CONTENT) { + if (horizontalBehaviour == WRAP_CONTENT) { widget.setHorizontalDimensionBehaviour(DimensionBehaviour.FIXED); } - if (verticalBehaviour == DimensionBehaviour.WRAP_CONTENT) { + if (verticalBehaviour == WRAP_CONTENT) { widget.setVerticalDimensionBehaviour(DimensionBehaviour.FIXED); } widget.addToSolver(system); - if (horizontalBehaviour == DimensionBehaviour.WRAP_CONTENT) { + if (horizontalBehaviour == WRAP_CONTENT) { widget.setHorizontalDimensionBehaviour(horizontalBehaviour); } - if (verticalBehaviour == DimensionBehaviour.WRAP_CONTENT) { + if (verticalBehaviour == WRAP_CONTENT) { widget.setVerticalDimensionBehaviour(verticalBehaviour); } } else { @@ -261,13 +262,14 @@ public class ConstraintWidgetContainer extends WidgetContainer { /** * Graph analysis + * @param optimizationLevel the current optimisation level */ @Override - public void analyze() { - super.analyze(); + public void analyze(int optimizationLevel) { + super.analyze(optimizationLevel); final int count = mChildren.size(); for (int i = 0; i < count; i++) { - mChildren.get(i).analyze(); + mChildren.get(i).analyze(optimizationLevel); } } @@ -308,49 +310,10 @@ public class ConstraintWidgetContainer extends WidgetContainer { } if (mOptimizationLevel != Optimizer.OPTIMIZATION_NONE) { - if (DEBUG_GRAPH) { - System.out.println("### Graph resolution... " + mWidth + " x " + mHeight + " ###"); - } - final int count = mChildren.size(); - resetResolutionNodes(); - for (int i = 0; i < count; i++) { - mChildren.get(i).resetResolutionNodes(); - } - if (DEBUG_GRAPH) { - System.out.println("### Update Constraints Graph ###"); - setDebugName("Root"); - } - - analyze(); - - if (DEBUG_GRAPH) { - for (int i = 0; i < mChildren.size(); i++) { - ConstraintWidget widget = mChildren.get(i); - System.out.println("(pre) child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode() - + ", " + widget.mTop.getResolutionNode() - + ", " + widget.mRight.getResolutionNode() - + ", " + widget.mBottom.getResolutionNode()); - } - } - ResolutionNode leftNode = getAnchor(ConstraintAnchor.Type.LEFT).getResolutionNode(); - ResolutionNode topNode = getAnchor(ConstraintAnchor.Type.TOP).getResolutionNode(); - - if (DEBUG_GRAPH) { - System.out.println("### RESOLUTION ###"); - } - - leftNode.resolve(null, 0); - topNode.resolve(null, 0); - - if (DEBUG_GRAPH) { - for (int i = 0; i < mChildren.size(); i++) { - ConstraintWidget widget = mChildren.get(i); - System.out.println("child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode() - + ", " + widget.mTop.getResolutionNode() - + ", " + widget.mRight.getResolutionNode() - + ", " + widget.mBottom.getResolutionNode()); - } + if (!optimizeFor(Optimizer.OPTIMIZATION_DIMENSIONS)) { + optimizeReset(); } + optimize(); mSystem.graphOptimizer = true; } else { mSystem.graphOptimizer = false; @@ -433,24 +396,24 @@ public class ConstraintWidgetContainer extends WidgetContainer { } maxX = Math.max(mMinWidth, maxX); maxY = Math.max(mMinHeight, maxY); - if (originalHorizontalDimensionBehaviour == DimensionBehaviour.WRAP_CONTENT) { + 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] = DimensionBehaviour.WRAP_CONTENT; // force using the solver + mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT; // force using the solver wrap_override = true; needsSolving = true; } } - if (originalVerticalDimensionBehaviour == DimensionBehaviour.WRAP_CONTENT) { + 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] = DimensionBehaviour.WRAP_CONTENT; // force using the solver + mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT; // force using the solver wrap_override = true; needsSolving = true; } @@ -480,7 +443,7 @@ public class ConstraintWidgetContainer extends WidgetContainer { } if (!wrap_override) { - if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.WRAP_CONTENT && prew > 0) { + 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); @@ -492,7 +455,7 @@ public class ConstraintWidgetContainer extends WidgetContainer { needsSolving = true; } } - if (mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.WRAP_CONTENT && preh > 0) { + 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); @@ -542,6 +505,89 @@ public class ConstraintWidgetContainer extends WidgetContainer { } } + public void preOptimize() { + optimizeReset(); + analyze(mOptimizationLevel); + } + + public void solveGraph() { + ResolutionAnchor leftNode = getAnchor(ConstraintAnchor.Type.LEFT).getResolutionNode(); + ResolutionAnchor topNode = getAnchor(ConstraintAnchor.Type.TOP).getResolutionNode(); + + if (DEBUG_GRAPH) { + System.out.println("### RESOLUTION ###"); + } + + leftNode.resolve(null, 0); + topNode.resolve(null, 0); + } + + public void resetGraph() { + ResolutionAnchor leftNode = getAnchor(ConstraintAnchor.Type.LEFT).getResolutionNode(); + ResolutionAnchor topNode = getAnchor(ConstraintAnchor.Type.TOP).getResolutionNode(); + + if (DEBUG_GRAPH) { + System.out.println("### RESET ###"); + } + + leftNode.invalidateAnchors(); + topNode.invalidateAnchors(); + leftNode.resolve(null, 0); + topNode.resolve(null, 0); + } + + public void optimizeForDimensions(int width, int height) { + if (mListDimensionBehaviors[HORIZONTAL] != WRAP_CONTENT && mResolutionWidth != null) { + mResolutionWidth.resolve(width); + } + if (mListDimensionBehaviors[VERTICAL] != WRAP_CONTENT && mResolutionHeight != null) { + mResolutionHeight.resolve(height); + } + } + + public void optimizeReset() { + final int count = mChildren.size(); + resetResolutionNodes(); + for (int i = 0; i < count; i++) { + mChildren.get(i).resetResolutionNodes(); + } + } + + public void optimize() { + if (DEBUG_GRAPH) { + System.out.println("### Graph resolution... " + mWidth + " x " + mHeight + " ###"); + } + + if (DEBUG_GRAPH) { + System.out.println("### Update Constraints Graph ###"); + setDebugName("Root"); + } + + if (!optimizeFor(Optimizer.OPTIMIZATION_DIMENSIONS)) { + analyze(mOptimizationLevel); + } + + if (DEBUG_GRAPH) { + for (int i = 0; i < mChildren.size(); i++) { + ConstraintWidget widget = mChildren.get(i); + System.out.println("(pre) child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode() + + ", " + widget.mTop.getResolutionNode() + + ", " + widget.mRight.getResolutionNode() + + ", " + widget.mBottom.getResolutionNode()); + } + } + solveGraph(); + if (DEBUG_GRAPH) { + for (int i = 0; i < mChildren.size(); i++) { + ConstraintWidget widget = mChildren.get(i); + System.out.println("child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode() + + ", " + widget.mTop.getResolutionNode() + + ", " + widget.mRight.getResolutionNode() + + ", " + widget.mBottom.getResolutionNode()); + } + } + } + /** * Indicates if the container knows how to layout its content on its own * diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Guideline.java b/solver/src/main/java/android/support/constraint/solver/widgets/Guideline.java index d51c299..66354e4 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/Guideline.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/Guideline.java @@ -206,40 +206,41 @@ public class Guideline extends ConstraintWidget { /** * Graph analysis + * @param optimizationLevel */ @Override - public void analyze() { + public void analyze(int optimizationLevel) { ConstraintWidget constraintWidgetContainer = getParent(); if (constraintWidgetContainer == null) { return; } if (getOrientation() == Guideline.VERTICAL) { - mTop.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION,constraintWidgetContainer.mTop.getResolutionNode(), 0); - mBottom.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), 0); + mTop.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION,constraintWidgetContainer.mTop.getResolutionNode(), 0); + mBottom.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), 0); if (mRelativeBegin != -1) { - mLeft.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), mRelativeBegin); - mRight.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), mRelativeBegin); + mLeft.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), mRelativeBegin); + mRight.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), mRelativeBegin); } else if (mRelativeEnd != -1) { - mLeft.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mRight.getResolutionNode(), -mRelativeEnd); - mRight.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mRight.getResolutionNode(), -mRelativeEnd); + mLeft.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mRight.getResolutionNode(), -mRelativeEnd); + mRight.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mRight.getResolutionNode(), -mRelativeEnd); } else if (mRelativePercent != -1 && constraintWidgetContainer.getHorizontalDimensionBehaviour() == FIXED) { int position = (int) (constraintWidgetContainer.mWidth * mRelativePercent); - mLeft.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), position); - mRight.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), position); + mLeft.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), position); + mRight.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), position); } } else { - mLeft.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), 0); - mRight.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), 0); + mLeft.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), 0); + mRight.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mLeft.getResolutionNode(), 0); if (mRelativeBegin != -1) { - mTop.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), mRelativeBegin); - mBottom.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), mRelativeBegin); + mTop.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), mRelativeBegin); + mBottom.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), mRelativeBegin); } else if (mRelativeEnd != -1) { - mTop.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mBottom.getResolutionNode(), -mRelativeEnd); - mBottom.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mBottom.getResolutionNode(), -mRelativeEnd); + mTop.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mBottom.getResolutionNode(), -mRelativeEnd); + mBottom.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mBottom.getResolutionNode(), -mRelativeEnd); } else if (mRelativePercent != -1 && constraintWidgetContainer.getVerticalDimensionBehaviour() == FIXED) { int position = (int) (constraintWidgetContainer.mHeight * mRelativePercent); - mTop.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), position); - mBottom.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), position); + mTop.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), position); + mBottom.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, constraintWidgetContainer.mTop.getResolutionNode(), position); } } } 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 b2af858..c80ca07 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 @@ -31,8 +31,13 @@ public class Optimizer { public static final int OPTIMIZATION_DIRECT = 1; public static final int OPTIMIZATION_BARRIER = 1 << 1; public static final int OPTIMIZATION_CHAIN = 1 << 2; - public static final int OPTIMIZATION_RATIO = 1 << 3; - public static final int OPTIMIZATION_STANDARD = OPTIMIZATION_DIRECT | OPTIMIZATION_BARRIER /* | OPTIMIZATION_CHAIN */; + public static final int OPTIMIZATION_DIMENSIONS = 1 << 3; + public static final int OPTIMIZATION_RATIO = 1 << 4; + public static final int OPTIMIZATION_STANDARD = OPTIMIZATION_DIRECT + | OPTIMIZATION_BARRIER + /* | OPTIMIZATION_CHAIN */ + /* | OPTIMIZATION_DIMENSIONS */ + ; // Internal use. static boolean[] flags = new boolean[3]; @@ -136,7 +141,7 @@ public class Optimizer { * * @param widget */ - static void analyze(ConstraintWidget widget) { + static void analyze(int optimisationLevel, ConstraintWidget widget) { // Let's update the graph from the nodes! // This will only apply if the nodes are not part of a chain. @@ -145,57 +150,98 @@ public class Optimizer { widget.updateResolutionNodes(); - ResolutionNode leftNode = widget.mLeft.getResolutionNode(); - ResolutionNode topNode = widget.mTop.getResolutionNode(); - ResolutionNode rightNode = widget.mRight.getResolutionNode(); - ResolutionNode bottomNode = widget.mBottom.getResolutionNode(); + ResolutionAnchor leftNode = widget.mLeft.getResolutionNode(); + ResolutionAnchor topNode = widget.mTop.getResolutionNode(); + ResolutionAnchor rightNode = widget.mRight.getResolutionNode(); + ResolutionAnchor bottomNode = widget.mBottom.getResolutionNode(); + + boolean optimiseDimensions = (optimisationLevel & OPTIMIZATION_DIMENSIONS) == OPTIMIZATION_DIMENSIONS; // First the horizontal nodes... - if (leftNode.type != ResolutionNode.CHAIN_CONNECTION - && rightNode.type != ResolutionNode.CHAIN_CONNECTION) { + if (leftNode.type != ResolutionAnchor.CHAIN_CONNECTION + && rightNode.type != ResolutionAnchor.CHAIN_CONNECTION) { if (widget.mListDimensionBehaviors[HORIZONTAL] == FIXED) { if (widget.mLeft.mTarget == null && widget.mRight.mTarget == null) { - leftNode.setType(ResolutionNode.DIRECT_CONNECTION); - rightNode.setType(ResolutionNode.DIRECT_CONNECTION); - rightNode.dependsOn(leftNode, widget.getWidth()); + leftNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + rightNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + if (optimiseDimensions) { + rightNode.dependsOn(leftNode, 1, widget.getResolutionWidth()); + } else { + rightNode.dependsOn(leftNode, widget.getWidth()); + } } else if (widget.mLeft.mTarget != null && widget.mRight.mTarget == null) { - leftNode.setType(ResolutionNode.DIRECT_CONNECTION); - rightNode.setType(ResolutionNode.DIRECT_CONNECTION); - rightNode.dependsOn(leftNode, widget.getWidth()); + leftNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + rightNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + if (optimiseDimensions) { + rightNode.dependsOn(leftNode, 1, widget.getResolutionWidth()); + } else { + rightNode.dependsOn(leftNode, widget.getWidth()); + } } else if (widget.mLeft.mTarget == null && widget.mRight.mTarget != null) { - leftNode.setType(ResolutionNode.DIRECT_CONNECTION); - rightNode.setType(ResolutionNode.DIRECT_CONNECTION); + leftNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + rightNode.setType(ResolutionAnchor.DIRECT_CONNECTION); leftNode.dependsOn(rightNode, -widget.getWidth()); + if (optimiseDimensions) { + leftNode.dependsOn(rightNode, -1, widget.getResolutionWidth()); + } else { + leftNode.dependsOn(rightNode, -widget.getWidth()); + } } else if (widget.mLeft.mTarget != null && widget.mRight.mTarget != null) { - leftNode.setType(ResolutionNode.CENTER_CONNECTION); - rightNode.setType(ResolutionNode.CENTER_CONNECTION); - leftNode.setOpposite(rightNode, -widget.getWidth()); - rightNode.setOpposite(leftNode, widget.getWidth()); + leftNode.setType(ResolutionAnchor.CENTER_CONNECTION); + rightNode.setType(ResolutionAnchor.CENTER_CONNECTION); + if (optimiseDimensions) { + widget.getResolutionWidth().addDependent(leftNode); + widget.getResolutionWidth().addDependent(rightNode); + leftNode.setOpposite(rightNode, -1, widget.getResolutionWidth()); + rightNode.setOpposite(leftNode, 1, widget.getResolutionWidth()); + } else { + leftNode.setOpposite(rightNode, -widget.getWidth()); + rightNode.setOpposite(leftNode, widget.getWidth()); + } } } else if (widget.mListDimensionBehaviors[HORIZONTAL] == MATCH_CONSTRAINT && optimizableMatchConstraint(widget, HORIZONTAL)) { int width = widget.getWidth(); - if (widget.mDimensionRatio != 0) { - width = (int) (widget.getHeight() * widget.mDimensionRatio); - } - leftNode.setType(ResolutionNode.DIRECT_CONNECTION); - rightNode.setType(ResolutionNode.DIRECT_CONNECTION); + // TODO: ratio won't work with optimiseDimensions as it is + // ...but ratio won't work period for now as optimizableMatchConstraint will return false + // if (widget.mDimensionRatio != 0) { + // width = (int) (widget.getHeight() * widget.mDimensionRatio); + // } + leftNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + rightNode.setType(ResolutionAnchor.DIRECT_CONNECTION); if (widget.mLeft.mTarget == null && widget.mRight.mTarget == null) { - rightNode.dependsOn(leftNode, width); + if (optimiseDimensions) { + rightNode.dependsOn(leftNode, 1, widget.getResolutionWidth()); + } else { + rightNode.dependsOn(leftNode, width); + } } else if (widget.mLeft.mTarget != null && widget.mRight.mTarget == null) { - rightNode.dependsOn(leftNode, width); + if (optimiseDimensions) { + rightNode.dependsOn(leftNode, 1, widget.getResolutionWidth()); + } else { + rightNode.dependsOn(leftNode, width); + } } else if (widget.mLeft.mTarget == null && widget.mRight.mTarget != null) { - leftNode.dependsOn(rightNode, -width); + if (optimiseDimensions) { + leftNode.dependsOn(rightNode, -1, widget.getResolutionWidth()); + } else { + leftNode.dependsOn(rightNode, -width); + } } else if (widget.mLeft.mTarget != null && widget.mRight.mTarget != null) { + if (optimiseDimensions) { + widget.getResolutionWidth().addDependent(leftNode); + widget.getResolutionWidth().addDependent(rightNode); + } if (widget.mDimensionRatio == 0) { - leftNode.setType(ResolutionNode.MATCH_CONNECTION); - rightNode.setType(ResolutionNode.MATCH_CONNECTION); + leftNode.setType(ResolutionAnchor.MATCH_CONNECTION); + rightNode.setType(ResolutionAnchor.MATCH_CONNECTION); leftNode.setOpposite(rightNode, 0); rightNode.setOpposite(leftNode, 0); } else { - leftNode.setType(ResolutionNode.CENTER_CONNECTION); - rightNode.setType(ResolutionNode.CENTER_CONNECTION); + // TODO -- fix ratio. For now this won't work. + leftNode.setType(ResolutionAnchor.CENTER_CONNECTION); + rightNode.setType(ResolutionAnchor.CENTER_CONNECTION); leftNode.setOpposite(rightNode, -width); rightNode.setOpposite(leftNode, width); widget.setWidth(width); @@ -206,70 +252,106 @@ public class Optimizer { // ...then the vertical ones - if (topNode.type != ResolutionNode.CHAIN_CONNECTION - && bottomNode.type != ResolutionNode.CHAIN_CONNECTION - /* && mBaseline.getResolutionNode().type == ResolutionNode.UNCONNECTED */) { + if (topNode.type != ResolutionAnchor.CHAIN_CONNECTION + && bottomNode.type != ResolutionAnchor.CHAIN_CONNECTION + /* && mBaseline.getResolutionNode().type == ResolutionAnchor.UNCONNECTED */) { if (widget.mListDimensionBehaviors[VERTICAL] == FIXED) { if (widget.mTop.mTarget == null && widget.mBottom.mTarget == null) { - topNode.setType(ResolutionNode.DIRECT_CONNECTION); - bottomNode.setType(ResolutionNode.DIRECT_CONNECTION); - bottomNode.dependsOn(topNode, widget.getHeight()); + topNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + bottomNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + if (optimiseDimensions) { + bottomNode.dependsOn(topNode, 1, widget.getResolutionHeight()); + } else { + bottomNode.dependsOn(topNode, widget.getHeight()); + } if (widget.mBaseline.mTarget != null) { - widget.mBaseline.getResolutionNode().setType(ResolutionNode.DIRECT_CONNECTION); - topNode.dependsOn(ResolutionNode.DIRECT_CONNECTION, + widget.mBaseline.getResolutionNode().setType(ResolutionAnchor.DIRECT_CONNECTION); + topNode.dependsOn(ResolutionAnchor.DIRECT_CONNECTION, widget.mBaseline.getResolutionNode(), -widget.mBaselineDistance); } } else if (widget.mTop.mTarget != null && widget.mBottom.mTarget == null) { - topNode.setType(ResolutionNode.DIRECT_CONNECTION); - bottomNode.setType(ResolutionNode.DIRECT_CONNECTION); - bottomNode.dependsOn(topNode, widget.getHeight()); + topNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + bottomNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + if (optimiseDimensions) { + bottomNode.dependsOn(topNode, 1, widget.getResolutionHeight()); + } else { + bottomNode.dependsOn(topNode, widget.getHeight()); + } if (widget.mBaselineDistance > 0) { - widget.mBaseline.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); + widget.mBaseline.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); } } else if (widget.mTop.mTarget == null && widget.mBottom.mTarget != null) { - topNode.setType(ResolutionNode.DIRECT_CONNECTION); - bottomNode.setType(ResolutionNode.DIRECT_CONNECTION); - topNode.dependsOn(bottomNode, -widget.getHeight()); + topNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + bottomNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + if (optimiseDimensions) { + topNode.dependsOn(bottomNode, -1, widget.getResolutionHeight()); + } else { + topNode.dependsOn(bottomNode, -widget.getHeight()); + } if (widget.mBaselineDistance > 0) { - widget.mBaseline.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); + widget.mBaseline.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); } } else if (widget.mTop.mTarget != null && widget.mBottom.mTarget != null) { - topNode.setType(ResolutionNode.CENTER_CONNECTION); - bottomNode.setType(ResolutionNode.CENTER_CONNECTION); - topNode.setOpposite(bottomNode, -widget.getHeight()); - bottomNode.setOpposite(topNode, widget.getHeight()); + topNode.setType(ResolutionAnchor.CENTER_CONNECTION); + bottomNode.setType(ResolutionAnchor.CENTER_CONNECTION); + if (optimiseDimensions) { + topNode.setOpposite(bottomNode, -1, widget.getResolutionHeight()); + bottomNode.setOpposite(topNode, 1, widget.getResolutionHeight()); + widget.getResolutionHeight().addDependent(topNode); + widget.getResolutionWidth().addDependent(bottomNode); + } else { + topNode.setOpposite(bottomNode, -widget.getHeight()); + bottomNode.setOpposite(topNode, widget.getHeight()); + } if (widget.mBaselineDistance > 0) { - widget.mBaseline.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); + widget.mBaseline.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); } } } else if (widget.mListDimensionBehaviors[VERTICAL] == MATCH_CONSTRAINT && optimizableMatchConstraint(widget, VERTICAL)) { int height = widget.getHeight(); - if (widget.mDimensionRatio != 0) { - height = (int) (widget.getWidth() * widget.mDimensionRatio); - } - topNode.setType(ResolutionNode.DIRECT_CONNECTION); - bottomNode.setType(ResolutionNode.DIRECT_CONNECTION); + // TODO: fix ratio (right it won't work, optimizableMatchConstraint will return false + // if (widget.mDimensionRatio != 0) { + // height = (int) (widget.getWidth() * widget.mDimensionRatio); + // } + topNode.setType(ResolutionAnchor.DIRECT_CONNECTION); + bottomNode.setType(ResolutionAnchor.DIRECT_CONNECTION); if (widget.mTop.mTarget == null && widget.mBottom.mTarget == null) { - bottomNode.dependsOn(topNode, height); + if (optimiseDimensions) { + bottomNode.dependsOn(topNode, 1, widget.getResolutionHeight()); + } else { + bottomNode.dependsOn(topNode, height); + } } else if (widget.mTop.mTarget != null && widget.mBottom.mTarget == null) { - bottomNode.dependsOn(topNode, height); + if (optimiseDimensions) { + bottomNode.dependsOn(topNode, 1, widget.getResolutionHeight()); + } else { + bottomNode.dependsOn(topNode, height); + } } else if (widget.mTop.mTarget == null && widget.mBottom.mTarget != null) { - topNode.dependsOn(bottomNode, -height); + if (optimiseDimensions) { + topNode.dependsOn(bottomNode, -1, widget.getResolutionHeight()); + } else { + topNode.dependsOn(bottomNode, -height); + } } else if (widget.mTop.mTarget != null && widget.mBottom.mTarget != null) { + if (optimiseDimensions) { + widget.getResolutionHeight().addDependent(topNode); + widget.getResolutionWidth().addDependent(bottomNode); + } if (widget.mDimensionRatio == 0) { - topNode.setType(ResolutionNode.MATCH_CONNECTION); - bottomNode.setType(ResolutionNode.MATCH_CONNECTION); + topNode.setType(ResolutionAnchor.MATCH_CONNECTION); + bottomNode.setType(ResolutionAnchor.MATCH_CONNECTION); topNode.setOpposite(bottomNode, 0); bottomNode.setOpposite(topNode, 0); } else { - topNode.setType(ResolutionNode.CENTER_CONNECTION); - bottomNode.setType(ResolutionNode.CENTER_CONNECTION); + topNode.setType(ResolutionAnchor.CENTER_CONNECTION); + bottomNode.setType(ResolutionAnchor.CENTER_CONNECTION); topNode.setOpposite(bottomNode, -height); bottomNode.setOpposite(topNode, height); widget.setHeight(height); if (widget.mBaselineDistance > 0) { - widget.mBaseline.getResolutionNode().dependsOn(ResolutionNode.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); + widget.mBaseline.getResolutionNode().dependsOn(ResolutionAnchor.DIRECT_CONNECTION, topNode, widget.mBaselineDistance); } } } @@ -428,8 +510,8 @@ public class Optimizer { } ConstraintWidget last = widget; - ResolutionNode firstNode = first.mListAnchors[offset].getResolutionNode(); - ResolutionNode lastNode = last.mListAnchors[offset + 1].getResolutionNode(); + ResolutionAnchor firstNode = first.mListAnchors[offset].getResolutionNode(); + ResolutionAnchor lastNode = last.mListAnchors[offset + 1].getResolutionNode(); if (firstNode.target == null || lastNode.target == null) { // dangling chain, let's bail for now @@ -437,8 +519,8 @@ public class Optimizer { } // let's look at the endpoints - if (firstNode.target.state != ResolutionNode.RESOLVED - && lastNode.target.state != ResolutionNode.RESOLVED) { + if (firstNode.target.state != ResolutionAnchor.RESOLVED + && lastNode.target.state != ResolutionAnchor.RESOLVED) { // No resolved endpoints, let's exit return false; } 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 new file mode 100644 index 0000000..32e2b6e --- /dev/null +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionAnchor.java @@ -0,0 +1,314 @@ +/* + * 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.LinearSystem; +import android.support.constraint.solver.SolverVariable; + +/** + * Implements a mechanism to resolve nodes via a dependency graph + */ +public class ResolutionAnchor extends ResolutionNode { + ConstraintAnchor myAnchor; + float computedValue; + ResolutionAnchor target; + float offset; + + ResolutionAnchor resolvedTarget; + float resolvedOffset; + + int type = UNCONNECTED; + + public static final int UNCONNECTED = 0; + public static final int DIRECT_CONNECTION = 1; + public static final int CENTER_CONNECTION = 2; + public static final int MATCH_CONNECTION = 3; + public static final int CHAIN_CONNECTION = 4; + public static final int BARRIER_CONNECTION = 5; + + private ResolutionAnchor opposite; + private float oppositeOffset; + + private ResolutionDimension dimension = null; + private int dimensionMultiplier = 1; + private ResolutionDimension oppositeDimension = null; + private int oppositeDimensionMultiplier = 1; + + public ResolutionAnchor(ConstraintAnchor anchor) { + myAnchor = anchor; + } + + public void remove(ResolutionDimension resolutionDimension) { + if (dimension == resolutionDimension) { + dimension = null; + offset = dimensionMultiplier; + } else if (dimension == oppositeDimension) { + oppositeDimension = null; + oppositeOffset = oppositeDimensionMultiplier; + } + resolve(); + } + + @Override + public String toString() { + if (state == RESOLVED) { + if (resolvedTarget == this) { + return "[" + myAnchor + ", RESOLVED: " + resolvedOffset + "] " + " type: " + sType(type); + } + return "[" + myAnchor + ", RESOLVED: " + resolvedTarget + ":" + resolvedOffset + "]" + + " type: " + sType(type); + } + return "{ " + myAnchor + " UNRESOLVED} type: " + sType(type); + } + + public void resolve(ResolutionAnchor target, float offset) { + if (state == UNRESOLVED || (resolvedTarget != target && resolvedOffset != offset)) { + resolvedTarget = target; + resolvedOffset = offset; + if (state == RESOLVED) { + invalidate(); + } + didResolve(); + } + } + + String sType(int type) { + if (type == 1) { + return "DIRECT"; + } else if (type == 2) { + return "CENTER"; + } else if (type == 3) { + return "MATCH"; + } else if (type == 4) { + return "CHAIN"; + } else if (type == 5) { + return "BARRIER"; + } + return "UNCONNECTED"; + } + + @Override + public void resolve() { + if (ConstraintWidgetContainer.DEBUG_GRAPH) { + System.out.println("resolve " + this + " type: " + sType(type) + + " current target: " + target + " with offset " + offset); + } + if (state == RESOLVED) { + return; + } + if (type == CHAIN_CONNECTION) { + return; + } + if (dimension != null) { + if (dimension.state != RESOLVED) { + return; + } + offset = dimensionMultiplier * dimension.value; + } + if (oppositeDimension != null) { + if (oppositeDimension.state != RESOLVED) { + return; + } + oppositeOffset = oppositeDimensionMultiplier * oppositeDimension.value; + } + if (type == DIRECT_CONNECTION + && ((target == null) || (target.state == RESOLVED))) { + + // Let's solve direct connections... + + if (target == null) { + resolvedTarget = this; + resolvedOffset = offset; + } else { + resolvedTarget = target.resolvedTarget; + resolvedOffset = target.resolvedOffset + offset; + } + didResolve(); + } else if (type == CENTER_CONNECTION + && target != null + && target.state == RESOLVED + && opposite != null && opposite.target != null + && opposite.target.state == RESOLVED) { + + // Let's solve center connections... + + if (LinearSystem.getMetrics() != null) { + LinearSystem.getMetrics().centerConnectionResolved++; + } + resolvedTarget = target.resolvedTarget; + opposite.resolvedTarget = opposite.target.resolvedTarget; + + float distance = 0; + float percent = 0.5f; + + if (oppositeOffset > 0) { + // we are right or bottom + distance = target.resolvedOffset - opposite.target.resolvedOffset; + } else { + distance = opposite.target.resolvedOffset - target.resolvedOffset; + } + + if (myAnchor.mType == ConstraintAnchor.Type.LEFT + || myAnchor.mType == ConstraintAnchor.Type.RIGHT) { + distance -= myAnchor.mOwner.getWidth(); + percent = myAnchor.mOwner.mHorizontalBiasPercent; + } else { + distance -= myAnchor.mOwner.getHeight(); + percent = myAnchor.mOwner.mVerticalBiasPercent; + } + int margin = myAnchor.getMargin(); + int oppositeMargin = opposite.myAnchor.getMargin(); + if (myAnchor.getTarget() == opposite.myAnchor.getTarget()) { + percent = 0.5f; + margin = 0; + oppositeMargin = 0; + } + + distance -= margin; + distance -= oppositeMargin; + + if (oppositeOffset > 0) { + // we are right or bottom + opposite.resolvedOffset = opposite.target.resolvedOffset + + oppositeMargin + distance * percent; + resolvedOffset = target.resolvedOffset - margin - (distance * (1 - percent)); + } else { + resolvedOffset = target.resolvedOffset + margin + distance * percent; + opposite.resolvedOffset = opposite.target.resolvedOffset + - oppositeMargin - (distance * (1 - percent)); + } + + didResolve(); + opposite.didResolve(); + } else if (type == MATCH_CONNECTION + && target != null + && target.state == RESOLVED + && opposite != null && opposite.target != null + && opposite.target.state == RESOLVED) { + + // Let's solve match connections... + + if (LinearSystem.getMetrics() != null) { + LinearSystem.getMetrics().matchConnectionResolved++; + } + resolvedTarget = target.resolvedTarget; + opposite.resolvedTarget = opposite.target.resolvedTarget; + + resolvedOffset = target.resolvedOffset + offset; + opposite.resolvedOffset = opposite.target.resolvedOffset + opposite.offset; + + didResolve(); + opposite.didResolve(); + } else if (type == BARRIER_CONNECTION) { + myAnchor.mOwner.resolve(); + } + } + + public void setType(int type) { + this.type = type; + } + + @Override + public void reset() { + super.reset(); + target = null; + offset = 0; + dimension = null; + dimensionMultiplier = 1; + oppositeDimension = null; + oppositeDimensionMultiplier = 1; + resolvedTarget = null; + resolvedOffset = 0; + computedValue = 0; + opposite = null; + oppositeOffset = 0; + type = UNCONNECTED; + } + + public void update() { + ConstraintAnchor targetAnchor = myAnchor.getTarget(); + if (targetAnchor == null) { + return; + } + if (targetAnchor.getTarget() == myAnchor) { + type = CHAIN_CONNECTION; + targetAnchor.getResolutionNode().type = CHAIN_CONNECTION; + } + int margin = myAnchor.getMargin(); + if (myAnchor.mType == ConstraintAnchor.Type.RIGHT + || myAnchor.mType == ConstraintAnchor.Type.BOTTOM) { + margin = -margin; + } + dependsOn(targetAnchor.getResolutionNode(), margin); + } + + public void dependsOn(int type, ResolutionAnchor node, int offset) { + this.type = type; + target = node; + this.offset = offset; + target.addDependent(this); + if (ConstraintWidgetContainer.DEBUG_GRAPH) { + System.out.println("a- " + this + " DEPENDS [" + sType(type) + "] ON " + node + " WITH OFFSET " + offset); + } + } + + public void dependsOn(ResolutionAnchor node, int offset) { + target = node; + this.offset = offset; + target.addDependent(this); + if (ConstraintWidgetContainer.DEBUG_GRAPH) { + System.out.println("b- " + this + " DEPENDS [" + sType(type) + "] ON " + node + " WITH OFFSET " + offset); + } + } + + public void dependsOn(ResolutionAnchor node, int multiplier, ResolutionDimension dimension) { + target = node; + target.addDependent(this); + this.dimension = dimension; + this.dimensionMultiplier = multiplier; + this.dimension.addDependent(this); + + if (ConstraintWidgetContainer.DEBUG_GRAPH) { + System.out.println("c- " + this + " DEPENDS [" + sType(type) + "] ON " + node + " WITH DIMENSION " + dimension); + } + } + + public void setOpposite(ResolutionAnchor opposite, float oppositeOffset) { + this.opposite = opposite; + this.oppositeOffset = oppositeOffset; + } + + public void setOpposite(ResolutionAnchor opposite, int multiplier, ResolutionDimension dimension) { + this.opposite = opposite; + this.oppositeDimension = dimension; + this.oppositeDimensionMultiplier = multiplier; + } + + void addResolvedValue(LinearSystem system) { + SolverVariable sv = myAnchor.getSolverVariable(); + + if (resolvedTarget == null) { + system.addEquality(sv, (int) resolvedOffset); + } else { + SolverVariable v = system.createObjectVariable(resolvedTarget.myAnchor); + system.addEquality(sv, v, (int) resolvedOffset, SolverVariable.STRENGTH_FIXED); + } + } + + public float getResolvedValue() { + return resolvedOffset; + } +} diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionDimension.java b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionDimension.java new file mode 100644 index 0000000..df994ac --- /dev/null +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionDimension.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Resolution node for widget dimensions + */ +public class ResolutionDimension extends ResolutionNode { + + float value = 0; + + public void reset() { + super.reset(); + value = 0; + } + + public void resolve(int value) { + if (state == UNRESOLVED || this.value != value) { + this.value = value; + if (state == RESOLVED) { + invalidate(); + } + didResolve(); + } + } + + public void remove() { + state = REMOVED; + } +}
\ No newline at end of file diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionNode.java b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionNode.java index d421f68..31fb7ec 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionNode.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ResolutionNode.java @@ -15,32 +15,13 @@ */ package android.support.constraint.solver.widgets; -import android.support.constraint.solver.LinearSystem; -import android.support.constraint.solver.SolverVariable; - import java.util.HashSet; /** - * Implements a mechanism to resolve nodes via a dependency graph + * Root class for Resolution nodes used by the direct solver. */ public class ResolutionNode { - ConstraintAnchor myAnchor; - float computedValue; - ResolutionNode target; - float offset; - - HashSet<ResolutionNode> dependents = new HashSet<>(4); - ResolutionNode resolvedTarget; - float resolvedOffset; - - int type = UNCONNECTED; - - public static final int UNCONNECTED = 0; - public static final int DIRECT_CONNECTION = 1; - public static final int CENTER_CONNECTION = 2; - public static final int MATCH_CONNECTION = 3; - public static final int CHAIN_CONNECTION = 4; - public static final int BARRIER_CONNECTION = 5; + HashSet<ResolutionNode> dependents = new HashSet<>(2); /** * A node has two possible states: @@ -49,247 +30,57 @@ public class ResolutionNode { */ public static final int UNRESOLVED = 0; public static final int RESOLVED = 1; + public static final int REMOVED = 2; int state = UNRESOLVED; - private ResolutionNode opposite; - private float oppositeOffset; - - public ResolutionNode(ConstraintAnchor anchor) { - myAnchor = anchor; - } - - @Override - public String toString() { - if (state == RESOLVED) { - if (resolvedTarget == this) { - return "[" + myAnchor + ", RESOLVED: " + resolvedOffset + "]"; - } - return "[" + myAnchor + ", RESOLVED: " + resolvedTarget + ":" + resolvedOffset + "]"; - } - return "{ " + myAnchor + " UNRESOLVED}"; - } - - public void didResolve() { - state = RESOLVED; - if (ConstraintWidgetContainer.DEBUG_GRAPH) { - System.out.println(" -> did resolve " + this + " type: " + sType(type)); - for (ResolutionNode node : dependents) { - System.out.println(" * dependent: " + node); - } - } - - for (ResolutionNode node : dependents) { - node.resolve(); - } - } - - public void resolve(ResolutionNode target, float offset) { - resolvedTarget = target; - resolvedOffset = offset; - didResolve(); - } - - String sType(int type) { - if (type == 1) { - return "DIRECT"; - } else if (type == 2) { - return "CENTER"; - } else if (type == 3) { - return "MATCH"; - } else if (type == 4) { - return "CHAIN"; - } else if (type == 5) { - return "BARRIER"; - } - return "UNCONNECTED"; - } - - public void resolve() { - if (ConstraintWidgetContainer.DEBUG_GRAPH) { - System.out.println("resolve " + this + " type: " + sType(type) - + " current target: " + target + " with offset " + offset); - } - if (state == RESOLVED) { - return; - } - if (type == CHAIN_CONNECTION) { - return; - } - if (type == DIRECT_CONNECTION - && ((target == null) || (target.state == RESOLVED))) { - - // Let's solve direct connections... - - if (target == null) { - resolvedTarget = this; - resolvedOffset = offset; - } else { - resolvedTarget = target.resolvedTarget; - resolvedOffset = target.resolvedOffset + offset; - } - didResolve(); - } else if (type == CENTER_CONNECTION - && target != null - && target.state == RESOLVED - && opposite != null && opposite.target != null - && opposite.target.state == RESOLVED) { - - // Let's solve center connections... - - if (LinearSystem.getMetrics() != null) { - LinearSystem.getMetrics().centerConnectionResolved++; - } - resolvedTarget = target.resolvedTarget; - opposite.resolvedTarget = opposite.target.resolvedTarget; - - float distance = 0; - float percent = 0.5f; - - if (oppositeOffset > 0) { - // we are right or bottom - distance = target.resolvedOffset - opposite.target.resolvedOffset; - } else { - distance = opposite.target.resolvedOffset - target.resolvedOffset; - } - - if (myAnchor.mType == ConstraintAnchor.Type.LEFT - || myAnchor.mType == ConstraintAnchor.Type.RIGHT) { - distance -= myAnchor.mOwner.getWidth(); - percent = myAnchor.mOwner.mHorizontalBiasPercent; - } else { - distance -= myAnchor.mOwner.getHeight(); - percent = myAnchor.mOwner.mVerticalBiasPercent; - } - int margin = myAnchor.getMargin(); - int oppositeMargin = opposite.myAnchor.getMargin(); - if (myAnchor.getTarget() == opposite.myAnchor.getTarget()) { - percent = 0.5f; - margin = 0; - oppositeMargin = 0; - } - - distance -= margin; - distance -= oppositeMargin; - - if (oppositeOffset > 0) { - // we are right or bottom - opposite.resolvedOffset = opposite.target.resolvedOffset - + oppositeMargin + distance * percent; - resolvedOffset = target.resolvedOffset - margin - (distance * (1 - percent)); - } else { - resolvedOffset = target.resolvedOffset + margin + distance * percent; - opposite.resolvedOffset = opposite.target.resolvedOffset - - oppositeMargin - (distance * (1 - percent)); - } - - didResolve(); - opposite.didResolve(); - } else if (type == MATCH_CONNECTION - && target != null - && target.state == RESOLVED - && opposite != null && opposite.target != null - && opposite.target.state == RESOLVED) { - - // Let's solve match connections... - - if (LinearSystem.getMetrics() != null) { - LinearSystem.getMetrics().matchConnectionResolved++; - } - resolvedTarget = target.resolvedTarget; - opposite.resolvedTarget = opposite.target.resolvedTarget; - - resolvedOffset = target.resolvedOffset + offset; - opposite.resolvedOffset = opposite.target.resolvedOffset + opposite.offset; - - didResolve(); - opposite.didResolve(); - } else if (type == BARRIER_CONNECTION) { - myAnchor.mOwner.resolve(); - } - } - - // First pass we build a graph of ResolutionNode - - public void setType(int type) { - this.type = type; - } public void addDependent(ResolutionNode node) { dependents.add(node); } - public void resetResolution() { + public void reset() { state = UNRESOLVED; - resolvedTarget = null; - resolvedOffset = 0; + dependents.clear(); } - public void reset() { - target = null; - offset = 0; - dependents.clear(); - resolvedTarget = null; - resolvedOffset = 0; - computedValue = 0; - opposite = null; - oppositeOffset = 0; - type = UNCONNECTED; + public void invalidate() { state = UNRESOLVED; + for (ResolutionNode node : dependents) { + node.invalidate(); + } } - public void update() { - ConstraintAnchor targetAnchor = myAnchor.getTarget(); - if (targetAnchor == null) { - return; + public void invalidateAnchors() { + if (this instanceof ResolutionAnchor) { + state = UNRESOLVED; } - if (targetAnchor.getTarget() == myAnchor) { - type = CHAIN_CONNECTION; - targetAnchor.getResolutionNode().type = CHAIN_CONNECTION; - } - int margin = myAnchor.getMargin(); - if (myAnchor.mType == ConstraintAnchor.Type.RIGHT - || myAnchor.mType == ConstraintAnchor.Type.BOTTOM) { - margin = -margin; + for (ResolutionNode node : dependents) { + node.invalidateAnchors(); } - dependsOn(targetAnchor.getResolutionNode(), margin); } - public void useAnchor(ConstraintAnchor anchor) { - ResolutionNode node = anchor.getResolutionNode(); - } - - public void dependsOn(int type, ResolutionNode node, int offset) { - this.type = type; - target = node; - this.offset = offset; - target.addDependent(this); + public void didResolve() { + state = RESOLVED; if (ConstraintWidgetContainer.DEBUG_GRAPH) { - System.out.println("a- " + this + " DEPENDS [" + sType(type) + "] ON " + node + " WITH OFFSET " + offset); + System.out.println(" -> did resolve " + this); + for (ResolutionNode node : dependents) { + System.out.println(" * dependent: " + node); + } } - } - - public void dependsOn(ResolutionNode node, int offset) { - target = node; - this.offset = offset; - target.addDependent(this); - if (ConstraintWidgetContainer.DEBUG_GRAPH) { - System.out.println("b- " + this + " DEPENDS [" + sType(type) + "] ON " + node + " WITH OFFSET " + offset); + for (ResolutionNode node : dependents) { + node.resolve(); } } - public void setOpposite(ResolutionNode opposite, float oppositeOffset) { - this.opposite = opposite; - this.oppositeOffset = oppositeOffset; + public boolean isResolved() { + return state == RESOLVED; } - void addResolvedValue(LinearSystem system) { - SolverVariable sv = myAnchor.getSolverVariable(); + public void resolve() { + // do nothing + } - if (resolvedTarget == null) { - system.addEquality(sv, (int) resolvedOffset); - } else { - SolverVariable v = system.createObjectVariable(resolvedTarget.myAnchor); - system.addEquality(sv, v, (int) resolvedOffset, SolverVariable.STRENGTH_FIXED); - } + public void remove(ResolutionDimension resolutionDimension) { + // do nothing } } |