| /* |
| * 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; |
| |
| /** |
| * 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 implements Solvable { |
| private static final boolean AUTOTAG_CENTER = false; |
| |
| private Animator mAnimator = new Animator(this); |
| |
| public final static int VISIBLE = 0; |
| public final static int INVISIBLE = 4; |
| public final static int GONE = 8; |
| |
| int mDistToTop; |
| int mDistToLeft; |
| int mDistToRight; |
| int mDistToBottom; |
| boolean mVisited; |
| |
| /** |
| * 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, ANY |
| } |
| |
| // 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 ArrayList<ConstraintAnchor> mAnchors = new ArrayList<>(); |
| |
| // Parent of this widget |
| ConstraintWidget mParent = null; |
| |
| // Dimensions of the widget |
| private int mWidth = 0; |
| private int mHeight = 0; |
| private float mDimensionRatio = 0; |
| |
| private int mSolverLeft = 0; |
| private int mSolverTop = 0; |
| private int mSolverRight = 0; |
| private int mSolverBottom = 0; |
| |
| // Origin of the widget |
| protected int mX = 0; |
| protected int mY = 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 |
| private int mBaselineDistance = 0; |
| |
| // Minimum sizes for the widget |
| private int mMinWidth; |
| private 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; |
| private float mHorizontalBiasPercent = DEFAULT_BIAS; |
| private float mVerticalBiasPercent = DEFAULT_BIAS; |
| |
| // The horizontal and vertical behaviour for the widgets' dimensions |
| private DimensionBehaviour mHorizontalDimensionBehaviour = DimensionBehaviour.FIXED; |
| private DimensionBehaviour mVerticalDimensionBehaviour = DimensionBehaviour.FIXED; |
| |
| // 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; |
| |
| // 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; |
| mWidth = 0; |
| mHeight = 0; |
| mDimensionRatio = 0.0f; |
| 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; |
| mHorizontalDimensionBehaviour = DimensionBehaviour.FIXED; |
| mVerticalDimensionBehaviour = DimensionBehaviour.FIXED; |
| mCompanionWidget = null; |
| mContainerItemSkip = 0; |
| mVisibility = VISIBLE; |
| mDebugName = null; |
| mType = null; |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // 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); |
| } |
| |
| /** |
| * Reset the anchors' group |
| */ |
| public void resetGroups() { |
| final int numAnchors = mAnchors.size(); |
| for (int i = 0; i < numAnchors; i++) { |
| mAnchors.get(i).mGroup = ConstraintAnchor.ANY_GROUP; |
| } |
| } |
| |
| /** |
| * 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); |
| if (ConstraintAnchor.USE_CENTER_ANCHOR) { |
| 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; |
| } |
| |
| /** |
| * 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 |
| */ |
| @Override |
| 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"); |
| } |
| } |
| |
| /** |
| * Returns true if the widget is animating |
| * |
| * @return |
| */ |
| public boolean isAnimating() { |
| if (Animator.doAnimation()) { |
| return mAnimator.isAnimating(); |
| } |
| return false; |
| } |
| |
| /** |
| * 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 + ")"; |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // 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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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 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; |
| } |
| |
| /** |
| * 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; |
| if (Animator.doAnimation()) { |
| mAnimator.animate(left, top, right, bottom); |
| left = mAnimator.getCurrentLeft(); |
| top = mAnimator.getCurrentTop(); |
| right = mAnimator.getCurrentRight(); |
| bottom = mAnimator.getCurrentBottom(); |
| } |
| 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 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.ANY -- the dimension's value will be set to the other dimension * ratio. |
| */ |
| public void setDimensionRatio(float ratio) { |
| mDimensionRatio = ratio; |
| } |
| |
| /** |
| * Return the current ratio of this widget |
| * @return the dimension ratio |
| */ |
| public float getDimensionRatio() { |
| return mDimensionRatio; |
| } |
| |
| /** |
| * 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) { |
| mMinWidth = w; |
| } |
| |
| /** |
| * Set the minimum height of the widget |
| * |
| * @param h minimum height |
| */ |
| public void setMinHeight(int h) { |
| 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; |
| // correct dimensional instability caused by rounding errors |
| if (getHorizontalDimensionBehaviour() == DimensionBehaviour.FIXED) { |
| if (w < getWidth()) { |
| w = getWidth(); |
| } |
| } |
| if (getVerticalDimensionBehaviour() == DimensionBehaviour.FIXED) { |
| if (h < getHeight()) { |
| h = getHeight(); |
| } |
| } |
| mX = left; |
| mY = top; |
| setDimension(w, h); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // 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 |
| */ |
| public void immediateConnect(ConstraintAnchor.Type startType, ConstraintWidget target, |
| ConstraintAnchor.Type endType, int margin) { |
| ConstraintAnchor startAnchor = getAnchor(startType); |
| ConstraintAnchor endAnchor = target.getAnchor(endType); |
| startAnchor.connect(endAnchor, margin, 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.ANY) { |
| if (getWidth() == getWrapWidth()) { |
| setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.WRAP_CONTENT); |
| } else if (getWidth() > getMinWidth()) { |
| setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED); |
| } |
| } |
| if (getVerticalDimensionBehaviour() == ConstraintWidget.DimensionBehaviour.ANY) { |
| if (getHeight() == getWrapHeight()) { |
| setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.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; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Accessor for the horizontal dimension behaviour |
| * |
| * @return dimension behaviour |
| */ |
| public DimensionBehaviour getHorizontalDimensionBehaviour() { |
| return mHorizontalDimensionBehaviour; |
| } |
| |
| /** |
| * Accessor for the vertical dimension behaviour |
| * |
| * @return dimension behaviour |
| */ |
| public DimensionBehaviour getVerticalDimensionBehaviour() { |
| return mVerticalDimensionBehaviour; |
| } |
| |
| /** |
| * Set the widget's behaviour for the horizontal dimension |
| * |
| * @param behaviour the horizontal dimension's behaviour |
| */ |
| public void setHorizontalDimensionBehaviour(DimensionBehaviour behaviour) { |
| mHorizontalDimensionBehaviour = behaviour; |
| if (mHorizontalDimensionBehaviour == DimensionBehaviour.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) { |
| mVerticalDimensionBehaviour = behaviour; |
| if (mVerticalDimensionBehaviour == DimensionBehaviour.WRAP_CONTENT) { |
| setHeight(mWrapHeight); |
| } |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| // Constraints |
| /*-----------------------------------------------------------------------*/ |
| |
| public void addToSolver(LinearSystem system) { |
| addToSolver(system, ConstraintAnchor.ANY_GROUP); |
| } |
| |
| /** |
| * Add this widget to the solver |
| * |
| * @param system the solver we want to add the widget to |
| */ |
| @Override |
| public void addToSolver(LinearSystem system, int group) { |
| SolverVariable left = null; |
| SolverVariable right = null; |
| SolverVariable top = null; |
| SolverVariable bottom = null; |
| SolverVariable baseline = null; |
| if (group == ConstraintAnchor.ANY_GROUP || mLeft.mGroup == group) { |
| left = system.createObjectVariable(mLeft); |
| } |
| if (group == ConstraintAnchor.ANY_GROUP || mRight.mGroup == group) { |
| right = system.createObjectVariable(mRight); |
| } |
| if (group == ConstraintAnchor.ANY_GROUP || mTop.mGroup == group) { |
| top = system.createObjectVariable(mTop); |
| } |
| if (group == ConstraintAnchor.ANY_GROUP || mBottom.mGroup == group) { |
| bottom = system.createObjectVariable(mBottom); |
| } |
| if (group == ConstraintAnchor.ANY_GROUP || mBaseline.mGroup == group) { |
| baseline = system.createObjectVariable(mBaseline); |
| } |
| |
| if (mParent != null) { |
| |
| // If the parent is set to wrap content, we need to: |
| // - possibly add an extra constraint to ensure that the widget is contained into the parent |
| // - or if there is an existing constraint to the parent, make sure it's a strict constraint |
| // (i.e. top of widget strictly superior to top of parent -- otherwise the error could be |
| // bi-directional, and would result in an unstable system where the widget would sometimes |
| // not be contained in the parent) |
| |
| if (mParent.getHorizontalDimensionBehaviour() |
| == DimensionBehaviour.WRAP_CONTENT) { |
| if (mLeft.mTarget == null || |
| mLeft.mTarget.mOwner != mParent) { |
| SolverVariable parentLeft = system.createObjectVariable(mParent.mLeft); |
| ArrayRow row = system.createRow(); |
| row.createRowGreaterThan(left, parentLeft, system.createSlackVariable(), 0); |
| system.addConstraint(row); |
| } else if (mLeft.mTarget != null && |
| mLeft.mTarget.mOwner == mParent) { |
| mLeft.setConnectionType(ConstraintAnchor.ConnectionType.STRICT); |
| } |
| |
| if (mRight.mTarget == null || |
| mRight.mTarget.mOwner != mParent) { |
| SolverVariable parentRight = system.createObjectVariable(mParent.mRight); |
| ArrayRow row = system.createRow(); |
| row.createRowGreaterThan(parentRight, right, system.createSlackVariable(), 0); |
| system.addConstraint(row); |
| } else if (mRight.mTarget != null && |
| mRight.mTarget.mOwner == mParent) { |
| mRight.setConnectionType(ConstraintAnchor.ConnectionType.STRICT); |
| } |
| } |
| |
| if (mParent.getVerticalDimensionBehaviour() |
| == DimensionBehaviour.WRAP_CONTENT) { |
| if (mTop.mTarget == null || |
| mTop.mTarget.mOwner != mParent) { |
| SolverVariable parentTop = system.createObjectVariable(mParent.mTop); |
| ArrayRow row = system.createRow(); |
| row.createRowGreaterThan(top, parentTop, system.createSlackVariable(), 0); |
| system.addConstraint(row); |
| } else if (mTop.mTarget != null && |
| mTop.mTarget.mOwner == mParent) { |
| mTop.setConnectionType(ConstraintAnchor.ConnectionType.STRICT); |
| } |
| if (mBottom.mTarget == null || |
| mBottom.mTarget.mOwner != mParent) { |
| SolverVariable parentBottom = system.createObjectVariable(getParent().mBottom); |
| ArrayRow row = system.createRow(); |
| row.createRowGreaterThan(parentBottom, bottom, system.createSlackVariable(), 0); |
| system.addConstraint(row); |
| } else if (mBottom.mTarget != null && |
| mBottom.mTarget.mOwner == mParent) { |
| mBottom.setConnectionType(ConstraintAnchor.ConnectionType.STRICT); |
| } |
| } |
| } |
| |
| int width = mWidth; |
| if (width < mMinWidth) { |
| width = mMinWidth; |
| } |
| int height = mHeight; |
| if (height < mMinHeight) { |
| height = mMinHeight; |
| } |
| |
| boolean horizontalDimensionLocked = mHorizontalDimensionBehaviour != DimensionBehaviour.ANY; |
| boolean verticalDimensionLocked = mVerticalDimensionBehaviour != DimensionBehaviour.ANY; |
| |
| if (!horizontalDimensionLocked && mLeft != null && mRight != null |
| && (mLeft.mTarget == null || mRight.mTarget == null)) { |
| horizontalDimensionLocked = true; |
| } |
| if (!verticalDimensionLocked && mTop != null && mBottom != null) { |
| if (!(mTop.mTarget != null && mBottom.mTarget != null)) { |
| // if we are in any mode but either top or bottom aren't connected |
| if (mBaselineDistance == 0 |
| || (mBaseline != null && !(mTop.mTarget != null && mBaseline.mTarget != null))) { |
| // if there are no baseline, or if the baseline is also not connected... |
| verticalDimensionLocked = true; |
| } |
| } |
| } |
| boolean useRatio = false; |
| if (mDimensionRatio > 0) { |
| if (!horizontalDimensionLocked && !verticalDimensionLocked) { |
| useRatio = true; |
| // add an equation |
| ArrayRow row = system.createRow(); |
| if (group == ConstraintAnchor.ANY_GROUP || (mLeft.mGroup == group && mRight.mGroup == group)) { |
| system.addConstraint(row.createRowDimensionRatio(right, left, bottom, top, mDimensionRatio)); |
| } |
| } else if (!horizontalDimensionLocked && verticalDimensionLocked) { |
| width = (int) (mDimensionRatio * mHeight); |
| horizontalDimensionLocked = true; |
| } else if (horizontalDimensionLocked && !verticalDimensionLocked) { |
| height = (int) (mDimensionRatio * mWidth); |
| verticalDimensionLocked = true; |
| } |
| } |
| |
| boolean wrapContent = (mHorizontalDimensionBehaviour == DimensionBehaviour.WRAP_CONTENT) |
| && (this instanceof ConstraintWidgetContainer); |
| if (group == ConstraintAnchor.ANY_GROUP || (mLeft.mGroup == group && mRight.mGroup == group)) { |
| applyConstraints(system, wrapContent, horizontalDimensionLocked, mLeft, mRight, |
| mX, mX + width, width, mHorizontalBiasPercent, useRatio); |
| } |
| |
| wrapContent = (mVerticalDimensionBehaviour == DimensionBehaviour.WRAP_CONTENT) |
| && (this instanceof ConstraintWidgetContainer); |
| |
| if (mBaselineDistance > 0) { |
| ConstraintAnchor end = mBottom; |
| if (group == ConstraintAnchor.ANY_GROUP || (mBottom.mGroup == group && mBaseline.mGroup == group)) { |
| system.addConstraint( |
| EquationCreation.createRowEquals(system, bottom, baseline, |
| height - getBaselineDistance(), |
| false)); |
| } |
| if (mBaseline.mTarget != null) { |
| height = mBaselineDistance; |
| end = mBaseline; |
| } |
| if (group == ConstraintAnchor.ANY_GROUP || (mTop.mGroup == group && end.mGroup == group)) { |
| applyConstraints(system, wrapContent, verticalDimensionLocked, |
| mTop, end, mY, mY + height, height, mVerticalBiasPercent, useRatio); |
| } |
| } else { |
| if (group == ConstraintAnchor.ANY_GROUP || (mTop.mGroup == group && mBottom.mGroup == group)) { |
| applyConstraints(system, wrapContent, verticalDimensionLocked, |
| mTop, mBottom, mY, mY + height, height, mVerticalBiasPercent, useRatio); |
| } |
| } |
| } |
| |
| /** |
| * 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 wrapContent is the widget trying to wrap its content (i.e. its size will depends on its content) |
| * @param dimensionLocked is the widget dimensions locked or can they change |
| * @param beginAnchor the first anchor |
| * @param endAnchor the second anchor |
| * @param beginPosition the original position of the anchor |
| * @param endPosition the original position of the anchor |
| * @param dimension the dimension |
| */ |
| private void applyConstraints(LinearSystem system, boolean wrapContent, boolean dimensionLocked, |
| ConstraintAnchor beginAnchor, ConstraintAnchor endAnchor, |
| int beginPosition, int endPosition, int dimension, float bias, boolean useRatio) { |
| SolverVariable begin = system.createObjectVariable(beginAnchor); |
| SolverVariable end = system.createObjectVariable(endAnchor); |
| SolverVariable beginTarget = system.createObjectVariable(beginAnchor.getTarget()); |
| SolverVariable endTarget = system.createObjectVariable(endAnchor.getTarget()); |
| |
| if (mVisibility == ConstraintWidget.GONE) { |
| dimension = 0; |
| } |
| if (beginTarget == null && endTarget == null) { |
| system.addConstraint(system.createRow().createRowEquals(begin, beginPosition)); |
| if (wrapContent) { |
| system.addConstraint(EquationCreation.createRowEquals(system, end, begin, 0, true)); |
| } else { |
| if (dimensionLocked) { |
| system.addConstraint( |
| EquationCreation.createRowEquals(system, end, begin, dimension, false)); |
| } else { |
| system.addConstraint(system.createRow().createRowEquals(end, endPosition)); |
| } |
| } |
| } else if (beginTarget != null && endTarget == null) { |
| system.addConstraint(system.createRow().createRowEquals(begin, beginTarget, beginAnchor.mMargin)); |
| if (wrapContent) { |
| system.addConstraint(EquationCreation.createRowEquals(system, end, begin, 0, true)); |
| } else { |
| if (dimensionLocked) { |
| system.addConstraint(system.createRow().createRowEquals(end, begin, dimension)); |
| } else { |
| system.addConstraint(system.createRow().createRowEquals(end, endPosition)); |
| } |
| } |
| } else if (beginTarget == null && endTarget != null) { |
| system.addConstraint(system.createRow().createRowEquals(end, endTarget, -1 * endAnchor.mMargin)); |
| if (wrapContent) { |
| system.addConstraint(EquationCreation.createRowEquals(system, end, begin, 0, true)); |
| } else { |
| if (dimensionLocked) { |
| system.addConstraint(system.createRow().createRowEquals(end, begin, dimension)); |
| } else { |
| system.addConstraint(system.createRow().createRowEquals(begin, beginPosition)); |
| } |
| } |
| } else { // both constraints set |
| if (dimensionLocked) { |
| if (wrapContent) { |
| system.addConstraint( |
| EquationCreation.createRowEquals(system, end, begin, 0, true)); |
| } else { |
| system.addConstraint(system.createRow().createRowEquals(end, begin, dimension)); |
| } |
| |
| if (beginAnchor.getStrength() != endAnchor.getStrength()) { |
| if (beginAnchor.getStrength() == ConstraintAnchor.Strength.STRONG) { |
| system.addConstraint(system.createRow().createRowEquals(begin, beginTarget, beginAnchor.mMargin)); |
| SolverVariable slack = system.createSlackVariable(); |
| ArrayRow row = system.createRow(); |
| row.createRowLowerThan(end, endTarget, slack, -1 * endAnchor.mMargin); |
| system.addConstraint(row); |
| } else { |
| SolverVariable slack = system.createSlackVariable(); |
| ArrayRow row = system.createRow(); |
| row.createRowGreaterThan(begin, beginTarget, slack, beginAnchor.mMargin); |
| system.addConstraint(row); |
| system.addConstraint(system.createRow().createRowEquals(end, endTarget, -1 * endAnchor.mMargin)); |
| } |
| } else { |
| if (beginTarget == endTarget) { |
| system.addConstraint(EquationCreation |
| .createRowCentering(system, begin, beginTarget, |
| 0, 0.5f, endTarget, end, 0, true)); |
| } else { |
| boolean useBidirectionalError = (beginAnchor.getConnectionType() != |
| ConstraintAnchor.ConnectionType.STRICT); |
| system.addConstraint(EquationCreation |
| .createRowGreaterThan(system, begin, beginTarget, |
| beginAnchor.getMargin(), useBidirectionalError)); |
| useBidirectionalError = (endAnchor.getConnectionType() != |
| ConstraintAnchor.ConnectionType.STRICT); |
| system.addConstraint(EquationCreation |
| .createRowLowerThan(system, end, endTarget, |
| -1 * endAnchor.getMargin(), |
| useBidirectionalError)); |
| system.addConstraint(EquationCreation |
| .createRowCentering(system, begin, beginTarget, |
| beginAnchor.getMargin(), |
| bias, endTarget, end, endAnchor.getMargin(), false)); |
| } |
| } |
| } else if (useRatio) { |
| system.addConstraint(EquationCreation |
| .createRowEquals(system, begin, beginTarget, beginAnchor.getMargin(), |
| true)); |
| system.addConstraint(EquationCreation |
| .createRowEquals(system, end, endTarget, -1 * endAnchor.getMargin(), |
| true)); |
| system.addConstraint(EquationCreation |
| .createRowCentering(system, begin, beginTarget, |
| 0, 0.5f, endTarget, end, 0, true)); |
| } else { |
| system.addConstraint(system.createRow().createRowEquals(begin, beginTarget, beginAnchor.mMargin)); |
| system.addConstraint(system.createRow().createRowEquals(end, endTarget, -1 * endAnchor.mMargin)); |
| } |
| } |
| } |
| |
| /** |
| * Update the widget from the values generated by the solver |
| * |
| * @param system the solver we get the values from. |
| */ |
| @Override |
| public void updateFromSolver(LinearSystem system, int group) { |
| if (group == ConstraintAnchor.ANY_GROUP) { |
| int left = system.getObjectVariableValue(mLeft); |
| int top = system.getObjectVariableValue(mTop); |
| int right = system.getObjectVariableValue(mRight); |
| int bottom = system.getObjectVariableValue(mBottom); |
| setFrame(left, top, right, bottom); |
| } else if (group == ConstraintAnchor.APPLY_GROUP_RESULTS) { |
| setFrame(mSolverLeft, mSolverTop, mSolverRight, mSolverBottom); |
| } else { |
| if (mLeft.mGroup == group) { |
| mSolverLeft = system.getObjectVariableValue(mLeft); |
| } |
| if (mTop.mGroup == group) { |
| mSolverTop = system.getObjectVariableValue(mTop); |
| } |
| if (mRight.mGroup == group) { |
| mSolverRight = system.getObjectVariableValue(mRight); |
| } |
| if (mBottom.mGroup == group) { |
| mSolverBottom = system.getObjectVariableValue(mBottom); |
| } |
| } |
| } |
| |
| public void updateFromSolver(LinearSystem system) { |
| updateFromSolver(system, ConstraintAnchor.ANY_GROUP); |
| } |
| } |