| /* |
| * Copyright (C) 2015 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.*; |
| |
| 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; |
| |
| /** |
| * Implements a constraint Widget model supporting constraints relations between other widgets. |
| * <p> |
| * The widget has various anchors (i.e. Left, Top, Right, Bottom, representing their respective |
| * sides, as well as Baseline, Center_X and Center_Y). Connecting anchors from one widget to another |
| * represents a constraint relation between the two anchors; the {@link LinearSystem} will then |
| * be able to use this model to try to minimize the distances between connected anchors. |
| * </p> |
| * <p> |
| * If opposite anchors are connected (e.g. Left and Right anchors), if they have the same strength, |
| * the widget will be equally pulled toward their respective target anchor positions; if the widget |
| * has a fixed size, this means that the widget will be centered between the two target anchors. If |
| * the widget's size is allowed to adjust, the size of the widget will change to be as large as |
| * necessary so that the widget's anchors and the target anchors' distances are zero. |
| * </p> |
| * Constraints are set by connecting a widget's anchor to another via the |
| * {@link #connect} function. |
| */ |
| public class ConstraintWidget { |
| private static final boolean AUTOTAG_CENTER = false; |
| protected static final int SOLVER = 1; |
| protected static final int DIRECT = 2; |
| |
| public static final int MATCH_CONSTRAINT_SPREAD = 0; |
| public static final int MATCH_CONSTRAINT_WRAP = 1; |
| public static final int MATCH_CONSTRAINT_PERCENT = 2; |
| public static final int MATCH_CONSTRAINT_RATIO = 3; |
| public static final int MATCH_CONSTRAINT_RATIO_RESOLVED = 4; |
| |
| public static final int UNKNOWN = -1; |
| public static final int HORIZONTAL = 0; |
| public static final int VERTICAL = 1; |
| |
| public static final int VISIBLE = 0; |
| public static final int INVISIBLE = 4; |
| public static final int GONE = 8; |
| |
| // Values of the chain styles |
| public static final int CHAIN_SPREAD = 0; |
| public static final int CHAIN_SPREAD_INSIDE = 1; |
| public static final int CHAIN_PACKED = 2; |
| |
| // Support for direct resolution |
| public int mHorizontalResolution = UNKNOWN; |
| public int mVerticalResolution = UNKNOWN; |
| |
| private static final int WRAP = -2; |
| |
| ResolutionDimension mResolutionWidth; |
| ResolutionDimension mResolutionHeight; |
| |
| int mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD; |
| int mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD; |
| int[] mResolvedMatchConstraintDefault = new int[2]; |
| |
| int mMatchConstraintMinWidth = 0; |
| int mMatchConstraintMaxWidth = 0; |
| float mMatchConstraintPercentWidth = 1; |
| int mMatchConstraintMinHeight = 0; |
| int mMatchConstraintMaxHeight = 0; |
| float mMatchConstraintPercentHeight = 1; |
| boolean mIsWidthWrapContent; |
| boolean mIsHeightWrapContent; |
| |
| 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; |
| |
| public int getMaxHeight() { |
| return mMaxDimension[VERTICAL]; |
| } |
| |
| public int getMaxWidth() { |
| return mMaxDimension[HORIZONTAL]; |
| } |
| |
| public void setMaxWidth(int maxWidth) { |
| mMaxDimension[HORIZONTAL] = maxWidth; |
| } |
| |
| public void setMaxHeight(int maxHeight) { |
| mMaxDimension[VERTICAL] = maxHeight; |
| } |
| |
| 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 |
| */ |
| public enum ContentAlignment { |
| BEGIN, MIDDLE, END, TOP, VERTICAL_MIDDLE, BOTTOM, LEFT, RIGHT |
| } |
| |
| /** |
| * Define how the widget will resize |
| */ |
| public enum DimensionBehaviour { |
| FIXED, WRAP_CONTENT, MATCH_CONSTRAINT, MATCH_PARENT |
| } |
| |
| // The anchors available on the widget |
| // note: all anchors should be added to the mAnchors array (see addAnchors()) |
| ConstraintAnchor mLeft = new ConstraintAnchor(this, ConstraintAnchor.Type.LEFT); |
| ConstraintAnchor mTop = new ConstraintAnchor(this, ConstraintAnchor.Type.TOP); |
| ConstraintAnchor mRight = new ConstraintAnchor(this, ConstraintAnchor.Type.RIGHT); |
| ConstraintAnchor mBottom = new ConstraintAnchor(this, ConstraintAnchor.Type.BOTTOM); |
| ConstraintAnchor mBaseline = new ConstraintAnchor(this, ConstraintAnchor.Type.BASELINE); |
| ConstraintAnchor mCenterX = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_X); |
| ConstraintAnchor mCenterY = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_Y); |
| ConstraintAnchor mCenter = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER); |
| |
| protected static final int ANCHOR_LEFT = 0; |
| protected static final int ANCHOR_RIGHT = 1; |
| protected static final int ANCHOR_TOP = 2; |
| protected static final int ANCHOR_BOTTOM = 3; |
| protected static final int ANCHOR_BASELINE = 4; |
| |
| protected ConstraintAnchor[] mListAnchors = {mLeft, mRight, mTop, mBottom, mBaseline, mCenter}; |
| protected ArrayList<ConstraintAnchor> mAnchors = new ArrayList<>(); |
| |
| // The horizontal and vertical behaviour for the widgets' dimensions |
| static final int DIMENSION_HORIZONTAL = 0; |
| static final int DIMENSION_VERTICAL = 1; |
| protected DimensionBehaviour[] mListDimensionBehaviors = {DimensionBehaviour.FIXED, DimensionBehaviour.FIXED}; |
| |
| // Parent of this widget |
| ConstraintWidget mParent = null; |
| |
| // Dimensions of the widget |
| int mWidth = 0; |
| int mHeight = 0; |
| protected float mDimensionRatio = 0; |
| protected int mDimensionRatioSide = UNKNOWN; |
| |
| // 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; |
| private int mDrawY = 0; |
| private int mDrawWidth = 0; |
| private int mDrawHeight = 0; |
| |
| // Root offset |
| protected int mOffsetX = 0; |
| protected int mOffsetY = 0; |
| |
| // Baseline distance relative to the top of the widget |
| int mBaselineDistance = 0; |
| |
| // Minimum sizes for the widget |
| protected int mMinWidth; |
| protected int mMinHeight; |
| |
| // Wrap content sizes for the widget |
| private int mWrapWidth; |
| private int mWrapHeight; |
| |
| // Percentages used for biasing one connection over another when dual connections |
| // of the same strength exist |
| public static float DEFAULT_BIAS = 0.5f; |
| float mHorizontalBiasPercent = DEFAULT_BIAS; |
| float mVerticalBiasPercent = DEFAULT_BIAS; |
| |
| // The companion widget (typically, the real widget we represent) |
| private Object mCompanionWidget; |
| |
| // This is used to possibly "skip" a position while inside a container. For example, |
| // a container like ConstraintTableLayout can use this to implement empty cells |
| // (the item positioned after the empty cell will have a skip value of 1) |
| private int mContainerItemSkip = 0; |
| |
| // Contains the visibility status of the widget (VISIBLE, INVISIBLE, or GONE) |
| private int mVisibility = VISIBLE; |
| |
| private String mDebugName = null; |
| private String mType = null; |
| |
| int mDistToTop; |
| int mDistToLeft; |
| int mDistToRight; |
| int mDistToBottom; |
| boolean mLeftHasCentered; |
| boolean mRightHasCentered; |
| boolean mTopHasCentered; |
| boolean mBottomHasCentered; |
| boolean mHorizontalWrapVisited; |
| boolean mVerticalWrapVisited; |
| boolean mOptimizerMeasurable = false; |
| boolean mOptimizerMeasured = false; |
| boolean mGroupsToSolver = false; |
| |
| // Chain support |
| int mHorizontalChainStyle = CHAIN_SPREAD; |
| int mVerticalChainStyle = CHAIN_SPREAD; |
| boolean mHorizontalChainFixedPosition; |
| boolean mVerticalChainFixedPosition; |
| |
| float[] mWeight = { UNKNOWN, UNKNOWN}; |
| |
| protected ConstraintWidget[] mListNextMatchConstraintsWidget = {null, null}; |
| protected ConstraintWidget[] mNextChainWidget = {null, null}; |
| |
| ConstraintWidget mHorizontalNextWidget = null; |
| ConstraintWidget mVerticalNextWidget = null; |
| |
| // TODO: see if we can make this simpler |
| public void reset() { |
| mLeft.reset(); |
| mTop.reset(); |
| mRight.reset(); |
| mBottom.reset(); |
| mBaseline.reset(); |
| mCenterX.reset(); |
| mCenterY.reset(); |
| mCenter.reset(); |
| mParent = null; |
| mCircleConstraintAngle = 0; |
| mWidth = 0; |
| mHeight = 0; |
| mDimensionRatio = 0; |
| mDimensionRatioSide = UNKNOWN; |
| mX = 0; |
| mY = 0; |
| mDrawX = 0; |
| mDrawY = 0; |
| mDrawWidth = 0; |
| mDrawHeight = 0; |
| mOffsetX = 0; |
| mOffsetY = 0; |
| mBaselineDistance = 0; |
| mMinWidth = 0; |
| mMinHeight = 0; |
| mWrapWidth = 0; |
| mWrapHeight = 0; |
| mHorizontalBiasPercent = DEFAULT_BIAS; |
| mVerticalBiasPercent = DEFAULT_BIAS; |
| mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED; |
| mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED; |
| mCompanionWidget = null; |
| mContainerItemSkip = 0; |
| mVisibility = VISIBLE; |
| mType = null; |
| mHorizontalWrapVisited = false; |
| mVerticalWrapVisited = false; |
| mHorizontalChainStyle = CHAIN_SPREAD; |
| mVerticalChainStyle = CHAIN_SPREAD; |
| mHorizontalChainFixedPosition = false; |
| mVerticalChainFixedPosition = false; |
| mWeight[DIMENSION_HORIZONTAL] = UNKNOWN; |
| mWeight[DIMENSION_VERTICAL] = UNKNOWN; |
| mHorizontalResolution = UNKNOWN; |
| mVerticalResolution = UNKNOWN; |
| mMaxDimension[HORIZONTAL] = Integer.MAX_VALUE; |
| mMaxDimension[VERTICAL] = Integer.MAX_VALUE; |
| mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD; |
| mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD; |
| mMatchConstraintPercentWidth = 1; |
| mMatchConstraintPercentHeight = 1; |
| mMatchConstraintMaxWidth = Integer.MAX_VALUE; |
| mMatchConstraintMaxHeight = Integer.MAX_VALUE; |
| mMatchConstraintMinWidth = 0; |
| mMatchConstraintMinHeight = 0; |
| mResolvedDimensionRatioSide = UNKNOWN; |
| mResolvedDimensionRatio = 1f; |
| if (mResolutionWidth != null) { |
| mResolutionWidth.reset(); |
| } |
| if (mResolutionHeight != null) { |
| mResolutionHeight.reset(); |
| } |
| mBelongingGroup = null; |
| mOptimizerMeasurable = false; |
| mOptimizerMeasured = false; |
| mGroupsToSolver = false; |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // Optimizer-related methods |
| /*-----------------------------------------------------------------------*/ |
| |
| /** |
| * Reset the resolution nodes of the anchors |
| */ |
| public void resetResolutionNodes() { |
| for (int i = 0; i < 6; i++) { |
| mListAnchors[i].getResolutionNode().reset(); |
| } |
| } |
| |
| /** |
| * Update the resolution nodes of the anchors |
| */ |
| public void updateResolutionNodes() { |
| for (int i = 0; i < 6; i++) { |
| mListAnchors[i].getResolutionNode().update(); |
| } |
| } |
| |
| /** |
| * Graph analysis |
| * @param optimizationLevel the current optimisation level |
| */ |
| public void analyze(int optimizationLevel) { |
| Optimizer.analyze(optimizationLevel,this); |
| } |
| |
| /** |
| * Try resolving the graph analysis |
| */ |
| public void resolve() { |
| // basic constraints resolution is done in ResolutionAnchor |
| } |
| |
| /** |
| * Returns true if all the nodes are resolved |
| * |
| * @return true if the widget is fully resolved |
| */ |
| public boolean isFullyResolved() { |
| 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 |
| /*-----------------------------------------------------------------------*/ |
| |
| /** |
| * Default constructor |
| */ |
| public ConstraintWidget() { |
| addAnchors(); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param x x position |
| * @param y y position |
| * @param width width of the layout |
| * @param height height of the layout |
| */ |
| public ConstraintWidget(int x, int y, int width, int height) { |
| mX = x; |
| mY = y; |
| mWidth = width; |
| mHeight = height; |
| addAnchors(); |
| forceUpdateDrawPosition(); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param width width of the layout |
| * @param height height of the layout |
| */ |
| public ConstraintWidget(int width, int height) { |
| this(0, 0, width, height); |
| } |
| |
| /** |
| * Reset the solver variables of the anchors |
| */ |
| public void resetSolverVariables(Cache cache) { |
| mLeft.resetSolverVariable(cache); |
| mTop.resetSolverVariable(cache); |
| mRight.resetSolverVariable(cache); |
| mBottom.resetSolverVariable(cache); |
| mBaseline.resetSolverVariable(cache); |
| mCenter.resetSolverVariable(cache); |
| mCenterX.resetSolverVariable(cache); |
| mCenterY.resetSolverVariable(cache); |
| } |
| |
| /** |
| * Add all the anchors to the mAnchors array |
| */ |
| private void addAnchors() { |
| mAnchors.add(mLeft); |
| mAnchors.add(mTop); |
| mAnchors.add(mRight); |
| mAnchors.add(mBottom); |
| mAnchors.add(mCenterX); |
| mAnchors.add(mCenterY); |
| mAnchors.add(mCenter); |
| mAnchors.add(mBaseline); |
| } |
| |
| /** |
| * Returns true if the widget is the root widget |
| * |
| * @return true if root widget, false otherwise |
| */ |
| public boolean isRoot() { |
| return mParent == null; |
| } |
| |
| /** |
| * Returns true if the widget is a root container in the hierarchy, |
| * or the root widget itself |
| * |
| * @return true if root container |
| */ |
| public boolean isRootContainer() { |
| return (this instanceof ConstraintWidgetContainer) |
| && (mParent == null || !(mParent instanceof ConstraintWidgetContainer)); |
| } |
| |
| /** |
| * Returns true if the widget is contained in a ConstraintLayout |
| * |
| * @return |
| */ |
| public boolean isInsideConstraintLayout() { |
| ConstraintWidget widget = getParent(); |
| if (widget == null) { |
| return false; |
| } |
| while (widget != null) { |
| if (widget instanceof ConstraintWidgetContainer) { |
| return true; |
| } |
| widget = widget.getParent(); |
| } |
| return false; |
| } |
| |
| /** |
| * Return true if the widget is one of our ancestor |
| * |
| * @param widget widget we want to check |
| * @return true if the given widget is one of our ancestor, false otherwise |
| */ |
| public boolean hasAncestor(ConstraintWidget widget) { |
| ConstraintWidget parent = getParent(); |
| if (parent == widget) { |
| return true; |
| } |
| if (parent == widget.getParent()) { |
| return false; // the widget is one of our sibling |
| } |
| while (parent != null) { |
| if (parent == widget) { |
| return true; |
| } |
| if (parent == widget.getParent()) { |
| // the widget is a sibling of one of our ancestor |
| return true; |
| } |
| parent = parent.getParent(); |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the top-level container, regardless if it's a ConstraintWidgetContainer |
| * or a simple WidgetContainer. |
| * |
| * @return top-level WidgetContainer |
| */ |
| public WidgetContainer getRootWidgetContainer() { |
| ConstraintWidget root = this; |
| while (root.getParent() != null) { |
| root = root.getParent(); |
| } |
| if (root instanceof WidgetContainer) { |
| return (WidgetContainer) root; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the parent of this widget if there is one |
| * |
| * @return parent |
| */ |
| public ConstraintWidget getParent() { |
| return mParent; |
| } |
| |
| /** |
| * Set the parent of this widget |
| * |
| * @param widget parent |
| */ |
| public void setParent(ConstraintWidget widget) { |
| mParent = widget; |
| } |
| |
| /** |
| * Keep track of wrap_content for width |
| * @param widthWrapContent |
| */ |
| public void setWidthWrapContent(boolean widthWrapContent) { |
| this.mIsWidthWrapContent = widthWrapContent; |
| } |
| |
| /** |
| * Returns true if width is set to wrap_content |
| * @return |
| */ |
| public boolean isWidthWrapContent() { |
| return mIsWidthWrapContent; |
| } |
| |
| /** |
| * Keep track of wrap_content for height |
| * @param heightWrapContent |
| */ |
| public void setHeightWrapContent(boolean heightWrapContent) { |
| this.mIsHeightWrapContent = heightWrapContent; |
| } |
| |
| /** |
| * Returns true if height is set to wrap_content |
| * @return |
| */ |
| public boolean isHeightWrapContent() { return mIsHeightWrapContent; } |
| |
| /** |
| * Set a circular constraint |
| * |
| * @param target the target widget we will use as the center of the circle |
| * @param angle the angle (from 0 to 360) |
| * @param radius the radius used |
| */ |
| public void connectCircularConstraint(ConstraintWidget target, float angle, int radius) { |
| immediateConnect(ConstraintAnchor.Type.CENTER, target, ConstraintAnchor.Type.CENTER, |
| radius, 0); |
| mCircleConstraintAngle = angle; |
| } |
| |
| /** |
| * Returns the type string if set |
| * |
| * @return type (null if not set) |
| */ |
| public String getType() { |
| return mType; |
| } |
| |
| /** |
| * Set the type of the widget (as a String) |
| * |
| * @param type type of the widget |
| */ |
| public void setType(String type) { |
| mType = type; |
| } |
| |
| /** |
| * Set the visibility for this widget |
| * |
| * @param visibility either VISIBLE, INVISIBLE, or GONE |
| */ |
| public void setVisibility(int visibility) { |
| mVisibility = visibility; |
| } |
| |
| /** |
| * Returns the current visibility value for this widget |
| * |
| * @return the visibility (VISIBLE, INVISIBLE, or GONE) |
| */ |
| public int getVisibility() { |
| return mVisibility; |
| } |
| |
| /** |
| * Returns the name of this widget (used for debug purposes) |
| * |
| * @return the debug name |
| */ |
| public String getDebugName() { |
| return mDebugName; |
| } |
| |
| /** |
| * Set the debug name of this widget |
| */ |
| public void setDebugName(String name) { |
| mDebugName = name; |
| } |
| |
| /** |
| * Utility debug function. Sets the names of the anchors in the solver given |
| * a widget's name. The given name is used as a prefix, resulting in anchors' names |
| * of the form: |
| * <p/> |
| * <ul> |
| * <li>{name}.left</li> |
| * <li>{name}.top</li> |
| * <li>{name}.right</li> |
| * <li>{name}.bottom</li> |
| * <li>{name}.baseline</li> |
| * </ul> |
| * |
| * @param system solver used |
| * @param name name of the widget |
| */ |
| public void setDebugSolverName(LinearSystem system, String name) { |
| mDebugName = name; |
| SolverVariable left = system.createObjectVariable(mLeft); |
| SolverVariable top = system.createObjectVariable(mTop); |
| SolverVariable right = system.createObjectVariable(mRight); |
| SolverVariable bottom = system.createObjectVariable(mBottom); |
| left.setName(name + ".left"); |
| top.setName(name + ".top"); |
| right.setName(name + ".right"); |
| bottom.setName(name + ".bottom"); |
| if (mBaselineDistance > 0) { |
| SolverVariable baseline = system.createObjectVariable(mBaseline); |
| baseline.setName(name + ".baseline"); |
| } |
| } |
| |
| /** |
| * Create all the system variables for this widget |
| * @hide |
| * @param system |
| */ |
| public void createObjectVariables(LinearSystem system) { |
| SolverVariable left = system.createObjectVariable(mLeft); |
| SolverVariable top = system.createObjectVariable(mTop); |
| SolverVariable right = system.createObjectVariable(mRight); |
| SolverVariable bottom = system.createObjectVariable(mBottom); |
| if (mBaselineDistance > 0) { |
| SolverVariable baseline = system.createObjectVariable(mBaseline); |
| } |
| } |
| |
| /** |
| * Returns a string representation of the ConstraintWidget |
| * |
| * @return string representation of the widget |
| */ |
| @Override |
| public String toString() { |
| return (mType != null ? "type: " + mType + " " : "") |
| + (mDebugName != null ? "id: " + mDebugName + " " : "") |
| + "(" + mX + ", " + mY + ") - (" + mWidth + " x " + mHeight + ")" |
| + " wrap: (" + mWrapWidth + " x " + mWrapHeight + ")"; |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // Position |
| /*-----------------------------------------------------------------------*/ |
| // The widget position is expressed in two ways: |
| // - relative to its direct parent container (getX(), getY()) |
| // - relative to the root container (getDrawX(), getDrawY()) |
| // Additionally, getDrawX()/getDrawY() are used when animating the |
| // widget position on screen |
| /*-----------------------------------------------------------------------*/ |
| |
| int getInternalDrawX() { |
| return mDrawX; |
| } |
| |
| int getInternalDrawY() { |
| return mDrawY; |
| } |
| |
| public int getInternalDrawRight() { |
| return mDrawX + mDrawWidth; |
| } |
| |
| public int getInternalDrawBottom() { |
| return mDrawY + mDrawHeight; |
| } |
| |
| |
| /** |
| * Return the x position of the widget, relative to its container |
| * |
| * @return x position |
| */ |
| public int getX() { |
| return mX; |
| } |
| |
| /** |
| * Return the y position of the widget, relative to its container |
| * |
| * @return y position |
| */ |
| public int getY() { |
| return mY; |
| } |
| |
| /** |
| * Return the width of the widget |
| * |
| * @return width width |
| */ |
| public int getWidth() { |
| if (mVisibility == ConstraintWidget.GONE) { |
| return 0; |
| } |
| return mWidth; |
| } |
| |
| public int getOptimizerWrapWidth() { |
| int w = mWidth; |
| if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.MATCH_CONSTRAINT) { |
| if (mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) { |
| w = Math.max(mMatchConstraintMinWidth, w); |
| } else if (mMatchConstraintMinWidth > 0) { |
| w = mMatchConstraintMinWidth; |
| mWidth = w; |
| } else { |
| w = 0; |
| } |
| if (mMatchConstraintMaxWidth > 0 && mMatchConstraintMaxWidth < w) { |
| w = mMatchConstraintMaxWidth; |
| } |
| } |
| return w; |
| } |
| |
| public int getOptimizerWrapHeight() { |
| int h = mHeight; |
| if (mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.MATCH_CONSTRAINT) { |
| if (mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) { |
| h = Math.max(mMatchConstraintMinHeight, h); |
| } else if (mMatchConstraintMinHeight > 0) { |
| h = mMatchConstraintMinHeight; |
| mHeight = h; |
| } else { |
| h = 0; |
| } |
| if (mMatchConstraintMaxHeight > 0 && mMatchConstraintMaxHeight < h) { |
| h = mMatchConstraintMaxHeight; |
| } |
| } |
| return h; |
| } |
| |
| /** |
| * Return the wrap width of the widget |
| * |
| * @return the wrap width |
| */ |
| public int getWrapWidth() { |
| return mWrapWidth; |
| } |
| |
| /** |
| * Return the height of the widget |
| * |
| * @return height height |
| */ |
| public int getHeight() { |
| if (mVisibility == ConstraintWidget.GONE) { |
| return 0; |
| } |
| return mHeight; |
| } |
| |
| /** |
| * Return the wrap height of the widget |
| * |
| * @return the wrap height |
| */ |
| public int getWrapHeight() { |
| return mWrapHeight; |
| } |
| |
| /** |
| * 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 |
| */ |
| public int getDrawX() { |
| return mDrawX + mOffsetX; |
| } |
| |
| /** |
| * Return the y position of the widget, relative to the root |
| * |
| * @return |
| */ |
| public int getDrawY() { |
| return mDrawY + mOffsetY; |
| } |
| |
| public int getDrawWidth() { |
| return mDrawWidth; |
| } |
| |
| public int getDrawHeight() { |
| return mDrawHeight; |
| } |
| |
| /** |
| * Return the bottom position of the widget, relative to the root |
| * |
| * @return bottom position of the widget |
| */ |
| public int getDrawBottom() { |
| return getDrawY() + mDrawHeight; |
| } |
| |
| /** |
| * Return the right position of the widget, relative to the root |
| * |
| * @return right position of the widget |
| */ |
| public int getDrawRight() { |
| return getDrawX() + mDrawWidth; |
| } |
| |
| /** |
| * Return the x position of the widget, relative to the root |
| * (without animation) |
| * |
| * @return x position |
| */ |
| protected int getRootX() { |
| return mX + mOffsetX; |
| } |
| |
| /** |
| * Return the y position of the widget, relative to the root |
| * (without animation) |
| * |
| * @return |
| */ |
| protected int getRootY() { |
| return mY + mOffsetY; |
| } |
| |
| /** |
| * Return the minimum width of the widget |
| * |
| * @return minimum width |
| */ |
| public int getMinWidth() { |
| return mMinWidth; |
| } |
| |
| /** |
| * Return the minimum height of the widget |
| * |
| * @return minimum height |
| */ |
| public int getMinHeight() { |
| return mMinHeight; |
| } |
| |
| /** |
| * Return the left position of the widget (similar to {@link #getX()}) |
| * |
| * @return left position of the widget |
| */ |
| public int getLeft() { |
| return getX(); |
| } |
| |
| /** |
| * Return the top position of the widget (similar to {@link #getY()}) |
| * |
| * @return top position of the widget |
| */ |
| public int getTop() { |
| return getY(); |
| } |
| |
| /** |
| * Return the right position of the widget |
| * |
| * @return right position of the widget |
| */ |
| public int getRight() { |
| return getX() + mWidth; |
| } |
| |
| /** |
| * Return the bottom position of the widget |
| * |
| * @return bottom position of the widget |
| */ |
| public int getBottom() { |
| return getY() + mHeight; |
| } |
| |
| /** |
| * Return the horizontal percentage bias that is used when two opposite connections |
| * exist of the same strengh. |
| * |
| * @return horizontal percentage bias |
| */ |
| public float getHorizontalBiasPercent() { |
| return mHorizontalBiasPercent; |
| } |
| |
| /** |
| * Return the vertical percentage bias that is used when two opposite connections |
| * exist of the same strengh. |
| * |
| * @return vertical percentage bias |
| */ |
| public float getVerticalBiasPercent() { |
| return mVerticalBiasPercent; |
| } |
| |
| /** |
| * 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 |
| */ |
| public boolean hasBaseline() { |
| return mBaselineDistance > 0; |
| } |
| |
| /** |
| * Return the baseline distance relative to the top of the widget |
| * |
| * @return baseline |
| */ |
| public int getBaselineDistance() { |
| return mBaselineDistance; |
| } |
| |
| /** |
| * Return the companion widget. Typically, this would be the real |
| * widget we represent with this instance of ConstraintWidget. |
| * |
| * @return the companion widget, if set. |
| */ |
| public Object getCompanionWidget() { |
| return mCompanionWidget; |
| } |
| |
| /** |
| * Return the array of anchors of this widget |
| * |
| * @return array of anchors |
| */ |
| public ArrayList<ConstraintAnchor> getAnchors() { |
| return mAnchors; |
| } |
| |
| /** |
| * Set the x position of the widget, relative to its container |
| * |
| * @param x x position |
| */ |
| public void setX(int x) { |
| mX = x; |
| } |
| |
| /** |
| * Set the y position of the widget, relative to its container |
| * |
| * @param y y position |
| */ |
| public void setY(int y) { |
| mY = y; |
| } |
| |
| /** |
| * Set both the origin in (x, y) of the widget, relative to its container |
| * |
| * @param x x position |
| * @param y y position |
| */ |
| public void setOrigin(int x, int y) { |
| mX = x; |
| mY = y; |
| } |
| |
| /** |
| * Set the offset of this widget relative to the root widget |
| * |
| * @param x horizontal offset |
| * @param y vertical offset |
| */ |
| public void setOffset(int x, int y) { |
| mOffsetX = x; |
| mOffsetY = y; |
| } |
| |
| /** |
| * Set the margin to be used when connected to a widget with a visibility of GONE |
| * |
| * @param type the anchor to set the margin on |
| * @param goneMargin the margin value to use |
| */ |
| public void setGoneMargin(ConstraintAnchor.Type type, int goneMargin) { |
| switch (type) { |
| case LEFT: { |
| mLeft.mGoneMargin = goneMargin; |
| } |
| break; |
| case TOP: { |
| mTop.mGoneMargin = goneMargin; |
| } |
| break; |
| case RIGHT: { |
| mRight.mGoneMargin = goneMargin; |
| } |
| break; |
| case BOTTOM: { |
| mBottom.mGoneMargin = goneMargin; |
| } |
| break; |
| case BASELINE: |
| case CENTER: |
| case CENTER_X: |
| case CENTER_Y: |
| case NONE: |
| break; |
| } |
| } |
| |
| /** |
| * Update the draw position to match the true position. |
| * If animating is on, the transition between the old |
| * position and new position will be animated... |
| */ |
| public void updateDrawPosition() { |
| int left = mX; |
| int top = mY; |
| int right = mX + mWidth; |
| int bottom = mY + mHeight; |
| mDrawX = left; |
| mDrawY = top; |
| mDrawWidth = right - left; |
| mDrawHeight = bottom - top; |
| } |
| |
| /** |
| * Update the draw positition immediately to match the true position |
| */ |
| public void forceUpdateDrawPosition() { |
| int left = mX; |
| int top = mY; |
| int right = mX + mWidth; |
| int bottom = mY + mHeight; |
| mDrawX = left; |
| mDrawY = top; |
| mDrawWidth = right - left; |
| mDrawHeight = bottom - top; |
| } |
| |
| /** |
| * Set both the origin in (x, y) of the widget, relative to the root |
| * |
| * @param x x position |
| * @param y y position |
| */ |
| public void setDrawOrigin(int x, int y) { |
| mDrawX = x - mOffsetX; |
| mDrawY = y - mOffsetY; |
| mX = mDrawX; |
| mY = mDrawY; |
| } |
| |
| /** |
| * Set the x position of the widget, relative to the root |
| * |
| * @param x x position |
| */ |
| public void setDrawX(int x) { |
| mDrawX = x - mOffsetX; |
| mX = mDrawX; |
| } |
| |
| /** |
| * Set the y position of the widget, relative to its container |
| * |
| * @param y y position |
| */ |
| public void setDrawY(int y) { |
| mDrawY = y - mOffsetY; |
| mY = mDrawY; |
| } |
| |
| /** |
| * Set the draw width of the widget |
| * |
| * @param drawWidth |
| */ |
| public void setDrawWidth(int drawWidth) { |
| mDrawWidth = drawWidth; |
| } |
| |
| /** |
| * Set the draw height of the widget |
| * |
| * @param drawHeight |
| */ |
| public void setDrawHeight(int drawHeight) { |
| mDrawHeight = drawHeight; |
| } |
| |
| /** |
| * Set the width of the widget |
| * |
| * @param w width |
| */ |
| public void setWidth(int w) { |
| mWidth = w; |
| if (mWidth < mMinWidth) { |
| mWidth = mMinWidth; |
| } |
| } |
| |
| /** |
| * Set the height of the widget |
| * |
| * @param h height |
| */ |
| public void setHeight(int h) { |
| mHeight = h; |
| if (mHeight < mMinHeight) { |
| mHeight = mMinHeight; |
| } |
| } |
| |
| /** |
| * 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 |
| * @param min minimum value |
| * @param max maximum value |
| * @param percent |
| */ |
| public void setHorizontalMatchStyle(int horizontalMatchStyle, int min, int max, float percent) { |
| mMatchConstraintDefaultWidth = horizontalMatchStyle; |
| mMatchConstraintMinWidth = min; |
| mMatchConstraintMaxWidth = max; |
| mMatchConstraintPercentWidth = percent; |
| if (percent < 1 && mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) { |
| mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_PERCENT; |
| } |
| } |
| |
| /** |
| * Set the vertical style when MATCH_CONSTRAINT is set |
| * |
| * @param verticalMatchStyle MATCH_CONSTRAINT_SPREAD or MATCH_CONSTRAINT_WRAP |
| * @param min minimum value |
| * @param max maximum value |
| * @param percent |
| */ |
| public void setVerticalMatchStyle(int verticalMatchStyle, int min, int max, float percent) { |
| mMatchConstraintDefaultHeight = verticalMatchStyle; |
| mMatchConstraintMinHeight = min; |
| mMatchConstraintMaxHeight = max; |
| mMatchConstraintPercentHeight = percent; |
| if (percent < 1 && mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) { |
| mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_PERCENT; |
| } |
| } |
| |
| /** |
| * Set the ratio of the widget from a given string of format [H|V],[float|x:y] or [float|x:y] |
| * |
| * @param ratio |
| */ |
| public void setDimensionRatio(String ratio) { |
| if (ratio == null || ratio.length() == 0) { |
| mDimensionRatio = 0; |
| return; |
| } |
| int dimensionRatioSide = UNKNOWN; |
| float dimensionRatio = 0; |
| int len = ratio.length(); |
| int commaIndex = ratio.indexOf(','); |
| if (commaIndex > 0 && commaIndex < len - 1) { |
| String dimension = ratio.substring(0, commaIndex); |
| if (dimension.equalsIgnoreCase("W")) { |
| dimensionRatioSide = HORIZONTAL; |
| } else if (dimension.equalsIgnoreCase("H")) { |
| dimensionRatioSide = VERTICAL; |
| } |
| commaIndex++; |
| } else { |
| commaIndex = 0; |
| } |
| int colonIndex = ratio.indexOf(':'); |
| |
| if (colonIndex >= 0 && colonIndex < len - 1) { |
| String nominator = ratio.substring(commaIndex, colonIndex); |
| String denominator = ratio.substring(colonIndex + 1); |
| if (nominator.length() > 0 && denominator.length() > 0) { |
| try { |
| float nominatorValue = Float.parseFloat(nominator); |
| float denominatorValue = Float.parseFloat(denominator); |
| if (nominatorValue > 0 && denominatorValue > 0) { |
| if (dimensionRatioSide == VERTICAL) { |
| dimensionRatio = Math.abs(denominatorValue / nominatorValue); |
| } else { |
| dimensionRatio = Math.abs(nominatorValue / denominatorValue); |
| } |
| } |
| } catch (NumberFormatException e) { |
| // Ignore |
| } |
| } |
| } else { |
| String r = ratio.substring(commaIndex); |
| if (r.length() > 0) { |
| try { |
| dimensionRatio = Float.parseFloat(r); |
| } catch (NumberFormatException e) { |
| // Ignore |
| } |
| } |
| } |
| |
| if (dimensionRatio > 0) { |
| mDimensionRatio = dimensionRatio; |
| mDimensionRatioSide = dimensionRatioSide; |
| } |
| } |
| |
| /** |
| * Set the ratio of the widget |
| * The ratio will be applied if at least one of the dimension (width or height) is set to a behaviour |
| * of DimensionBehaviour.MATCH_CONSTRAINT -- the dimension's value will be set to the other dimension * ratio. |
| */ |
| public void setDimensionRatio(float ratio, int dimensionRatioSide) { |
| mDimensionRatio = ratio; |
| mDimensionRatioSide = dimensionRatioSide; |
| } |
| |
| /** |
| * Return the current ratio of this widget |
| * |
| * @return the dimension ratio |
| */ |
| public float getDimensionRatio() { |
| return mDimensionRatio; |
| } |
| |
| /** |
| * Return the current side on which ratio will be applied |
| * |
| * @return HORIZONTAL, VERTICAL, or UNKNOWN |
| */ |
| public int getDimensionRatioSide() { |
| return mDimensionRatioSide; |
| } |
| |
| /** |
| * Set the horizontal bias percent to apply when we have two opposite constraints of |
| * equal strength |
| * |
| * @param horizontalBiasPercent the percentage used |
| */ |
| public void setHorizontalBiasPercent(float horizontalBiasPercent) { |
| mHorizontalBiasPercent = horizontalBiasPercent; |
| } |
| |
| /** |
| * Set the vertical bias percent to apply when we have two opposite constraints of |
| * equal strength |
| * |
| * @param verticalBiasPercent the percentage used |
| */ |
| public void setVerticalBiasPercent(float verticalBiasPercent) { |
| mVerticalBiasPercent = verticalBiasPercent; |
| } |
| |
| /** |
| * Set the minimum width of the widget |
| * |
| * @param w minimum width |
| */ |
| public void setMinWidth(int w) { |
| if (w < 0) { |
| mMinWidth = 0; |
| } else { |
| mMinWidth = w; |
| } |
| } |
| |
| /** |
| * Set the minimum height of the widget |
| * |
| * @param h minimum height |
| */ |
| public void setMinHeight(int h) { |
| if (h < 0) { |
| mMinHeight = 0; |
| } else { |
| mMinHeight = h; |
| } |
| } |
| |
| /** |
| * Set the wrap content width of the widget |
| * |
| * @param w wrap content width |
| */ |
| public void setWrapWidth(int w) { |
| mWrapWidth = w; |
| } |
| |
| /** |
| * Set the wrap content height of the widget |
| * |
| * @param h wrap content height |
| */ |
| public void setWrapHeight(int h) { |
| mWrapHeight = h; |
| } |
| |
| /** |
| * Set both width and height of the widget |
| * |
| * @param w width |
| * @param h height |
| */ |
| public void setDimension(int w, int h) { |
| mWidth = w; |
| if (mWidth < mMinWidth) { |
| mWidth = mMinWidth; |
| } |
| mHeight = h; |
| if (mHeight < mMinHeight) { |
| mHeight = mMinHeight; |
| } |
| } |
| |
| /** |
| * Set the position+dimension of the widget given left/top/right/bottom |
| * |
| * @param left left side position of the widget |
| * @param top top side position of the widget |
| * @param right right side position of the widget |
| * @param bottom bottom side position of the widget |
| */ |
| public void setFrame(int left, int top, int right, int bottom) { |
| int w = right - left; |
| int h = bottom - top; |
| |
| mX = left; |
| mY = top; |
| |
| if (mVisibility == ConstraintWidget.GONE) { |
| mWidth = 0; |
| mHeight = 0; |
| return; |
| } |
| |
| // correct dimensional instability caused by rounding errors |
| if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.FIXED && w < mWidth) { |
| w = mWidth; |
| } |
| if (mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.FIXED && h < mHeight) { |
| h = mHeight; |
| } |
| |
| mWidth = w; |
| mHeight = h; |
| |
| if (mHeight < mMinHeight) { |
| mHeight = mMinHeight; |
| } |
| if (mWidth < mMinWidth) { |
| mWidth = mMinWidth; |
| } |
| |
| 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; |
| } |
| |
| /** |
| * Set the positions for the horizontal dimension only |
| * |
| * @param left |
| * @param right |
| */ |
| public void setHorizontalDimension(int left, int right) { |
| mX = left; |
| mWidth = right - left; |
| if (mWidth < mMinWidth) { |
| mWidth = mMinWidth; |
| } |
| } |
| |
| /** |
| * Set the positions for the vertical dimension only |
| * |
| * @param top |
| * @param bottom |
| */ |
| public void setVerticalDimension(int top, int bottom) { |
| mY = top; |
| mHeight = bottom - top; |
| if (mHeight < mMinHeight) { |
| mHeight = mMinHeight; |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| public void setBaselineDistance(int baseline) { |
| mBaselineDistance = baseline; |
| } |
| |
| /** |
| * Set the companion widget. Typically, this would be the real widget we |
| * represent with this instance of ConstraintWidget. |
| * |
| * @param companion |
| */ |
| public void setCompanionWidget(Object companion) { |
| mCompanionWidget = companion; |
| } |
| |
| /** |
| * Set the skip value for this widget. This can be used when a widget is in a container, |
| * so that container can position the widget as if it was positioned further in the list |
| * of widgets. For example, with ConstraintTableLayout, this is used to skip empty cells |
| * (the widget after an empty cell will have a skip value of one) |
| * |
| * @param skip |
| */ |
| public void setContainerItemSkip(int skip) { |
| if (skip >= 0) { |
| mContainerItemSkip = skip; |
| } else { |
| mContainerItemSkip = 0; |
| } |
| } |
| |
| /** |
| * Accessor for the skip value |
| * |
| * @return skip value |
| */ |
| public int getContainerItemSkip() { |
| return mContainerItemSkip; |
| } |
| |
| /** |
| * Set the horizontal weight (only used in chains) |
| * |
| * @param horizontalWeight |
| */ |
| public void setHorizontalWeight(float horizontalWeight) { |
| mWeight[DIMENSION_HORIZONTAL] = horizontalWeight; |
| } |
| |
| /** |
| * Set the vertical weight (only used in chains) |
| * |
| * @param verticalWeight |
| */ |
| public void setVerticalWeight(float verticalWeight) { |
| mWeight[DIMENSION_VERTICAL] = verticalWeight; |
| } |
| |
| /** |
| * Set the chain starting from this widget to be packed. |
| * The horizontal bias will control how elements of the chain are positioned. |
| * |
| * @param horizontalChainStyle |
| */ |
| public void setHorizontalChainStyle(int horizontalChainStyle) { |
| mHorizontalChainStyle = horizontalChainStyle; |
| } |
| |
| /** |
| * get the chain starting from this widget to be packed. |
| * The horizontal bias will control how elements of the chain are positioned. |
| * |
| * @return Horizontal Chain Style |
| */ |
| public int getHorizontalChainStyle() { |
| return mHorizontalChainStyle; |
| } |
| |
| /** |
| * Set the chain starting from this widget to be packed. |
| * The vertical bias will control how elements of the chain are positioned. |
| * |
| * @param verticalChainStyle |
| */ |
| public void setVerticalChainStyle(int verticalChainStyle) { |
| mVerticalChainStyle = verticalChainStyle; |
| } |
| |
| /** |
| * Set the chain starting from this widget to be packed. |
| * The vertical bias will control how elements of the chain are positioned. |
| * |
| * @return |
| */ |
| public int getVerticalChainStyle() { |
| return mVerticalChainStyle; |
| } |
| |
| /** |
| * Returns true if this widget should be used in a barrier |
| */ |
| public boolean allowedInBarrier() { |
| return mVisibility != GONE; |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // Connections |
| /*-----------------------------------------------------------------------*/ |
| |
| /** |
| * Callback when a widget connects to us |
| * |
| * @param source |
| */ |
| public void connectedTo(ConstraintWidget source) { |
| // do nothing by default |
| } |
| |
| /** |
| * Immediate connection to an anchor without any checks. |
| * |
| * @param startType |
| * @param target |
| * @param endType |
| * @param margin |
| * @param goneMargin |
| */ |
| public void immediateConnect(ConstraintAnchor.Type startType, ConstraintWidget target, |
| ConstraintAnchor.Type endType, int margin, int goneMargin) { |
| ConstraintAnchor startAnchor = getAnchor(startType); |
| ConstraintAnchor endAnchor = target.getAnchor(endType); |
| startAnchor.connect(endAnchor, margin, goneMargin, ConstraintAnchor.Strength.STRONG, |
| ConstraintAnchor.USER_CREATOR, true); |
| } |
| |
| /** |
| * Connect the given anchors together (the from anchor should be owned by this widget) |
| * |
| * @param from the anchor we are connecting from (of this widget) |
| * @param to the anchor we are connecting to |
| * @param margin how much margin we want to have |
| * @param creator who created the connection |
| */ |
| public void connect(ConstraintAnchor from, ConstraintAnchor to, int margin, int creator) { |
| connect(from, to, margin, ConstraintAnchor.Strength.STRONG, creator); |
| } |
| |
| public void connect(ConstraintAnchor from, ConstraintAnchor to, int margin) { |
| connect(from, to, margin, ConstraintAnchor.Strength.STRONG, ConstraintAnchor.USER_CREATOR); |
| } |
| |
| public void connect(ConstraintAnchor from, ConstraintAnchor to, int margin, |
| ConstraintAnchor.Strength strength, int creator) { |
| if (from.getOwner() == this) { |
| connect(from.getType(), to.getOwner(), to.getType(), margin, strength, creator); |
| } |
| } |
| |
| /** |
| * Connect a given anchor of this widget to another anchor of a target widget |
| * |
| * @param constraintFrom which anchor of this widget to connect from |
| * @param target the target widget |
| * @param constraintTo the target anchor on the target widget |
| * @param margin how much margin we want to keep as a minimum distance between the two anchors |
| * @return the undo operation |
| */ |
| public void connect(ConstraintAnchor.Type constraintFrom, ConstraintWidget target, |
| ConstraintAnchor.Type constraintTo, int margin) { |
| connect(constraintFrom, target, constraintTo, margin, |
| ConstraintAnchor.Strength.STRONG); |
| } |
| |
| /** |
| * Connect a given anchor of this widget to another anchor of a target widget |
| * |
| * @param constraintFrom which anchor of this widget to connect from |
| * @param target the target widget |
| * @param constraintTo the target anchor on the target widget |
| * @return the undo operation |
| */ |
| public void connect(ConstraintAnchor.Type constraintFrom, |
| ConstraintWidget target, |
| ConstraintAnchor.Type constraintTo) { |
| connect(constraintFrom, target, constraintTo, 0, ConstraintAnchor.Strength.STRONG); |
| } |
| |
| |
| /** |
| * Connect a given anchor of this widget to another anchor of a target widget |
| * |
| * @param constraintFrom which anchor of this widget to connect from |
| * @param target the target widget |
| * @param constraintTo the target anchor on the target widget |
| * @param margin how much margin we want to keep as a minimum distance between the two anchors |
| * @param strength the constraint strength (Weak/Strong) |
| */ |
| public void connect(ConstraintAnchor.Type constraintFrom, |
| ConstraintWidget target, |
| ConstraintAnchor.Type constraintTo, int margin, |
| ConstraintAnchor.Strength strength) { |
| connect(constraintFrom, target, constraintTo, margin, strength, |
| ConstraintAnchor.USER_CREATOR); |
| } |
| |
| /** |
| * Connect a given anchor of this widget to another anchor of a target widget |
| * |
| * @param constraintFrom which anchor of this widget to connect from |
| * @param target the target widget |
| * @param constraintTo the target anchor on the target widget |
| * @param margin how much margin we want to keep as a minimum distance between the two anchors |
| * @param strength the constraint strength (Weak/Strong) |
| * @param creator who created the constraint |
| */ |
| public void connect(ConstraintAnchor.Type constraintFrom, |
| ConstraintWidget target, |
| ConstraintAnchor.Type constraintTo, int margin, |
| ConstraintAnchor.Strength strength, int creator) { |
| if (constraintFrom == ConstraintAnchor.Type.CENTER) { |
| // If we have center, we connect instead to the corresponding |
| // left/right or top/bottom pairs |
| if (constraintTo == ConstraintAnchor.Type.CENTER) { |
| ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); |
| ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); |
| ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); |
| ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); |
| boolean centerX = false; |
| boolean centerY = false; |
| if ((left != null && left.isConnected()) |
| || (right != null && right.isConnected())) { |
| // don't apply center here |
| } else { |
| connect(ConstraintAnchor.Type.LEFT, target, |
| ConstraintAnchor.Type.LEFT, 0, strength, creator); |
| connect(ConstraintAnchor.Type.RIGHT, target, |
| ConstraintAnchor.Type.RIGHT, 0, strength, creator); |
| centerX = true; |
| } |
| if ((top != null && top.isConnected()) |
| || (bottom != null && bottom.isConnected())) { |
| // don't apply center here |
| } else { |
| connect(ConstraintAnchor.Type.TOP, target, |
| ConstraintAnchor.Type.TOP, 0, strength, creator); |
| connect(ConstraintAnchor.Type.BOTTOM, target, |
| ConstraintAnchor.Type.BOTTOM, 0, strength, creator); |
| centerY = true; |
| } |
| if (centerX && centerY) { |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); |
| center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER), 0, creator); |
| } else if (centerX) { |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER_X); |
| center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER_X), 0, creator); |
| } else if (centerY) { |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER_Y); |
| center.connect(target.getAnchor(ConstraintAnchor.Type.CENTER_Y), 0, creator); |
| } |
| } else if ((constraintTo == ConstraintAnchor.Type.LEFT) |
| || (constraintTo == ConstraintAnchor.Type.RIGHT)) { |
| connect(ConstraintAnchor.Type.LEFT, target, |
| constraintTo, 0, strength, creator); |
| connect(ConstraintAnchor.Type.RIGHT, target, |
| constraintTo, 0, strength, creator); |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); |
| center.connect(target.getAnchor(constraintTo), 0, creator); |
| } else if ((constraintTo == ConstraintAnchor.Type.TOP) |
| || (constraintTo == ConstraintAnchor.Type.BOTTOM)) { |
| connect(ConstraintAnchor.Type.TOP, target, |
| constraintTo, 0, strength, creator); |
| connect(ConstraintAnchor.Type.BOTTOM, target, |
| constraintTo, 0, strength, creator); |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); |
| center.connect(target.getAnchor(constraintTo), 0, creator); |
| } |
| } else if (constraintFrom == ConstraintAnchor.Type.CENTER_X |
| && (constraintTo == ConstraintAnchor.Type.LEFT |
| || constraintTo == ConstraintAnchor.Type.RIGHT)) { |
| ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); |
| ConstraintAnchor targetAnchor = target.getAnchor(constraintTo); |
| ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); |
| left.connect(targetAnchor, 0, creator); |
| right.connect(targetAnchor, 0, creator); |
| ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); |
| centerX.connect(targetAnchor, 0, creator); |
| } else if (constraintFrom == ConstraintAnchor.Type.CENTER_Y |
| && (constraintTo == ConstraintAnchor.Type.TOP |
| || constraintTo == ConstraintAnchor.Type.BOTTOM)) { |
| ConstraintAnchor targetAnchor = target.getAnchor(constraintTo); |
| ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); |
| top.connect(targetAnchor, 0, creator); |
| ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); |
| bottom.connect(targetAnchor, 0, creator); |
| ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); |
| centerY.connect(targetAnchor, 0, creator); |
| } else if (constraintFrom == ConstraintAnchor.Type.CENTER_X |
| && constraintTo == ConstraintAnchor.Type.CENTER_X) { |
| // Center X connection will connect left & right |
| ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); |
| ConstraintAnchor leftTarget = target.getAnchor(ConstraintAnchor.Type.LEFT); |
| left.connect(leftTarget, 0, creator); |
| ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); |
| ConstraintAnchor rightTarget = target.getAnchor(ConstraintAnchor.Type.RIGHT); |
| right.connect(rightTarget, 0, creator); |
| ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); |
| centerX.connect(target.getAnchor(constraintTo), 0, creator); |
| } else if (constraintFrom == ConstraintAnchor.Type.CENTER_Y |
| && constraintTo == ConstraintAnchor.Type.CENTER_Y) { |
| // Center Y connection will connect top & bottom. |
| ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); |
| ConstraintAnchor topTarget = target.getAnchor(ConstraintAnchor.Type.TOP); |
| top.connect(topTarget, 0, creator); |
| ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); |
| ConstraintAnchor bottomTarget = target.getAnchor(ConstraintAnchor.Type.BOTTOM); |
| bottom.connect(bottomTarget, 0, creator); |
| ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); |
| centerY.connect(target.getAnchor(constraintTo), 0, creator); |
| } else { |
| ConstraintAnchor fromAnchor = getAnchor(constraintFrom); |
| ConstraintAnchor toAnchor = target.getAnchor(constraintTo); |
| if (fromAnchor.isValidConnection(toAnchor)) { |
| // make sure that the baseline takes precedence over top/bottom |
| // and reversely, reset the baseline if we are connecting top/bottom |
| if (constraintFrom == ConstraintAnchor.Type.BASELINE) { |
| ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); |
| ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); |
| if (top != null) { |
| top.reset(); |
| } |
| if (bottom != null) { |
| bottom.reset(); |
| } |
| margin = 0; |
| } else if ((constraintFrom == ConstraintAnchor.Type.TOP) |
| || (constraintFrom == ConstraintAnchor.Type.BOTTOM)) { |
| ConstraintAnchor baseline = getAnchor(ConstraintAnchor.Type.BASELINE); |
| if (baseline != null) { |
| baseline.reset(); |
| } |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); |
| if (center.getTarget() != toAnchor) { |
| center.reset(); |
| } |
| ConstraintAnchor opposite = getAnchor(constraintFrom).getOpposite(); |
| ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); |
| if (centerY.isConnected()) { |
| opposite.reset(); |
| centerY.reset(); |
| } else { |
| if (AUTOTAG_CENTER) { |
| // let's see if we need to mark center_y as connected |
| if (opposite.isConnected() && opposite.getTarget().getOwner() |
| == toAnchor.getOwner()) { |
| ConstraintAnchor targetCenterY = toAnchor.getOwner().getAnchor( |
| ConstraintAnchor.Type.CENTER_Y); |
| centerY.connect(targetCenterY, 0, creator); |
| } |
| } |
| } |
| } else if ((constraintFrom == ConstraintAnchor.Type.LEFT) |
| || (constraintFrom == ConstraintAnchor.Type.RIGHT)) { |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); |
| if (center.getTarget() != toAnchor) { |
| center.reset(); |
| } |
| ConstraintAnchor opposite = getAnchor(constraintFrom).getOpposite(); |
| ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); |
| if (centerX.isConnected()) { |
| opposite.reset(); |
| centerX.reset(); |
| } else { |
| if (AUTOTAG_CENTER) { |
| // let's see if we need to mark center_x as connected |
| if (opposite.isConnected() && opposite.getTarget().getOwner() |
| == toAnchor.getOwner()) { |
| ConstraintAnchor targetCenterX = toAnchor.getOwner().getAnchor( |
| ConstraintAnchor.Type.CENTER_X); |
| centerX.connect(targetCenterX, 0, creator); |
| } |
| } |
| } |
| |
| } |
| fromAnchor.connect(toAnchor, margin, strength, creator); |
| toAnchor.getOwner().connectedTo(fromAnchor.getOwner()); |
| } |
| } |
| } |
| |
| /** |
| * Reset all the constraints set on this widget |
| */ |
| public void resetAllConstraints() { |
| resetAnchors(); |
| setVerticalBiasPercent(DEFAULT_BIAS); |
| setHorizontalBiasPercent(DEFAULT_BIAS); |
| if (this instanceof ConstraintWidgetContainer) { |
| return; |
| } |
| if (getHorizontalDimensionBehaviour() == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { |
| if (getWidth() == getWrapWidth()) { |
| setHorizontalDimensionBehaviour(WRAP_CONTENT); |
| } else if (getWidth() > getMinWidth()) { |
| setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED); |
| } |
| } |
| if (getVerticalDimensionBehaviour() == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) { |
| if (getHeight() == getWrapHeight()) { |
| setVerticalDimensionBehaviour(WRAP_CONTENT); |
| } else if (getHeight() > getMinHeight()) { |
| setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED); |
| } |
| } |
| } |
| |
| /** |
| * Reset the given anchor |
| * |
| * @param anchor the anchor we want to reset |
| * @return the undo operation |
| */ |
| public void resetAnchor(ConstraintAnchor anchor) { |
| if (getParent() != null) { |
| if (getParent() instanceof ConstraintWidgetContainer) { |
| ConstraintWidgetContainer parent = (ConstraintWidgetContainer) getParent(); |
| if (parent.handlesInternalConstraints()) { |
| return; |
| } |
| } |
| } |
| ConstraintAnchor left = getAnchor(ConstraintAnchor.Type.LEFT); |
| ConstraintAnchor right = getAnchor(ConstraintAnchor.Type.RIGHT); |
| ConstraintAnchor top = getAnchor(ConstraintAnchor.Type.TOP); |
| ConstraintAnchor bottom = getAnchor(ConstraintAnchor.Type.BOTTOM); |
| ConstraintAnchor center = getAnchor(ConstraintAnchor.Type.CENTER); |
| ConstraintAnchor centerX = getAnchor(ConstraintAnchor.Type.CENTER_X); |
| ConstraintAnchor centerY = getAnchor(ConstraintAnchor.Type.CENTER_Y); |
| |
| if (anchor == center) { |
| if (left.isConnected() && right.isConnected() |
| && left.getTarget() == right.getTarget()) { |
| left.reset(); |
| right.reset(); |
| } |
| if (top.isConnected() && bottom.isConnected() |
| && top.getTarget() == bottom.getTarget()) { |
| top.reset(); |
| bottom.reset(); |
| } |
| mHorizontalBiasPercent = 0.5f; |
| mVerticalBiasPercent = 0.5f; |
| } else if (anchor == centerX) { |
| if (left.isConnected() && right.isConnected() |
| && left.getTarget().getOwner() == right.getTarget().getOwner()) { |
| left.reset(); |
| right.reset(); |
| } |
| mHorizontalBiasPercent = 0.5f; |
| } else if (anchor == centerY) { |
| if (top.isConnected() && bottom.isConnected() |
| && top.getTarget().getOwner() == bottom.getTarget().getOwner()) { |
| top.reset(); |
| bottom.reset(); |
| } |
| mVerticalBiasPercent = 0.5f; |
| } else if (anchor == left || anchor == right) { |
| if (left.isConnected() && left.getTarget() == right.getTarget()) { |
| center.reset(); |
| } |
| } else if (anchor == top || anchor == bottom) { |
| if (top.isConnected() && top.getTarget() == bottom.getTarget()) { |
| center.reset(); |
| } |
| } |
| anchor.reset(); |
| } |
| |
| /** |
| * Reset all connections |
| */ |
| public void resetAnchors() { |
| ConstraintWidget parent = getParent(); |
| if (parent != null && parent instanceof ConstraintWidgetContainer) { |
| ConstraintWidgetContainer parentContainer = (ConstraintWidgetContainer) getParent(); |
| if (parentContainer.handlesInternalConstraints()) { |
| return; |
| } |
| } |
| for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) { |
| final ConstraintAnchor anchor = mAnchors.get(i); |
| anchor.reset(); |
| } |
| } |
| |
| /** |
| * Reset all connections that have this connectCreator |
| */ |
| public void resetAnchors(int connectionCreator) { |
| ConstraintWidget parent = getParent(); |
| if (parent != null && parent instanceof ConstraintWidgetContainer) { |
| ConstraintWidgetContainer parentContainer = (ConstraintWidgetContainer) getParent(); |
| if (parentContainer.handlesInternalConstraints()) { |
| return; |
| } |
| } |
| for (int i = 0, mAnchorsSize = mAnchors.size(); i < mAnchorsSize; i++) { |
| final ConstraintAnchor anchor = mAnchors.get(i); |
| if (connectionCreator == anchor.getConnectionCreator()) { |
| if (anchor.isVerticalAnchor()) { |
| setVerticalBiasPercent(ConstraintWidget.DEFAULT_BIAS); |
| } else { |
| setHorizontalBiasPercent(ConstraintWidget.DEFAULT_BIAS); |
| } |
| anchor.reset(); |
| } |
| } |
| } |
| |
| /** |
| * Disconnect this widget if we have a connection to it |
| * |
| * @param widget the widget we are removing |
| */ |
| public void disconnectWidget(ConstraintWidget widget) { |
| final ArrayList<ConstraintAnchor> anchors = getAnchors(); |
| for (int i = 0, anchorsSize = anchors.size(); i < anchorsSize; i++) { |
| final ConstraintAnchor anchor = anchors.get(i); |
| if (anchor.isConnected() && (anchor.getTarget().getOwner() == widget)) { |
| anchor.reset(); |
| } |
| } |
| } |
| |
| /** |
| * Disconnect this widget if we have a auto connection to it |
| * |
| * @param widget the widget we are removing |
| */ |
| public void disconnectUnlockedWidget(ConstraintWidget widget) { |
| final ArrayList<ConstraintAnchor> anchors = getAnchors(); |
| for (int i = 0, anchorsSize = anchors.size(); i < anchorsSize; i++) { |
| final ConstraintAnchor anchor = anchors.get(i); |
| if (anchor.isConnected() && (anchor.getTarget().getOwner() == widget) |
| && anchor.getConnectionCreator() == ConstraintAnchor.AUTO_CONSTRAINT_CREATOR) { |
| anchor.reset(); |
| } |
| } |
| } |
| |
| /** |
| * Given a type of anchor, returns the corresponding anchor. |
| * |
| * @param anchorType type of the anchor (LEFT, TOP, RIGHT, BOTTOM, BASELINE, CENTER_X, CENTER_Y) |
| * @return the matching anchor |
| */ |
| public ConstraintAnchor getAnchor(ConstraintAnchor.Type anchorType) { |
| switch (anchorType) { |
| case LEFT: { |
| return mLeft; |
| } |
| case TOP: { |
| return mTop; |
| } |
| case RIGHT: { |
| return mRight; |
| } |
| case BOTTOM: { |
| return mBottom; |
| } |
| case BASELINE: { |
| return mBaseline; |
| } |
| case CENTER_X: { |
| return mCenterX; |
| } |
| case CENTER_Y: { |
| return mCenterY; |
| } |
| case CENTER: { |
| return mCenter; |
| } |
| case NONE: |
| return null; |
| } |
| throw new AssertionError(anchorType.name()); |
| } |
| |
| /** |
| * Accessor for the horizontal dimension behaviour |
| * |
| * @return dimension behaviour |
| */ |
| public DimensionBehaviour getHorizontalDimensionBehaviour() { |
| return mListDimensionBehaviors[DIMENSION_HORIZONTAL]; |
| } |
| |
| /** |
| * Accessor for the vertical dimension behaviour |
| * |
| * @return dimension behaviour |
| */ |
| public DimensionBehaviour getVerticalDimensionBehaviour() { |
| return mListDimensionBehaviors[DIMENSION_VERTICAL]; |
| } |
| |
| /** |
| * 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 |
| */ |
| public void setHorizontalDimensionBehaviour(DimensionBehaviour behaviour) { |
| mListDimensionBehaviors[DIMENSION_HORIZONTAL] = behaviour; |
| if (behaviour == WRAP_CONTENT) { |
| setWidth(mWrapWidth); |
| } |
| } |
| |
| /** |
| * Set the widget's behaviour for the vertical dimension |
| * |
| * @param behaviour the vertical dimension's behaviour |
| */ |
| public void setVerticalDimensionBehaviour(DimensionBehaviour behaviour) { |
| mListDimensionBehaviors[DIMENSION_VERTICAL] = behaviour; |
| if (behaviour == WRAP_CONTENT) { |
| setHeight(mWrapHeight); |
| } |
| } |
| |
| /** |
| * test if you are in a Horizontal chain |
| * |
| * @return |
| */ |
| public boolean isInHorizontalChain() { |
| if ((mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft) |
| || (mRight.mTarget != null && mRight.mTarget.mTarget == mRight)) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * if in a horizontal chain return the left most widget in the chain. |
| * |
| * @return left most widget in chain or null |
| */ |
| public ConstraintWidget getHorizontalChainControlWidget() { |
| ConstraintWidget found = null; |
| if (isInHorizontalChain()) { |
| ConstraintWidget tmp = this; |
| |
| while (found == null && tmp != null) { |
| ConstraintAnchor anchor = tmp.getAnchor(ConstraintAnchor.Type.LEFT); |
| ConstraintAnchor targetOwner = (anchor == null) ? null : anchor.getTarget(); |
| ConstraintWidget target = (targetOwner == null) ? null : targetOwner.getOwner(); |
| if (target == getParent()) { |
| found = tmp; |
| break; |
| } |
| ConstraintAnchor targetAnchor = (target == null) ? null : target.getAnchor(ConstraintAnchor.Type.RIGHT).getTarget(); |
| if (targetAnchor != null && targetAnchor.getOwner() != tmp) { |
| found = tmp; |
| } else { |
| tmp = target; |
| } |
| } |
| } |
| return found; |
| } |
| |
| |
| /** |
| * test if you are in a vertical chain |
| * |
| * @return |
| */ |
| public boolean isInVerticalChain() { |
| if ((mTop.mTarget != null && mTop.mTarget.mTarget == mTop) |
| || (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom)) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * return the top most widget in the control chain |
| * |
| * @return |
| */ |
| public ConstraintWidget getVerticalChainControlWidget() { |
| ConstraintWidget found = null; |
| if (isInVerticalChain()) { |
| ConstraintWidget tmp = this; |
| while (found == null && tmp != null) { |
| ConstraintAnchor anchor = tmp.getAnchor(ConstraintAnchor.Type.TOP); |
| ConstraintAnchor targetOwner = (anchor == null) ? null : anchor.getTarget(); |
| ConstraintWidget target = (targetOwner == null) ? null : targetOwner.getOwner(); |
| if (target == getParent()) { |
| found = tmp; |
| break; |
| } |
| ConstraintAnchor targetAnchor = (target == null) ? null : target.getAnchor(ConstraintAnchor.Type.BOTTOM).getTarget(); |
| if (targetAnchor != null && targetAnchor.getOwner() != tmp) { |
| found = tmp; |
| } else { |
| tmp = target; |
| } |
| } |
| |
| } |
| 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 |
| /*-----------------------------------------------------------------------*/ |
| |
| /** |
| * Add this widget to the solver |
| * |
| * @param system the solver we want to add the widget to |
| */ |
| public void addToSolver(LinearSystem system) { |
| if (LinearSystem.FULL_DEBUG) { |
| System.out.println("\n----------------------------------------------"); |
| System.out.println("-- adding " + getDebugName() + " to the solver"); |
| System.out.println("----------------------------------------------\n"); |
| } |
| |
| SolverVariable left = system.createObjectVariable(mLeft); |
| SolverVariable right = system.createObjectVariable(mRight); |
| SolverVariable top = system.createObjectVariable(mTop); |
| SolverVariable bottom = system.createObjectVariable(mBottom); |
| SolverVariable baseline = system.createObjectVariable(mBaseline); |
| |
| boolean inHorizontalChain = false; |
| boolean inVerticalChain = false; |
| boolean horizontalParentWrapContent = false; |
| boolean verticalParentWrapContent = false; |
| |
| if (mParent != null) { |
| horizontalParentWrapContent = mParent != null ? mParent.mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT : false; |
| verticalParentWrapContent = mParent != null ? mParent.mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT : false; |
| |
| // Add this widget to a horizontal chain if it is the Head of it. |
| if (isChainHead(HORIZONTAL)) { |
| ((ConstraintWidgetContainer) mParent).addChain(this, HORIZONTAL); |
| inHorizontalChain = true; |
| } else { |
| inHorizontalChain = isInHorizontalChain(); |
| } |
| |
| // Add this widget to a vertical chain if it is the Head of it. |
| if (isChainHead(VERTICAL)) { |
| ((ConstraintWidgetContainer) mParent).addChain(this, VERTICAL); |
| inVerticalChain = true; |
| } else { |
| inVerticalChain = isInVerticalChain(); |
| } |
| |
| if (horizontalParentWrapContent && mVisibility != GONE |
| && 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) { |
| SolverVariable parentBottom = system.createObjectVariable(mParent.mBottom); |
| system.addGreaterThan(parentBottom, bottom, 0, SolverVariable.STRENGTH_LOW); |
| } |
| } |
| |
| int width = mWidth; |
| if (width < mMinWidth) { |
| width = mMinWidth; |
| } |
| int height = mHeight; |
| if (height < mMinHeight) { |
| height = mMinHeight; |
| } |
| |
| // 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; |
| |
| // We evaluate the dimension ratio here as the connections can change. |
| // TODO: have a validation pass after connection instead |
| boolean useRatio = false; |
| mResolvedDimensionRatioSide = mDimensionRatioSide; |
| mResolvedDimensionRatio = mDimensionRatio; |
| |
| int matchConstraintDefaultWidth = mMatchConstraintDefaultWidth; |
| int matchConstraintDefaultHeight = mMatchConstraintDefaultHeight; |
| |
| if (mDimensionRatio > 0 && mVisibility != GONE) { |
| useRatio = true; |
| if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.MATCH_CONSTRAINT |
| && matchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) { |
| matchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO; |
| } |
| if (mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.MATCH_CONSTRAINT |
| && matchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) { |
| matchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO; |
| } |
| |
| if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.MATCH_CONSTRAINT |
| && mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.MATCH_CONSTRAINT |
| && matchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO |
| && matchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) { |
| setupDimensionRatio(horizontalParentWrapContent, verticalParentWrapContent, horizontalDimensionFixed, verticalDimensionFixed); |
| } else if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.MATCH_CONSTRAINT |
| && matchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) { |
| mResolvedDimensionRatioSide = HORIZONTAL; |
| width = (int) (mResolvedDimensionRatio * mHeight); |
| if (mListDimensionBehaviors[DIMENSION_VERTICAL] != DimensionBehaviour.MATCH_CONSTRAINT) { |
| matchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO_RESOLVED; |
| useRatio = false; |
| } |
| } else if (mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.MATCH_CONSTRAINT |
| && matchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) { |
| mResolvedDimensionRatioSide = VERTICAL; |
| if (mDimensionRatioSide == UNKNOWN) { |
| // need to reverse the ratio as the parsing is done in horizontal mode |
| mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; |
| } |
| height = (int) (mResolvedDimensionRatio * mWidth); |
| if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] != DimensionBehaviour.MATCH_CONSTRAINT) { |
| matchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO_RESOLVED; |
| useRatio = false; |
| } |
| } |
| } |
| |
| mResolvedMatchConstraintDefault[HORIZONTAL] = matchConstraintDefaultWidth; |
| mResolvedMatchConstraintDefault[VERTICAL] = matchConstraintDefaultHeight; |
| |
| boolean useHorizontalRatio = useRatio && (mResolvedDimensionRatioSide == HORIZONTAL |
| || mResolvedDimensionRatioSide == UNKNOWN); |
| |
| // Horizontal resolution |
| boolean wrapContent = (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT) |
| && (this instanceof ConstraintWidgetContainer); |
| |
| boolean applyPosition = true; |
| if (mCenter.isConnected()) { |
| applyPosition = false; |
| } |
| |
| if (mHorizontalResolution != DIRECT) { |
| SolverVariable parentMax = mParent != null ? system.createObjectVariable(mParent.mRight) : null; |
| SolverVariable parentMin = mParent != null ? system.createObjectVariable(mParent.mLeft) : null; |
| applyConstraints(system, horizontalParentWrapContent, parentMin, parentMax, mListDimensionBehaviors[DIMENSION_HORIZONTAL], wrapContent, |
| mLeft, mRight, mX, width, |
| mMinWidth, mMaxDimension[HORIZONTAL], mHorizontalBiasPercent, useHorizontalRatio, |
| inHorizontalChain, matchConstraintDefaultWidth, mMatchConstraintMinWidth, mMatchConstraintMaxWidth, mMatchConstraintPercentWidth, applyPosition); |
| } |
| |
| if (mVerticalResolution == DIRECT) { |
| if (LinearSystem.FULL_DEBUG) { |
| System.out.println("\n----------------------------------------------"); |
| System.out.println("-- DONE adding " + getDebugName() + " to the solver"); |
| System.out.println("-- SKIP VERTICAL RESOLUTION"); |
| System.out.println("----------------------------------------------\n"); |
| } |
| return; |
| } |
| // Vertical Resolution |
| wrapContent = (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT) |
| && (this instanceof ConstraintWidgetContainer); |
| |
| boolean useVerticalRatio = useRatio && (mResolvedDimensionRatioSide == VERTICAL |
| || mResolvedDimensionRatioSide == UNKNOWN); |
| |
| if (mBaselineDistance > 0) { |
| if (mBaseline.getResolutionNode().state == ResolutionAnchor.RESOLVED) { |
| mBaseline.getResolutionNode().addResolvedValue(system); |
| } else { |
| system.addEquality(baseline, top, getBaselineDistance(), SolverVariable.STRENGTH_FIXED); |
| if (mBaseline.mTarget != null) { |
| SolverVariable baselineTarget = system.createObjectVariable(mBaseline.mTarget); |
| int baselineMargin = 0; // for now at least, baseline don't have margins |
| system.addEquality(baseline, baselineTarget, baselineMargin, SolverVariable.STRENGTH_FIXED); |
| applyPosition = false; |
| } |
| } |
| } |
| SolverVariable parentMax = mParent != null ? system.createObjectVariable(mParent.mBottom) : null; |
| SolverVariable parentMin = mParent != null ? system.createObjectVariable(mParent.mTop) : null; |
| applyConstraints(system, verticalParentWrapContent, parentMin, parentMax, mListDimensionBehaviors[DIMENSION_VERTICAL], |
| wrapContent, mTop, mBottom, mY, height, |
| mMinHeight, mMaxDimension[VERTICAL], mVerticalBiasPercent, useVerticalRatio, |
| inVerticalChain, matchConstraintDefaultHeight, mMatchConstraintMinHeight, mMatchConstraintMaxHeight, mMatchConstraintPercentHeight, applyPosition); |
| |
| if (useRatio) { |
| int strength = SolverVariable.STRENGTH_FIXED; |
| if (mResolvedDimensionRatioSide == VERTICAL) { |
| system.addRatio(bottom, top, right, left, mResolvedDimensionRatio, strength); |
| } else { |
| system.addRatio(right, left, bottom, top, mResolvedDimensionRatio, strength); |
| } |
| } |
| |
| if (mCenter.isConnected()) { |
| system.addCenterPoint(this, mCenter.getTarget().getOwner(), (float) Math.toRadians(mCircleConstraintAngle + 90), mCenter.getMargin()); |
| } |
| |
| if (LinearSystem.FULL_DEBUG) { |
| System.out.println("\n----------------------------------------------"); |
| System.out.println("-- DONE adding " + getDebugName() + " to the solver"); |
| System.out.println("----------------------------------------------\n"); |
| } |
| } |
| |
| /** |
| * Resolves the dimension ratio parameters |
| * (mResolvedDimensionRatioSide & mDimensionRatio) |
| * |
| * @param hparentWrapContent true if parent is in wrap content horizontally |
| * @param vparentWrapContent true if parent is in wrap content vertically |
| * @param horizontalDimensionFixed true if this widget horizontal dimension is fixed |
| * @param verticalDimensionFixed true if this widget vertical dimension is fixed |
| */ |
| public void setupDimensionRatio(boolean hparentWrapContent, boolean vparentWrapContent, boolean horizontalDimensionFixed, boolean verticalDimensionFixed) { |
| if (mResolvedDimensionRatioSide == UNKNOWN) { |
| if (horizontalDimensionFixed && !verticalDimensionFixed) { |
| mResolvedDimensionRatioSide = HORIZONTAL; |
| } else if (!horizontalDimensionFixed && verticalDimensionFixed) { |
| mResolvedDimensionRatioSide = VERTICAL; |
| if (mDimensionRatioSide == UNKNOWN) { |
| // need to reverse the ratio as the parsing is done in horizontal mode |
| mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; |
| } |
| } |
| } |
| |
| if (mResolvedDimensionRatioSide == HORIZONTAL && !(mTop.isConnected() && mBottom.isConnected())) { |
| mResolvedDimensionRatioSide = VERTICAL; |
| } else if (mResolvedDimensionRatioSide == VERTICAL && !(mLeft.isConnected() && mRight.isConnected())) { |
| mResolvedDimensionRatioSide = HORIZONTAL; |
| } |
| |
| // if dimension is still unknown... check parentWrap |
| if (mResolvedDimensionRatioSide == UNKNOWN) { |
| if (!(mTop.isConnected() && mBottom.isConnected() |
| && mLeft.isConnected() && mRight.isConnected())) { |
| // only do that if not all connections are set |
| if (mTop.isConnected() && mBottom.isConnected()) { |
| mResolvedDimensionRatioSide = HORIZONTAL; |
| } else if (mLeft.isConnected() && mRight.isConnected()) { |
| mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; |
| mResolvedDimensionRatioSide = VERTICAL; |
| } |
| } |
| } |
| |
| if (mResolvedDimensionRatioSide == UNKNOWN) { |
| if (hparentWrapContent && !vparentWrapContent) { |
| mResolvedDimensionRatioSide = HORIZONTAL; |
| } else if (!hparentWrapContent && vparentWrapContent) { |
| mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; |
| mResolvedDimensionRatioSide = VERTICAL; |
| } |
| } |
| |
| if (mResolvedDimensionRatioSide == UNKNOWN) { |
| if (mMatchConstraintMinWidth > 0 && mMatchConstraintMinHeight == 0) { |
| mResolvedDimensionRatioSide = HORIZONTAL; |
| } else if (mMatchConstraintMinWidth == 0 && mMatchConstraintMinHeight > 0) { |
| mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; |
| mResolvedDimensionRatioSide = VERTICAL; |
| } |
| } |
| |
| if (mResolvedDimensionRatioSide == UNKNOWN && hparentWrapContent && vparentWrapContent) { |
| mResolvedDimensionRatio = 1 / mResolvedDimensionRatio; |
| mResolvedDimensionRatioSide = VERTICAL; |
| } |
| } |
| |
| /** |
| * Apply the constraints in the system depending on the existing anchors, in one dimension |
| * @param system the linear system we are adding constraints to |
| * @param parentWrapContent |
| * @param parentMax |
| * @param dimensionBehaviour |
| * @param wrapContent is the widget trying to wrap its content (i.e. its size will depends on its content) |
| * @param beginAnchor the first anchor |
| * @param endAnchor the second anchor |
| * @param beginPosition the original position of the anchor |
| * @param dimension the dimension |
| * @param maxDimension |
| * @param matchPercentDimension the percentage relative to the parent, applied if in match constraint and percent mode |
| * @param applyPosition |
| */ |
| private void applyConstraints(LinearSystem system, boolean parentWrapContent, SolverVariable parentMin, SolverVariable parentMax, |
| DimensionBehaviour dimensionBehaviour, boolean wrapContent, |
| ConstraintAnchor beginAnchor, ConstraintAnchor endAnchor, |
| int beginPosition, int dimension, int minDimension, |
| int maxDimension, float bias, boolean useRatio, boolean inChain, int matchConstraintDefault, |
| int matchMinDimension, int matchMaxDimension, float matchPercentDimension, boolean applyPosition) { |
| |
| SolverVariable begin = system.createObjectVariable(beginAnchor); |
| SolverVariable end = system.createObjectVariable(endAnchor); |
| SolverVariable beginTarget = system.createObjectVariable(beginAnchor.getTarget()); |
| SolverVariable endTarget = system.createObjectVariable(endAnchor.getTarget()); |
| |
| if (system.graphOptimizer) { |
| if (beginAnchor.getResolutionNode().state == ResolutionAnchor.RESOLVED |
| && endAnchor.getResolutionNode().state == ResolutionAnchor.RESOLVED) { |
| if (system.getMetrics() != null) { |
| system.getMetrics().resolvedWidgets++; |
| } |
| beginAnchor.getResolutionNode().addResolvedValue(system); |
| endAnchor.getResolutionNode().addResolvedValue(system); |
| if (!inChain && parentWrapContent) { |
| system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_FIXED); |
| } |
| return; |
| } |
| } |
| if (system.getMetrics() != null) { |
| system.getMetrics().nonresolvedWidgets++; |
| } |
| |
| boolean isBeginConnected = beginAnchor.isConnected(); |
| boolean isEndConnected = endAnchor.isConnected(); |
| boolean isCenterConnected = mCenter.isConnected(); |
| |
| boolean variableSize = false; |
| |
| int numConnections = 0; |
| if (isBeginConnected) { numConnections++; } |
| if (isEndConnected) { numConnections++; } |
| if (isCenterConnected) { numConnections++; } |
| |
| if (useRatio) { |
| matchConstraintDefault = MATCH_CONSTRAINT_RATIO; |
| } |
| switch (dimensionBehaviour) { |
| case FIXED: { |
| variableSize = false; |
| } break; |
| case WRAP_CONTENT: { |
| variableSize = false; |
| } break; |
| case MATCH_PARENT: { |
| variableSize = false; |
| } break; |
| case MATCH_CONSTRAINT: { |
| variableSize = true; |
| if (matchConstraintDefault == MATCH_CONSTRAINT_RATIO_RESOLVED) { |
| variableSize = false; |
| } |
| } break; |
| } |
| |
| if (mVisibility == ConstraintWidget.GONE) { |
| dimension = 0; |
| variableSize = false; |
| } |
| |
| // First apply starting direct connections (more solver-friendly) |
| if (applyPosition) { |
| if (!isBeginConnected && !isEndConnected && !isCenterConnected) { |
| system.addEquality(begin, beginPosition); |
| } else if (isBeginConnected && !isEndConnected) { |
| system.addEquality(begin, beginTarget, beginAnchor.getMargin(), SolverVariable.STRENGTH_FIXED); |
| } |
| } |
| |
| // Then apply the dimension |
| if (!variableSize) { |
| if (wrapContent) { |
| system.addEquality(end, begin, 0, SolverVariable.STRENGTH_HIGH); |
| if (minDimension > 0) { |
| system.addGreaterThan(end, begin, minDimension, SolverVariable.STRENGTH_FIXED); |
| } |
| if (maxDimension < Integer.MAX_VALUE) { |
| system.addLowerThan(end, begin, maxDimension, SolverVariable.STRENGTH_FIXED); |
| } |
| } else { |
| system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_FIXED); |
| } |
| } else { |
| if (matchMinDimension == WRAP) { |
| matchMinDimension = dimension; |
| } |
| if (matchMaxDimension == WRAP) { |
| matchMaxDimension = dimension; |
| } |
| |
| if (matchMinDimension > 0) { |
| system.addGreaterThan(end, begin, matchMinDimension, SolverVariable.STRENGTH_FIXED); |
| dimension = Math.max(dimension, matchMinDimension); |
| } |
| if (matchMaxDimension > 0) { |
| system.addLowerThan(end, begin, matchMaxDimension, SolverVariable.STRENGTH_FIXED); |
| dimension = Math.min(dimension, matchMaxDimension); |
| } |
| if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) { |
| if (parentWrapContent) { |
| system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_FIXED); |
| } else if (inChain) { |
| system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_HIGHEST); |
| } else { |
| system.addEquality(end, begin, dimension, SolverVariable.STRENGTH_LOW); |
| } |
| } else if (matchConstraintDefault == MATCH_CONSTRAINT_PERCENT) { |
| SolverVariable percentBegin = null; |
| SolverVariable percentEnd = null; |
| if (beginAnchor.getType() == ConstraintAnchor.Type.TOP || beginAnchor.getType() == ConstraintAnchor.Type.BOTTOM) { |
| // vertical |
| percentBegin = system.createObjectVariable(mParent.getAnchor(ConstraintAnchor.Type.TOP)); |
| percentEnd = system.createObjectVariable(mParent.getAnchor(ConstraintAnchor.Type.BOTTOM)); |
| } else { |
| percentBegin = system.createObjectVariable(mParent.getAnchor(ConstraintAnchor.Type.LEFT)); |
| percentEnd = system.createObjectVariable(mParent.getAnchor(ConstraintAnchor.Type.RIGHT)); |
| } |
| system.addConstraint(system.createRow().createRowDimensionRatio(end, begin, percentEnd, percentBegin, matchPercentDimension)); |
| variableSize = false; |
| } |
| |
| if (variableSize && numConnections != 2 && !useRatio) { |
| variableSize = false; |
| int d = Math.max(matchMinDimension, dimension); |
| if (matchMaxDimension > 0) { |
| d = Math.min(matchMaxDimension, d); |
| } |
| system.addEquality(end, begin, d, SolverVariable.STRENGTH_FIXED); |
| } |
| } |
| |
| if (!applyPosition || inChain) { |
| // If we don't need to apply the position, let's finish now. |
| if (LinearSystem.FULL_DEBUG) { |
| System.out.println("only deal with dimension for " + mDebugName |
| + ", not positioning (applyPosition: " + applyPosition + " inChain: " + inChain + ")"); |
| } |
| if (numConnections < 2 && parentWrapContent) { |
| system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_FIXED); |
| system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_FIXED); |
| } |
| return; |
| } |
| |
| // Ok, we are dealing with single or centered constraints, let's apply them |
| |
| if (!isBeginConnected && !isEndConnected && !isCenterConnected) { |
| // note we already applied the start position before, no need to redo it... |
| if (parentWrapContent) { |
| system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_EQUALITY); |
| } |
| } else if (isBeginConnected && !isEndConnected) { |
| // note we already applied the start position before, no need to redo it... |
| if (parentWrapContent) { |
| system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_EQUALITY); |
| } |
| } else if (!isBeginConnected && isEndConnected) { |
| system.addEquality(end, endTarget, -endAnchor.getMargin(), SolverVariable.STRENGTH_FIXED); |
| if (parentWrapContent) { |
| system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_EQUALITY); |
| } |
| } else if (isBeginConnected && isEndConnected) { |
| |
| // Ok, we have a centered connection, let's deal with it |
| |
| boolean applyBoundsCheck = false; |
| boolean applyCentering = false; |
| int centeringStrength = SolverVariable.STRENGTH_EQUALITY; |
| |
| if (variableSize) { |
| |
| if (parentWrapContent && minDimension == 0) { |
| system.addGreaterThan(end, begin, 0, SolverVariable.STRENGTH_FIXED); |
| } |
| |
| if (matchConstraintDefault == MATCH_CONSTRAINT_SPREAD) { |
| int strength = SolverVariable.STRENGTH_FIXED; |
| if (matchMaxDimension > 0 || matchMinDimension > 0) { |
| strength = SolverVariable.STRENGTH_HIGHEST; |
| applyBoundsCheck = true; |
| } |
| system.addEquality(begin, beginTarget, beginAnchor.getMargin(), strength); |
| system.addEquality(end, endTarget, -endAnchor.getMargin(), strength); |
| if (matchMaxDimension > 0 || matchMinDimension > 0) { |
| applyCentering = true; |
| } |
| } else if (matchConstraintDefault == MATCH_CONSTRAINT_WRAP) { |
| applyCentering = true; |
| applyBoundsCheck = true; |
| centeringStrength = SolverVariable.STRENGTH_FIXED; |
| } else if (matchConstraintDefault == MATCH_CONSTRAINT_RATIO) { |
| applyCentering = true; |
| applyBoundsCheck = true; |
| int strength = SolverVariable.STRENGTH_HIGHEST; |
| if (!useRatio && mResolvedDimensionRatioSide != UNKNOWN && matchMaxDimension <= 0) { |
| // useRatio is true if the side we base ourselves on for the ratio is this one |
| // if that's not the case, we need to have a stronger constraint. |
| strength = SolverVariable.STRENGTH_FIXED; |
| } |
| system.addEquality(begin, beginTarget, beginAnchor.getMargin(), strength); |
| system.addEquality(end, endTarget, -endAnchor.getMargin(), strength); |
| } |
| |
| } else { |
| applyCentering = true; |
| } |
| |
| 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) { |
| 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) { |
| system.addGreaterThan(begin, parentMin, 0, SolverVariable.STRENGTH_FIXED); |
| } |
| } |
| |
| if (parentWrapContent) { |
| system.addGreaterThan(parentMax, end, 0, SolverVariable.STRENGTH_FIXED); |
| } |
| } |
| |
| /** |
| * Update the widget from the values generated by the solver |
| * |
| * @param system the solver we get the values from. |
| */ |
| public void updateFromSolver(LinearSystem system) { |
| int left = system.getObjectVariableValue(mLeft); |
| int top = system.getObjectVariableValue(mTop); |
| int right = system.getObjectVariableValue(mRight); |
| int bottom = system.getObjectVariableValue(mBottom); |
| int w = right - left; |
| int h = bottom - top; |
| if (w < 0 || h < 0 |
| || left == Integer.MIN_VALUE || left == Integer.MAX_VALUE |
| || top == Integer.MIN_VALUE || top == Integer.MAX_VALUE |
| || right == Integer.MIN_VALUE || right == Integer.MAX_VALUE |
| || bottom == Integer.MIN_VALUE || bottom == Integer.MAX_VALUE) { |
| left = 0; |
| top = 0; |
| right = 0; |
| bottom = 0; |
| } |
| setFrame(left, top, right, bottom); |
| } |
| } |