ConstraintProperties.java

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.constraintlayout.widget;

import android.os.Build;
import android.view.View;
import android.view.ViewGroup;

/**
 *  <b>Added in 2.0</b>
 *  <p>
 *  ConstraintProperties provides an easy to use api to update the layout params
 *  of {@link ConstraintLayout} children
 *  </p>
 */
public class ConstraintProperties {
    ConstraintLayout.LayoutParams mParams;
    View mView;
    /**
     * The left side of a view.
     */
    public static final int LEFT = ConstraintLayout.LayoutParams.LEFT;

    /**
     * The right side of a view.
     */
    public static final int RIGHT = ConstraintLayout.LayoutParams.RIGHT;

    /**
     * The top of a view.
     */
    public static final int TOP = ConstraintLayout.LayoutParams.TOP;

    /**
     * The bottom side of a view.
     */
    public static final int BOTTOM = ConstraintLayout.LayoutParams.BOTTOM;

/**
     * The baseline of the text in a view.
     */
    public static final int BASELINE = ConstraintLayout.LayoutParams.BASELINE;

    /**
     * The left side of a view in left to right languages.
     * In right to left languages it corresponds to the right side of the view
     */
    public static final int START = ConstraintLayout.LayoutParams.START;

    /**
     * The right side of a view in right to left languages.
     * In right to left languages it corresponds to the left side of the view
     */
    public static final int END = ConstraintLayout.LayoutParams.END;
    /**
     * Used to indicate a parameter is cleared or not set
     */
    public static final int UNSET = ConstraintLayout.LayoutParams.UNSET;
    /**
     * References the id of the parent.
     */
    public static final int PARENT_ID = ConstraintLayout.LayoutParams.PARENT_ID;

    /**
     * Dimension will be controlled by constraints
     */
    public static final int MATCH_CONSTRAINT = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT;

    /**
     * Dimension will set by the view's content
     */
    public static final int WRAP_CONTENT = ConstraintLayout.LayoutParams.WRAP_CONTENT;

    /**
     * How to calculate the size of a view in 0 dp by using its wrap_content size
     */
    public static final int MATCH_CONSTRAINT_WRAP = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT_WRAP;

    /**
     * Calculate the size of a view in 0 dp by reducing the constrains gaps as much as possible
     */
    public static final int MATCH_CONSTRAINT_SPREAD = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT_SPREAD;

    /**
     * Center view between the other two widgets.
     *
     * @param firstID      ID of the first widget to connect the left or top of the widget to
     * @param firstSide    the side of the widget to connect to
     * @param firstMargin  the connection margin
     * @param secondId     the ID of the second widget to connect to right or top of the widget to
     * @param secondSide   the side of the widget to connect to
     * @param secondMargin the connection margin
     * @param bias         the ratio between two connections
     * @return this
     */

    public ConstraintProperties center(int firstID, int firstSide, int firstMargin, int secondId, int secondSide, int secondMargin, float bias) {
        // Error checking

        if (firstMargin < 0) {
            throw new IllegalArgumentException("margin must be > 0");
        }
        if (secondMargin < 0) {
            throw new IllegalArgumentException("margin must be > 0");
        }
        if (bias <= 0 || bias > 1) {
            throw new IllegalArgumentException("bias must be between 0 and 1 inclusive");
        }

        if (firstSide == LEFT || firstSide == RIGHT) {
            connect(LEFT, firstID, firstSide, firstMargin);
            connect(RIGHT, secondId, secondSide, secondMargin);

            mParams.horizontalBias = bias;
        } else if (firstSide == START || firstSide == END) {
            connect(START, firstID, firstSide, firstMargin);
            connect(END, secondId, secondSide, secondMargin);

            mParams.horizontalBias = bias;
        } else {
            connect(TOP, firstID, firstSide, firstMargin);
            connect(BOTTOM, secondId, secondSide, secondMargin);
            mParams.verticalBias = bias;
        }

        return this;
    }

    /**
     * Centers the widget horizontally to the left and right side on another widgets sides.
     *
     * @param leftId      The Id of the widget on the left side
     * @param leftSide    The side of the leftId widget to connect to
     * @param leftMargin  The margin on the left side
     * @param rightId     The Id of the widget on the right side
     * @param rightSide   The side  of the rightId widget to connect to
     * @param rightMargin The margin on the right side
     * @param bias        The ratio of the space on the left vs. right sides 0.5 is centered (default)
     * @return this
     */
    public ConstraintProperties centerHorizontally(int leftId, int leftSide, int leftMargin, int rightId, int rightSide, int rightMargin, float bias) {
        connect(LEFT, leftId, leftSide, leftMargin);
        connect(RIGHT, rightId, rightSide, rightMargin);
        mParams.horizontalBias = bias;
        return this;
    }

    /**
     * Centers the widgets horizontally to the left and right side on another widgets sides.
     *
     * @param startId     The Id of the widget on the start side (left in non rtl languages)
     * @param startSide   The side of the startId widget to connect to
     * @param startMargin The margin on the start side
     * @param endId       The Id of the widget on the start side (left in non rtl languages)
     * @param endSide     The side of the endId widget to connect to
     * @param endMargin   The margin on the end side
     * @param bias        The ratio of the space on the start vs end side 0.5 is centered (default)
     * @return this
     */
    public ConstraintProperties centerHorizontallyRtl(int startId, int startSide, int startMargin, int endId, int endSide, int endMargin, float bias) {
        connect(START, startId, startSide, startMargin);
        connect(END, endId, endSide, endMargin);
        mParams.horizontalBias = bias;
        return this;
    }

    /**
     * Centers the widgets Vertically to the top and bottom side on another widgets sides.
     *
     * @param topId        The Id of the widget on the top side
     * @param topSide      The side of the leftId widget to connect to
     * @param topMargin    The margin on the top side
     * @param bottomId     The Id of the widget on the bottom side
     * @param bottomSide   The side of the bottomId widget to connect to
     * @param bottomMargin The margin on the bottom side
     * @param bias         The ratio of the space on the top vs. bottom sides 0.5 is centered (default)
     * @return this
     */
    public ConstraintProperties centerVertically(int topId, int topSide, int topMargin, int bottomId, int bottomSide, int bottomMargin, float bias) {
        connect(TOP, topId, topSide, topMargin);
        connect(BOTTOM, bottomId, bottomSide, bottomMargin);
        mParams.verticalBias = bias;
        return this;
    }

    /**
     * Centers the view horizontally relative to toView's position.
     *
     * @param toView ID of view to center on (or in)
     * @return this
     */
    public ConstraintProperties centerHorizontally(int toView) {
        if (toView == PARENT_ID) {
            center(PARENT_ID, ConstraintSet.LEFT, 0, PARENT_ID, ConstraintSet.RIGHT, 0, 0.5f);
        } else {
            center(toView, ConstraintSet.RIGHT, 0, toView, ConstraintSet.LEFT, 0, 0.5f);
        }
        return this;
    }

    /**
     * Centers the view horizontally relative to toView's position.
     *
     * @param toView ID of view to center on (or in)
     * @return this
     */
    public ConstraintProperties centerHorizontallyRtl(int toView) {
        if (toView == PARENT_ID) {
            center(PARENT_ID, ConstraintSet.START, 0, PARENT_ID, ConstraintSet.END, 0, 0.5f);
        } else {
            center(toView, ConstraintSet.END, 0, toView, ConstraintSet.START, 0, 0.5f);
        }
        return this;
    }

    /**
     * Centers the view vertically relative to toView's position.
     *
     * @param toView ID of view to center on (or in)
     * @return this
     */
    public ConstraintProperties centerVertically(int toView) {
        if (toView == PARENT_ID) {
            center(PARENT_ID, ConstraintSet.TOP, 0, PARENT_ID, ConstraintSet.BOTTOM, 0, 0.5f);
        } else {
            center(toView, ConstraintSet.BOTTOM, 0, toView, ConstraintSet.TOP, 0, 0.5f);
        }
        return this;
    }

    /**
     * Remove a constraint from this view.
     *
     * @param anchor the Anchor to remove constraint from
     * @return this
     */
    public ConstraintProperties removeConstraints(int anchor) {
        switch (anchor) {
            case LEFT:
                mParams.leftToRight = mParams.UNSET;
                mParams.leftToLeft = mParams.UNSET;
                mParams.leftMargin = mParams.UNSET;
                mParams.goneLeftMargin = mParams.GONE_UNSET;
                break;
            case RIGHT:
                mParams.rightToRight = mParams.UNSET;
                mParams.rightToLeft = mParams.UNSET;
                mParams.rightMargin = mParams.UNSET;
                mParams.goneRightMargin = mParams.GONE_UNSET;
                break;
            case TOP:
                mParams.topToBottom = mParams.UNSET;
                mParams.topToTop = mParams.UNSET;
                mParams.topMargin = mParams.UNSET;
                mParams.goneTopMargin = mParams.GONE_UNSET;
                break;
            case BOTTOM:
                mParams.bottomToTop = mParams.UNSET;
                mParams.bottomToBottom = mParams.UNSET;
                mParams.bottomMargin = mParams.UNSET;
                mParams.goneBottomMargin = mParams.GONE_UNSET;
                break;
            case BASELINE:
                mParams.baselineToBaseline = mParams.UNSET;
                break;
            case START:
                mParams.startToEnd = mParams.UNSET;
                mParams.startToStart = mParams.UNSET;
                mParams.setMarginStart(mParams.UNSET);
                mParams.goneStartMargin = mParams.GONE_UNSET;
                break;
            case END:
                mParams.endToStart = mParams.UNSET;
                mParams.endToEnd = mParams.UNSET;
                mParams.setMarginEnd(mParams.UNSET);
                mParams.goneEndMargin = mParams.GONE_UNSET;
                break;
            default:
                throw new IllegalArgumentException("unknown constraint");
        }
        return this;
    }

    /**
     * Sets the margin.
     *
     * @param anchor The side to adjust the margin on
     * @param value  The new value for the margin
     * @return this
     */
    public ConstraintProperties margin(int anchor, int value) {
        switch (anchor) {
            case LEFT:
                mParams.leftMargin = value;
                break;
            case RIGHT:
                mParams.rightMargin = value;
                break;
            case TOP:
                mParams.topMargin = value;
                break;
            case BOTTOM:
                mParams.bottomMargin = value;
                break;
            case BASELINE:
                throw new IllegalArgumentException("baseline does not support margins");
            case START:
                mParams.setMarginStart(value);
                break;
            case END:
                mParams.setMarginEnd(value);
                break;
            default:
                throw new IllegalArgumentException("unknown constraint");
        }
        return this;
    }

    /**
     * Sets the gone margin.
     *
     * @param anchor The side to adjust the margin on
     * @param value  The new value for the margin
     * @return this
     */
    public ConstraintProperties goneMargin(int anchor, int value) {
        switch (anchor) {
            case LEFT:
                mParams.goneLeftMargin = value;
                break;
            case RIGHT:
                mParams.goneRightMargin = value;
                break;
            case TOP:
                mParams.goneTopMargin = value;
                break;
            case BOTTOM:
                mParams.goneBottomMargin = value;
                break;
            case BASELINE:
                throw new IllegalArgumentException("baseline does not support margins");
            case START:
                mParams.goneStartMargin = value;
                break;
            case END:
                mParams.goneEndMargin = value;
                break;
            default:
                throw new IllegalArgumentException("unknown constraint");
        }
        return this;
    }

    /**
     * Adjust the horizontal bias of the view (used with views constrained on left and right).
     *
     * @param bias the new bias 0.5 is in the middle
     * @return this
     */
    public ConstraintProperties horizontalBias(float bias) {
        mParams.horizontalBias = bias;
        return this;
    }

    /**
     * Adjust the vertical bias of the view (used with views constrained on left and right).
     *
     * @param bias the new bias 0.5 is in the middle
     * @return this
     */
    public ConstraintProperties verticalBias(float bias) {
        mParams.verticalBias = bias;
        return this;
    }

    /**
     * Constrains the views aspect ratio.
     * For Example a HD screen is 16 by 9 = 16/(float)9 = 1.777f.
     *
     * @param ratio The ratio of the width to height (width / height)
     * @return this
     */
    public ConstraintProperties dimensionRatio(String ratio) {
        mParams.dimensionRatio = ratio;
        return this;
    }

    /**
     * Adjust the visibility of a view.
     *
     * @param visibility the visibility (View.VISIBLE, View.INVISIBLE, View.GONE)
     * @return this
     */
    public ConstraintProperties visibility(int visibility) {
        mView.setVisibility(visibility);
        return this;
    }

    /**
     * Adjust the alpha of a view.
     *
     * @param alpha the alpha
     * @return this
     */
    public ConstraintProperties alpha(float alpha) {
        mView.setAlpha(alpha);
        return this;
    }

    /**
     * Set the elevation of a view.
     *
     * @param elevation the elevation
     * @return this
     */
    public ConstraintProperties elevation(float elevation) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mView.setElevation(elevation);
        }
        return this;
    }

    /**
     * Adjust the post-layout rotation about the Z axis of a view.
     *
     * @param rotation the rotation about the Z axis
     * @return this
     */
    public ConstraintProperties rotation(float rotation) {
        mView.setRotation(rotation);
        return this;
    }

    /**
     * Adjust the post-layout rotation about the X axis of a view.
     *
     * @param rotationX the rotation about the X axis
     * @return this
     */
    public ConstraintProperties rotationX(float rotationX) {
        mView.setRotationX(rotationX);
        return this;
    }

    /**
     * Adjust the post-layout rotation about the Y axis of a view.
     *
     * @param rotationY the rotation about the Y axis
     * @return this
     */
    public ConstraintProperties rotationY(float rotationY) {
        mView.setRotationY(rotationY);
        return this;
    }

    /**
     * Adjust the post-layout scale in X of a view.
     *
     * @param scaleX the scale in X
     * @return this
     */
    public ConstraintProperties scaleX(float scaleX) {
        mView.setScaleY(scaleX);
        return this;
    }

    /**
     * Adjust the post-layout scale in Y of a view.
     *
     * @param scaleY the scale in Y
     * @return this
     */
    public ConstraintProperties scaleY(float scaleY) {
        return this;
    }

    /**
     * Set X location of the pivot point around which the view will rotate and scale.
     *
     * @param transformPivotX X location of the pivot point.
     * @return this
     */
    public ConstraintProperties transformPivotX(float transformPivotX) {
        mView.setPivotX(transformPivotX);
        return this;
    }

    /**
     * Set Y location of the pivot point around which the view will rotate and scale.
     *
     * @param transformPivotY Y location of the pivot point.
     * @return this
     */
    public ConstraintProperties transformPivotY(float transformPivotY) {
        mView.setPivotY(transformPivotY);
        return this;
    }

    /**
     * Set X and Y location of the pivot point around which the view will rotate and scale.
     *
     * @param transformPivotX X location of the pivot point.
     * @param transformPivotY Y location of the pivot point.
     * @return this
     */
    public ConstraintProperties transformPivot(float transformPivotX, float transformPivotY) {
        mView.setPivotX(transformPivotX);
        mView.setPivotY(transformPivotY);
        return this;
    }

    /**
     * Adjust the post-layout X translation of a view.
     *
     * @param translationX the translation in X
     * @return this
     */
    public ConstraintProperties translationX(float translationX) {
        mView.setTranslationX(translationX);
        return this;
    }

    /**
     * Adjust the  post-layout Y translation of a view.
     *
     * @param translationY the translation in Y
     * @return this
     */
    public ConstraintProperties translationY(float translationY) {
        mView.setTranslationY(translationY);
        return this;
    }

    /**
     * Adjust the  post-layout X and Y translation of a view.
     *
     * @param translationX the translation in X
     * @param translationY the translation in Y
     * @return this
     */
    public ConstraintProperties translation(float translationX, float translationY) {
        mView.setTranslationX(translationX);
        mView.setTranslationY(translationY);
        return this;
    }

    /**
     * Adjust the post-layout translation in Z of a view. This is the preferred way to adjust the shadow.
     *
     * @param translationZ the translationZ
     * @return this
     */
    public ConstraintProperties translationZ(float translationZ) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mView.setTranslationZ(translationZ);
        }
        return this;
    }

    /**
     * Sets the height of the view.
     *
     * @param height the height of the view
     * @return this
     */
    public ConstraintProperties constrainHeight(int height) {
        mParams.height = height;
        return this;
    }

    /**
     * Sets the width of the view.
     *
     * @param width the width of the view
     * @return this
     */
    public ConstraintProperties constrainWidth(int width) {
        mParams.width = width;
        return this;
    }

    /**
     * Sets the maximum height of the view. It is a dimension, It is only applicable if height is
     * #MATCH_CONSTRAINT}.
     *
     * @param height the maximum height of the view
     * @return this
     */
    public ConstraintProperties constrainMaxHeight(int height) {
        mParams.matchConstraintMaxHeight = height;
        return this;
    }

    /**
     * Sets the maximum width of the view. It is a dimension, It is only applicable if height is
     * #MATCH_CONSTRAINT}.
     *
     * @param width the maximum width of the view
     * @return this
     */
    public ConstraintProperties constrainMaxWidth(int width) {
        mParams.matchConstraintMaxWidth = width;
        return this;
    }

    /**
     * Sets the minimum height of the view. It is a dimension, It is only applicable if height is
     * #MATCH_CONSTRAINT}.
     *
     * @param height the minimum height of the view
     * @return this
     */
    public ConstraintProperties constrainMinHeight(int height) {
        mParams.matchConstraintMinHeight = height;
        return this;
    }

    /**
     * Sets the minimum width of the view. It is a dimension, It is only applicable if height is
     * #MATCH_CONSTRAINT}.
     *
     * @param width the minimum width of the view
     * @return this
     */
    public ConstraintProperties constrainMinWidth(int width) {
        mParams.matchConstraintMinWidth = width;
        return this;
    }

    /**
     * Sets how the height is calculated ether MATCH_CONSTRAINT_WRAP or MATCH_CONSTRAINT_SPREAD.
     * Default is spread.
     *
     * @param height MATCH_CONSTRAINT_WRAP or MATCH_CONSTRAINT_SPREAD
     * @return this
     */
    public ConstraintProperties constrainDefaultHeight(int height) {
        mParams.matchConstraintDefaultHeight = height;
        return this;
    }

    /**
     * Sets how the width is calculated ether MATCH_CONSTRAINT_WRAP or MATCH_CONSTRAINT_SPREAD.
     * Default is spread.
     *
     * @param width MATCH_CONSTRAINT_WRAP or MATCH_CONSTRAINT_SPREAD
     * @return this
     */
    public ConstraintProperties constrainDefaultWidth(int width) {
        mParams.matchConstraintDefaultWidth = width;
        return this;
    }

    /**
     * The child's weight that we can use to distribute the available horizontal space
     * in a chain, if the dimension behaviour is set to MATCH_CONSTRAINT
     *
     * @param weight the weight that we can use to distribute the horizontal space
     * @return this
     */
    public ConstraintProperties horizontalWeight(float weight) {
        mParams.horizontalWeight = weight;
        return this;
    }

    /**
     * The child's weight that we can use to distribute the available vertical space
     * in a chain, if the dimension behaviour is set to MATCH_CONSTRAINT
     *
     * @param weight the weight that we can use to distribute the vertical space
     * @return this
     */
    public ConstraintProperties verticalWeight(float weight) {
        mParams.verticalWeight = weight;
        return this;
    }

    /**
     * How the elements of the horizontal chain will be positioned. If the dimension
     * behaviour is set to MATCH_CONSTRAINT. The possible values are:
     * <p>
     * <ul>
     *   <li>CHAIN_SPREAD -- the elements will be spread out</li>
     *   <li>CHAIN_SPREAD_INSIDE -- similar, but the endpoints of the chain will not be spread out</li>
     *   <li>CHAIN_PACKED -- the elements of the chain will be packed together. The horizontal
     * bias attribute of the child will then affect the positioning of the packed elements</li>
     * </ul>
     *
     * @param chainStyle the weight that we can use to distribute the horizontal space
     * @return this
     */
    public ConstraintProperties horizontalChainStyle(int chainStyle) {
        mParams.horizontalChainStyle = chainStyle;
        return this;
    }

    /**
     * How the elements of the vertical chain will be positioned. in a chain, if the dimension
     * behaviour is set to MATCH_CONSTRAINT
     * <p>
     * <ul>
     *   <li>CHAIN_SPREAD -- the elements will be spread out</li>
     *   <li>CHAIN_SPREAD_INSIDE -- similar, but the endpoints of the chain will not be spread out</li>
     *   <li>CHAIN_PACKED -- the elements of the chain will be packed together. The horizontal
     * bias attribute of the child will then affect the positioning of the packed elements</li>
     * </ul>
     *
     * @param chainStyle the weight that we can use to distribute the horizontal space
     * @return this
     */
    public ConstraintProperties verticalChainStyle(int chainStyle) {
        mParams.verticalChainStyle = chainStyle;
        return this;
    }

    /**
     * Adds the view to a horizontal chain.
     *
     * @param leftId  id of the view in chain to the left
     * @param rightId id of the view in chain to the right
     * @return this
     */
    public ConstraintProperties addToHorizontalChain(int leftId, int rightId) {
        connect(LEFT, leftId, (leftId == PARENT_ID) ? LEFT : RIGHT, 0);
        connect(RIGHT, rightId, (rightId == PARENT_ID) ? RIGHT : LEFT, 0);
        if (leftId != PARENT_ID) {
            View leftView = ((ViewGroup) (mView.getParent())).findViewById(leftId);
            ConstraintProperties leftProp = new ConstraintProperties(leftView);
            leftProp.connect(RIGHT, mView.getId(), LEFT, 0);
        }
        if (rightId != PARENT_ID) {
            View rightView = ((ViewGroup) (mView.getParent())).findViewById(rightId);
            ConstraintProperties rightProp = new ConstraintProperties(rightView);
            rightProp.connect(LEFT, mView.getId(), RIGHT, 0);
        }
        return this;
    }

    /**
     * Adds the view to a horizontal chain using RTL attributes.
     *
     * @param leftId  id of the view in chain to the left
     * @param rightId id of the view in chain to the right
     * @return this
     */
    public ConstraintProperties addToHorizontalChainRTL(int leftId, int rightId) {
        connect(START, leftId, (leftId == PARENT_ID) ? START : END, 0);
        connect(END, rightId, (rightId == PARENT_ID) ? END : START, 0);
        if (leftId != PARENT_ID) {
            View leftView = ((ViewGroup) (mView.getParent())).findViewById(leftId);
            ConstraintProperties leftProp = new ConstraintProperties(leftView);
            leftProp.connect(END, mView.getId(), START, 0);
        }
        if (rightId != PARENT_ID) {
            View rightView = ((ViewGroup) (mView.getParent())).findViewById(rightId);
            ConstraintProperties rightProp = new ConstraintProperties(rightView);
            rightProp.connect(START, mView.getId(), END, 0);
        }
        return this;
    }

    /**
     * Adds a view to a vertical chain.
     *
     * @param topId    view above.
     * @param bottomId view below
     * @return this
     */
    public ConstraintProperties addToVerticalChain(int topId, int bottomId) {
        connect(TOP, topId, (topId == PARENT_ID) ? TOP : BOTTOM, 0);
        connect(BOTTOM, bottomId, (bottomId == PARENT_ID) ? BOTTOM : TOP, 0);
        if (topId != PARENT_ID) {
            View topView = ((ViewGroup) (mView.getParent())).findViewById(topId);
            ConstraintProperties topProp = new ConstraintProperties(topView);
            topProp.connect(BOTTOM, mView.getId(), TOP, 0);
        }
        if (bottomId != PARENT_ID) {
            View bottomView = ((ViewGroup) (mView.getParent())).findViewById(bottomId);
            ConstraintProperties bottomProp = new ConstraintProperties(bottomView);
            bottomProp.connect(TOP, mView.getId(), BOTTOM, 0);
        }
        return this;
    }

    /**
     * Removes a view from a vertical chain.
     * This assumes the view is connected to a vertical chain.
     * Its behaviour is undefined if not part of a vertical chain.
     *
     * @return this
     */
    public ConstraintProperties removeFromVerticalChain() {
        int topId = mParams.topToBottom;
        int bottomId = mParams.bottomToTop;
        if (topId != mParams.UNSET || bottomId != mParams.UNSET) {
            View topView = ((ViewGroup) (mView.getParent())).findViewById(topId);
            ConstraintProperties topProp = new ConstraintProperties(topView);
            View bottomView = ((ViewGroup) (mView.getParent())).findViewById(bottomId);
            ConstraintProperties bottomProp = new ConstraintProperties(bottomView);
            if (topId != mParams.UNSET && bottomId != mParams.UNSET) {
                // top and bottom connected to views
                topProp.connect(BOTTOM, bottomId, TOP, 0);
                bottomProp.connect(TOP, topId, BOTTOM, 0);
            } else if (topId != mParams.UNSET || bottomId != mParams.UNSET) {
                if (mParams.bottomToBottom != mParams.UNSET) {
                    // top connected to view. Bottom connected to parent
                    topProp.connect(BOTTOM, mParams.bottomToBottom, BOTTOM, 0);
                } else if (mParams.topToTop != mParams.UNSET) {
                    // bottom connected to view. Top connected to parent
                    bottomProp.connect(TOP, mParams.topToTop, TOP, 0);
                }
            }
        }

        removeConstraints(TOP);
        removeConstraints(BOTTOM);
        return this;
    }

    /**
     * Removes a view from a vertical chain.
     * This assumes the view is connected to a vertical chain.
     * Its behaviour is undefined if not part of a vertical chain.
     *
     * @return this
     */
    public ConstraintProperties removeFromHorizontalChain() {
        int leftId = mParams.leftToRight;
        int rightId = mParams.rightToLeft;

        if (leftId != mParams.UNSET || rightId != mParams.UNSET) {
            View leftView = ((ViewGroup) (mView.getParent())).findViewById(leftId);
            ConstraintProperties leftProp = new ConstraintProperties(leftView);
            View rightView = ((ViewGroup) (mView.getParent())).findViewById(rightId);
            ConstraintProperties rightProp = new ConstraintProperties(rightView);
            if (leftId != mParams.UNSET && rightId != mParams.UNSET) {
                // left and right connected to views
                leftProp.connect(RIGHT, rightId, LEFT, 0);
                rightProp.connect(LEFT, leftId, RIGHT, 0);
            } else if (leftId != mParams.UNSET || rightId != mParams.UNSET) {
                if (mParams.rightToRight != mParams.UNSET) {
                    // left connected to view. right connected to parent
                    leftProp.connect(RIGHT, mParams.rightToRight, RIGHT, 0);
                } else if (mParams.leftToLeft != mParams.UNSET) {
                    // right connected to view. left connected to parent
                    rightProp.connect(LEFT, mParams.leftToLeft, LEFT, 0);
                }
            }
            removeConstraints(LEFT);
            removeConstraints(RIGHT);
        } else {

            int startId = mParams.startToEnd;
            int endId = mParams.endToStart;
            if (startId != mParams.UNSET || endId != mParams.UNSET) {
                View startView = ((ViewGroup) (mView.getParent())).findViewById(startId);
                ConstraintProperties startProp = new ConstraintProperties(startView);
                View endView = ((ViewGroup) (mView.getParent())).findViewById(endId);
                ConstraintProperties endProp = new ConstraintProperties(endView);

                if (startId != mParams.UNSET && endId != mParams.UNSET) {
                    // start and end connected to views
                    startProp.connect(END, endId, START, 0);
                    endProp.connect(START, leftId, END, 0);
                } else if (leftId != mParams.UNSET || endId != mParams.UNSET) {
                    if (mParams.rightToRight != mParams.UNSET) {
                        // left connected to view. right connected to parent
                        startProp.connect(END, mParams.rightToRight, END, 0);
                    } else if (mParams.leftToLeft != mParams.UNSET) {
                        // right connected to view. left connected to parent
                        endProp.connect(START, mParams.leftToLeft, START, 0);
                    }
                }
            }
            removeConstraints(START);
            removeConstraints(END);
        }
        return this;
    }

    /**
     * Create a constraint between two widgets.
     *
     * @param startSide the side of the widget to constrain
     * @param endID     the id of the widget to constrain to
     * @param endSide   the side of widget to constrain to
     * @param margin    the margin to constrain (margin must be positive)
     */
    public ConstraintProperties connect(int startSide, int endID, int endSide, int margin) {

        switch (startSide) {
            case LEFT:
                if (endSide == LEFT) {
                    mParams.leftToLeft = endID;
                    mParams.leftToRight = mParams.UNSET;
                } else if (endSide == RIGHT) {
                    mParams.leftToRight = endID;
                    mParams.leftToLeft = mParams.UNSET;

                } else {
                    throw new IllegalArgumentException("Left to " + sideToString(endSide) + " undefined");
                }
                mParams.leftMargin = margin;
                break;
            case RIGHT:
                if (endSide == LEFT) {
                    mParams.rightToLeft = endID;
                    mParams.rightToRight = mParams.UNSET;

                } else if (endSide == RIGHT) {
                    mParams.rightToRight = endID;
                    mParams.rightToLeft = mParams.UNSET;

                } else {
                    throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined");
                }
                mParams.rightMargin = margin;
                break;
            case TOP:
                if (endSide == TOP) {
                    mParams.topToTop = endID;
                    mParams.topToBottom = mParams.UNSET;
                    mParams.baselineToBaseline = mParams.UNSET;
                    mParams.baselineToTop = mParams.UNSET;
                    mParams.baselineToBottom = mParams.UNSET;
                } else if (endSide == BOTTOM) {
                    mParams.topToBottom = endID;
                    mParams.topToTop = mParams.UNSET;
                    mParams.baselineToBaseline = mParams.UNSET;
                    mParams.baselineToTop = mParams.UNSET;
                    mParams.baselineToBottom = mParams.UNSET;
                } else {
                    throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined");
                }
                mParams.topMargin = margin;
                break;
            case BOTTOM:
                if (endSide == BOTTOM) {
                    mParams.bottomToBottom = endID;
                    mParams.bottomToTop = mParams.UNSET;
                    mParams.baselineToBaseline = mParams.UNSET;
                    mParams.baselineToTop = mParams.UNSET;
                    mParams.baselineToBottom = mParams.UNSET;
                } else if (endSide == TOP) {
                    mParams.bottomToTop = endID;
                    mParams.bottomToBottom = mParams.UNSET;
                    mParams.baselineToBaseline = mParams.UNSET;
                    mParams.baselineToTop = mParams.UNSET;
                    mParams.baselineToBottom = mParams.UNSET;
                } else {
                    throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined");
                }
                mParams.bottomMargin = margin;
                break;
            case BASELINE:
                if (endSide == BASELINE) {
                    mParams.baselineToBaseline = endID;
                    mParams.bottomToBottom = mParams.UNSET;
                    mParams.bottomToTop = mParams.UNSET;
                    mParams.topToTop = mParams.UNSET;
                    mParams.topToBottom = mParams.UNSET;
                } if (endSide == TOP) {
                    mParams.baselineToTop = endID;
                    mParams.bottomToBottom = mParams.UNSET;
                    mParams.bottomToTop = mParams.UNSET;
                    mParams.topToTop = mParams.UNSET;
                    mParams.topToBottom = mParams.UNSET;
                } else if (endSide == BOTTOM) {
                    mParams.baselineToBottom = endID;
                    mParams.bottomToBottom = mParams.UNSET;
                    mParams.bottomToTop = mParams.UNSET;
                    mParams.topToTop = mParams.UNSET;
                    mParams.topToBottom = mParams.UNSET;
                } else {
                    throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined");
                }
                mParams.baselineMargin = margin;
                break;
            case START:
                if (endSide == START) {
                    mParams.startToStart = endID;
                    mParams.startToEnd = mParams.UNSET;
                } else if (endSide == END) {
                    mParams.startToEnd = endID;
                    mParams.startToStart = mParams.UNSET;
                } else {
                    throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined");
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    mParams.setMarginStart(margin);
                }
                break;
            case END:
                if (endSide == END) {
                    mParams.endToEnd = endID;
                    mParams.endToStart = mParams.UNSET;
                } else if (endSide == START) {
                    mParams.endToStart = endID;
                    mParams.endToEnd = mParams.UNSET;
                } else {
                    throw new IllegalArgumentException("right to " + sideToString(endSide) + " undefined");
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

                    mParams.setMarginEnd(margin);
                }

                break;
            default:
                throw new IllegalArgumentException(
                        sideToString(startSide) + " to " + sideToString(endSide) + " unknown");
        }
        return this;
    }

    private String sideToString(int side) {
        switch (side) {
            case LEFT:
                return "left";
            case RIGHT:
                return "right";
            case TOP:
                return "top";
            case BOTTOM:
                return "bottom";
            case BASELINE:
                return "baseline";
            case START:
                return "start";
            case END:
                return "end";
        }
        return "undefined";
    }

    public ConstraintProperties(View view) {
        ViewGroup.LayoutParams params = view.getLayoutParams();
        if (params instanceof ConstraintLayout.LayoutParams) {
            mParams = (ConstraintLayout.LayoutParams) params;
            mView = view;
        } else {
            throw new RuntimeException("Only children of ConstraintLayout.LayoutParams supported");
        }
    }

    public void apply() {
    }
}