ConstraintLayout.java

/*
 * 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 androidx.constraintlayout.widget;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.core.LinearSystem;
import androidx.constraintlayout.core.Metrics;
import androidx.constraintlayout.core.widgets.ConstraintAnchor;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import androidx.constraintlayout.core.widgets.Guideline;
import androidx.constraintlayout.core.widgets.Optimizer;
import androidx.constraintlayout.core.widgets.analyzer.BasicMeasure;
import androidx.core.view.ViewCompat;

import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.HashMap;

import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.*;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

/**
 * A {@code ConstraintLayout} is a {@link android.view.ViewGroup} which allows you
 * to position and size widgets in a flexible way.
 * <p>
 * <b>Note:</b> {@code ConstraintLayout} is available as a support library that you can use
 * on Android systems starting with API level 9 (Gingerbread).
 * As such, we are planning on enriching its API and capabilities over time.
 * This documentation will reflect those changes.
 * </p>
 * <p>
 * There are currently various types of constraints that you can use:
 * <ul>
 * <li>
 * <a href="#RelativePositioning">Relative positioning</a>
 * </li>
 * <li>
 * <a href="#Margins">Margins</a>
 * </li>
 * <li>
 * <a href="#CenteringPositioning">Centering positioning</a>
 * </li>
 * <li>
 * <a href="#CircularPositioning">Circular positioning</a>
 * </li>
 * <li>
 * <a href="#VisibilityBehavior">Visibility behavior</a>
 * </li>
 * <li>
 * <a href="#DimensionConstraints">Dimension constraints</a>
 * </li>
 * <li>
 * <a href="#Chains">Chains</a>
 * </li>
 * <li>
 * <a href="#VirtualHelpers">Virtual Helpers objects</a>
 * </li>
 * <li>
 * <a href="#Optimizer">Optimizer</a>
 * </li>
 * </ul>
 * </p>
 *
 * <p>
 * Note that you cannot have a circular dependency in constraints.
 * </p>
 * <p>
 * Also see {@link ConstraintLayout.LayoutParams
 * ConstraintLayout.LayoutParams} for layout attributes
 * </p>
 *
 * <div class="special reference">
 * <h3>Developer Guide</h3>
 *
 * <h4 id="RelativePositioning"> Relative positioning </h4>
 * <p>
 * Relative positioning is one of the basic building blocks of creating layouts in ConstraintLayout.
 * Those constraints allow you to position a given widget relative to another one. You can constrain
 * a widget on the horizontal and vertical axis:
 * <ul>
 * <li>Horizontal Axis: left, right, start and end sides</li>
 * <li>Vertical Axis: top, bottom sides and text baseline</li>
 * </ul>
 * <p>
 * The general concept is to constrain a given side of a widget to another side of any other widget.
 * <p>
 * For example, in order to position button B to the right of button A (Fig. 1):
 * <br><div align="center">
 * <img width="300px" src="resources/images/relative-positioning.png">
 * <br><b><i>Fig. 1 - Relative Positioning Example</i></b>
 * </div>
 * </p>
 * <p>
 * you would need to do:
 * </p>
 * <pre>{@code
 *         <Button android:id="@+id/buttonA" ... />
 *         <Button android:id="@+id/buttonB" ...
 *                 app:layout_constraintLeft_toRightOf="@+id/buttonA" />
 *         }
 *     </pre>
 * This tells the system that we want the left side of button B to be constrained to the right side of button A.
 * Such a position constraint means that the system will try to have both sides share the same location.
 * <br><div align="center" >
 * <img width="350px" src="resources/images/relative-positioning-constraints.png">
 * <br><b><i>Fig. 2 - Relative Positioning Constraints</i></b>
 * </div>
 *
 * <p>Here is the list of available constraints (Fig. 2):</p>
 * <ul>
 * <li>{@code layout_constraintLeft_toLeftOf}</li>
 * <li>{@code layout_constraintLeft_toRightOf}</li>
 * <li>{@code layout_constraintRight_toLeftOf}</li>
 * <li>{@code layout_constraintRight_toRightOf}</li>
 * <li>{@code layout_constraintTop_toTopOf}</li>
 * <li>{@code layout_constraintTop_toBottomOf}</li>
 * <li>{@code layout_constraintBottom_toTopOf}</li>
 * <li>{@code layout_constraintBottom_toBottomOf}</li>
 * <li>{@code layout_constraintBaseline_toBaselineOf}</li>
 * <li>{@code layout_constraintStart_toEndOf}</li>
 * <li>{@code layout_constraintStart_toStartOf}</li>
 * <li>{@code layout_constraintEnd_toStartOf}</li>
 * <li>{@code layout_constraintEnd_toEndOf}</li>
 * </ul>
 * <p>
 * They all take a reference {@code id} to another widget, or the {@code parent} (which will reference the parent container, i.e. the ConstraintLayout):
 * <pre>{@code
 *         <Button android:id="@+id/buttonB" ...
 *                 app:layout_constraintLeft_toLeftOf="parent" />
 *         }
 *     </pre>
 *
 * </p>
 *
 * <h4 id="Margins"> Margins </h4>
 * <p>
 * <div align="center" >
 * <img width="325px" src="resources/images/relative-positioning-margin.png">
 * <br><b><i>Fig. 3 - Relative Positioning Margins</i></b>
 * </div>
 * <p>If side margins are set, they will be applied to the corresponding constraints (if they exist) (Fig. 3), enforcing
 * the margin as a space between the target and the source side. The usual layout margin attributes can be used to this effect:
 * <ul>
 * <li>{@code android:layout_marginStart}</li>
 * <li>{@code android:layout_marginEnd}</li>
 * <li>{@code android:layout_marginLeft}</li>
 * <li>{@code android:layout_marginTop}</li>
 * <li>{@code android:layout_marginRight}</li>
 * <li>{@code android:layout_marginBottom}</li>
 * <li>{@code layout_marginBaseline}</li>
 * </ul>
 * <p>Note that a margin can only be positive or equal to zero, and takes a {@code Dimension}.</p>
 * <h4 id="GoneMargin"> Margins when connected to a GONE widget</h4>
 * <p>When a position constraint target's visibility is {@code View.GONE}, you can also indicate a different
 * margin value to be used using the following attributes:</p>
 * <ul>
 * <li>{@code layout_goneMarginStart}</li>
 * <li>{@code layout_goneMarginEnd}</li>
 * <li>{@code layout_goneMarginLeft}</li>
 * <li>{@code layout_goneMarginTop}</li>
 * <li>{@code layout_goneMarginRight}</li>
 * <li>{@code layout_goneMarginBottom}</li>
 * <li>{@code layout_goneMarginBaseline}</li>
 * </ul>
 * </p>
 *
 * </p>
 * <h4 id="CenteringPositioning"> Centering positioning and bias</h4>
 * <p>
 * A useful aspect of {@code ConstraintLayout} is in how it deals with "impossible" constraints. For example, if
 * we have something like:
 * <pre>{@code
 *         <androidx.constraintlayout.widget.ConstraintLayout ...>
 *             <Button android:id="@+id/button" ...
 *                 app:layout_constraintLeft_toLeftOf="parent"
 *                 app:layout_constraintRight_toRightOf="parent"/>
 *         </>
 *         }
 *     </pre>
 * </p>
 * <p>
 * Unless the {@code ConstraintLayout} happens to have the exact same size as the {@code Button}, both constraints
 * cannot be satisfied at the same time (both sides cannot be where we want them to be).
 * <p><div align="center" >
 * <img width="325px" src="resources/images/centering-positioning.png">
 * <br><b><i>Fig. 4 - Centering Positioning</i></b>
 * </div>
 * <p>
 * What happens in this case is that the constraints act like opposite forces
 * pulling the widget apart equally (Fig. 4); such that the widget will end up being centered in the parent container.
 * This will apply similarly for vertical constraints.
 * </p>
 * <h5 id="Bias">Bias</h5>
 * <p>
 * The default when encountering such opposite constraints is to center the widget; but you can tweak
 * the positioning to favor one side over another using the bias attributes:
 * <ul>
 * <li>{@code layout_constraintHorizontal_bias}</li>
 * <li>{@code layout_constraintVertical_bias}</li>
 * </ul>
 * <p><div align="center" >
 * <img width="325px" src="resources/images/centering-positioning-bias.png">
 * <br><b><i>Fig. 5 - Centering Positioning with Bias</i></b>
 * </div>
 * <p>
 * For example the following will make the left side with a 30% bias instead of the default 50%, such that the left side will be
 * shorter, with the widget leaning more toward the left side (Fig. 5):
 * </p>
 * <pre>{@code
 *         <androidx.constraintlayout.widget.ConstraintLayout ...>
 *             <Button android:id="@+id/button" ...
 *                 app:layout_constraintHorizontal_bias="0.3"
 *                 app:layout_constraintLeft_toLeftOf="parent"
 *                 app:layout_constraintRight_toRightOf="parent"/>
 *         </>
 *         }
 *     </pre>
 * Using bias, you can craft User Interfaces that will better adapt to screen sizes changes.
 * </p>
 * </p>
 *
 * <h4 id="CircularPositioning"> Circular positioning (<b>Added in 1.1</b>)</h4>
 * <p>
 * You can constrain a widget center relative to another widget center, at an angle and a distance. This allows
 * you to position a widget on a circle (see Fig. 6). The following attributes can be used:
 * <ul>
 * <li>{@code layout_constraintCircle} : references another widget id</li>
 * <li>{@code layout_constraintCircleRadius} : the distance to the other widget center</li>
 * <li>{@code layout_constraintCircleAngle} : which angle the widget should be at (in degrees, from 0 to 360)</li>
 * </ul>
 * <p><div align="center" >
 * <img width="325px" src="resources/images/circle1.png">
 * <img width="325px" src="resources/images/circle2.png">
 * <br><b><i>Fig. 6 - Circular Positioning</i></b>
 * </div>
 * <br><br>
 * <pre>{@code
 *  <Button android:id="@+id/buttonA" ... />
 *  <Button android:id="@+id/buttonB" ...
 *      app:layout_constraintCircle="@+id/buttonA"
 *      app:layout_constraintCircleRadius="100dp"
 *      app:layout_constraintCircleAngle="45" />
 *         }
 *     </pre>
 * </p>
 * <h4 id="VisibilityBehavior"> Visibility behavior </h4>
 * <p>
 * {@code ConstraintLayout} has a specific handling of widgets being marked as {@code View.GONE}.
 * <p>{@code GONE} widgets, as usual, are not going to be displayed and are not part of the layout itself (i.e. their actual dimensions
 * will not be changed if marked as {@code GONE}).
 *
 * <p>But in terms of the layout computations, {@code GONE} widgets are still part of it, with an important distinction:
 * <ul>
 * <li> For the layout pass, their dimension will be considered as zero (basically, they will be resolved to a point)</li>
 * <li> If they have constraints to other widgets they will still be respected, but any margins will be as if equals to zero</li>
 * </ul>
 *
 * <p><div align="center" >
 * <img width="350px" src="resources/images/visibility-behavior.png">
 * <br><b><i>Fig. 7 - Visibility Behavior</i></b>
 * </div>
 * <p>This specific behavior allows to build layouts where you can temporarily mark widgets as being {@code GONE},
 * without breaking the layout (Fig. 7), which can be particularly useful when doing simple layout animations.
 * <p><b>Note: </b>The margin used will be the margin that B had defined when connecting to A (see Fig. 7 for an example).
 * In some cases, this might not be the margin you want (e.g. A had a 100dp margin to the side of its container,
 * B only a 16dp to A, marking
 * A as gone, B will have a margin of 16dp to the container).
 * For this reason, you can specify an alternate
 * margin value to be used when the connection is to a widget being marked as gone (see <a href="#GoneMargin">the section above about the gone margin attributes</a>).
 * </p>
 *
 * <h4 id="DimensionConstraints"> Dimensions constraints </h4>
 * <h5>Minimum dimensions on ConstraintLayout</h5>
 * <p>
 * You can define minimum and maximum sizes for the {@code ConstraintLayout} itself:
 * <ul>
 * <li>{@code android:minWidth} set the minimum width for the layout</li>
 * <li>{@code android:minHeight} set the minimum height for the layout</li>
 * <li>{@code android:maxWidth} set the maximum width for the layout</li>
 * <li>{@code android:maxHeight} set the maximum height for the layout</li>
 * </ul>
 * Those minimum and maximum dimensions will be used by {@code ConstraintLayout} when its dimensions are set to {@code WRAP_CONTENT}.
 * </p>
 * <h5>Widgets dimension constraints</h5>
 * <p>
 * The dimension of the widgets can be specified by setting the {@code android:layout_width} and
 * {@code android:layout_height} attributes in 3 different ways:
 * <ul>
 * <li>Using a specific dimension (either a literal value such as {@code 123dp} or a {@code Dimension} reference)</li>
 * <li>Using {@code WRAP_CONTENT}, which will ask the widget to compute its own size</li>
 * <li>Using {@code 0dp}, which is the equivalent of "{@code MATCH_CONSTRAINT}"</li>
 * </ul>
 * <p><div align="center" >
 * <img width="325px" src="resources/images/dimension-match-constraints.png">
 * <br><b><i>Fig. 8 - Dimension Constraints</i></b>
 * </div>
 * The first two works in a similar fashion as other layouts. The last one will resize the widget in such a way as
 * matching the constraints that are set (see Fig. 8, (a) is wrap_content, (b) is 0dp). If margins are set, they will be taken in account
 * in the computation (Fig. 8, (c) with 0dp).
 * <p>
 * <b>Important: </b> {@code MATCH_PARENT} is not recommended for widgets contained in a {@code ConstraintLayout}. Similar behavior can
 * be defined by using {@code MATCH_CONSTRAINT} with the corresponding left/right or top/bottom constraints being set to {@code "parent"}.
 * </p>
 * </p>
 * <h5>WRAP_CONTENT : enforcing constraints (<i><b>Added in 1.1</b></i>)</h5>
 * <p>
 * If a dimension is set to {@code WRAP_CONTENT}, in versions before 1.1 they will be treated as a literal dimension -- meaning, constraints will
 * not limit the resulting dimension. While in general this is enough (and faster), in some situations, you might want to use {@code WRAP_CONTENT},
 * yet keep enforcing constraints to limit the resulting dimension. In that case, you can add one of the corresponding attribute:
 * <ul>
 * <li>{@code app:layout_constrainedWidth="true|false"}</li>
 * <li>{@code app:layout_constrainedHeight="true|false"}</li>
 * </ul>
 * </p>
 * <h5>MATCH_CONSTRAINT dimensions (<i><b>Added in 1.1</b></i>)</h5>
 * <p>
 * When a dimension is set to {@code MATCH_CONSTRAINT}, the default behavior is to have the resulting size take all the available space.
 * Several additional modifiers are available:
 * <ul>
 * <li>{@code layout_constraintWidth_min} and {@code layout_constraintHeight_min} : will set the minimum size for this dimension</li>
 * <li>{@code layout_constraintWidth_max} and {@code layout_constraintHeight_max} : will set the maximum size for this dimension</li>
 * <li>{@code layout_constraintWidth_percent} and {@code layout_constraintHeight_percent} : will set the size of this dimension as a percentage of the parent</li>
 * </ul>
 * <h6>Min and Max</h6>
 * The value indicated for min and max can be either a dimension in Dp, or "wrap", which will use the same value as what {@code WRAP_CONTENT} would do.
 * <h6>Percent dimension</h6>
 * To use percent, you need to set the following:
 * <ul>
 * <li>The dimension should be set to {@code MATCH_CONSTRAINT} (0dp)
 * <li>The default should be set to percent {@code app:layout_constraintWidth_default="percent"}
 * or {@code app:layout_constraintHeight_default="percent"}
 * <li>Then set the {@code layout_constraintWidth_percent}
 * or {@code layout_constraintHeight_percent} attributes to a value between 0 and 1
 * </ul>
 * </p>
 * <h5>Ratio</h5>
 * <p>
 * You can also define one dimension of a widget as a ratio of the other one. In order to do that, you
 * need to have at least one constrained dimension be set to {@code 0dp} (i.e., {@code MATCH_CONSTRAINT}), and set the
 * attribute {@code layout_constraintDimensionRatio} to a given ratio.
 * For example:
 * <pre>
 *         {@code
 *           <Button android:layout_width="wrap_content"
 *                   android:layout_height="0dp"
 *                   app:layout_constraintDimensionRatio="1:1" />
 *         }
 *     </pre>
 * will set the height of the button to be the same as its width.
 * </p>
 * <p> The ratio can be expressed either as:
 * <ul>
 * <li>a float value, representing a ratio between width and height</li>
 * <li>a ratio in the form "width:height"</li>
 * </ul>
 * </p>
 * <p>
 * You can also use ratio if both dimensions are set to {@code MATCH_CONSTRAINT} (0dp). In this case the system sets the
 * largest dimensions that satisfies all constraints and maintains the aspect ratio specified. To constrain one specific side
 * based on the dimensions of another, you can pre append {@code W,}" or {@code H,} to constrain the width or height
 * respectively.
 * For example,
 * If one dimension is constrained by two targets (e.g. width is 0dp and centered on parent) you can indicate which
 * side should be constrained, by adding the letter {@code W} (for constraining the width) or {@code H}
 * (for constraining the height) in front of the ratio, separated
 * by a comma:
 * <pre>
 *         {@code
 *           <Button android:layout_width="0dp"
 *                   android:layout_height="0dp"
 *                   app:layout_constraintDimensionRatio="H,16:9"
 *                   app:layout_constraintBottom_toBottomOf="parent"
 *                   app:layout_constraintTop_toTopOf="parent"/>
 *         }
 *     </pre>
 * will set the height of the button following a 16:9 ratio, while the width of the button will match the constraints
 * to its parent.
 *
 * </p>
 *
 * <h4 id="Chains">Chains</h4>
 * <p>Chains provide group-like behavior in a single axis (horizontally or vertically). The other axis can be constrained independently.</p>
 * <h5>Creating a chain</h5>
 * <p>
 * A set of widgets are considered a chain if they are linked together via a bi-directional connection (see Fig. 9, showing a minimal chain, with two widgets).
 * </p>
 * <p><div align="center" >
 * <img width="325px" src="resources/images/chains.png">
 * <br><b><i>Fig. 9 - Chain</i></b>
 * </div>
 * <p>
 * <h5>Chain heads</h5>
 * <p>
 * Chains are controlled by attributes set on the first element of the chain (the "head" of the chain):
 * </p>
 * <p><div align="center" >
 * <img width="400px" src="resources/images/chains-head.png">
 * <br><b><i>Fig. 10 - Chain Head</i></b>
 * </div>
 * <p>The head is the left-most widget for horizontal chains, and the top-most widget for vertical chains.</p>
 * <h5>Margins in chains</h5>
 * <p>If margins are specified on connections, they will be taken into account. In the case of spread chains, margins will be deducted from the allocated space.</p>
 * <h5>Chain Style</h5>
 * <p>When setting the attribute {@code layout_constraintHorizontal_chainStyle} or {@code layout_constraintVertical_chainStyle} on the first element of a chain,
 * the behavior of the chain will change according to the specified style (default is {@code CHAIN_SPREAD}).
 * <ul>
 * <li>{@code CHAIN_SPREAD} -- the elements will be spread out (default style)</li>
 * <li>Weighted chain -- in {@code CHAIN_SPREAD} mode, if some widgets are set to {@code MATCH_CONSTRAINT}, they will split the available space</li>
 * <li>{@code CHAIN_SPREAD_INSIDE} -- similar, but the endpoints of the chain will not be spread out</li>
 * <li>{@code CHAIN_PACKED} -- the elements of the chain will be packed together. The horizontal or vertical
 * bias attribute of the child will then affect the positioning of the packed elements</li>
 * </ul>
 * <p><div align="center" >
 * <img width="600px" src="resources/images/chains-styles.png">
 * <br><b><i>Fig. 11 - Chains Styles</i></b>
 * </div>
 * </p>
 * <h5>Weighted chains</h5>
 * <p>The default behavior of a chain is to spread the elements equally in the available space. If one or more elements are using {@code MATCH_CONSTRAINT}, they
 * will use the available empty space (equally divided among themselves). The attribute {@code layout_constraintHorizontal_weight} and {@code layout_constraintVertical_weight}
 * will control how the space will be distributed among the elements using {@code MATCH_CONSTRAINT}. For example, on a chain containing two elements using {@code MATCH_CONSTRAINT},
 * with the first element using a weight of 2 and the second a weight of 1, the space occupied by the first element will be twice that of the second element.</p>
 *
 * <h5>Margins and chains (<i><b>in 1.1</b></i>)</h5>
 * <p>When using margins on elements in a chain, the margins are additive.</p>
 * <p>For example, on a horizontal chain, if one element defines a right margin of 10dp and the next element
 * defines a left margin of 5dp, the resulting margin between those two elements is 15dp.</p>
 * <p>An item plus its margins are considered together when calculating leftover space used by chains
 * to position items. The leftover space does not contain the margins.</p>
 *
 * <h4 id="VirtualHelpers"> Virtual Helper objects </h4>
 * <p>In addition to the intrinsic capabilities detailed previously, you can also use special helper objects
 * in {@code ConstraintLayout} to help you with your layout. Currently, the {@code Guideline}{@see Guideline} object allows you to create
 * Horizontal and Vertical guidelines which are positioned relative to the {@code ConstraintLayout} container. Widgets can
 * then be positioned by constraining them to such guidelines. In <b>1.1</b>, {@code Barrier} and {@code Group} were added too.</p>
 *
 * <h4 id="Optimizer">Optimizer (<i><b>in 1.1</b></i>)</h4>
 * <p>
 * In 1.1 we exposed the constraints optimizer. You can decide which optimizations are applied by adding the tag <i>app:layout_optimizationLevel</i> to the ConstraintLayout element.
 * <ul>
 * <li><b>none</b> : no optimizations are applied</li>
 * <li><b>standard</b> : Default. Optimize direct and barrier constraints only</li>
 * <li><b>direct</b> : optimize direct constraints</li>
 * <li><b>barrier</b> : optimize barrier constraints</li>
 * <li><b>chain</b> : optimize chain constraints (experimental)</li>
 * <li><b>dimensions</b> : optimize dimensions measures (experimental), reducing the number of measures of match constraints elements</li>
 * </ul>
 * </p>
 * <p>This attribute is a mask, so you can decide to turn on or off specific optimizations by listing the ones you want.
 * For example: <i>app:layout_optimizationLevel="direct|barrier|chain"</i> </p>
 * </div>
 */
public class ConstraintLayout extends ViewGroup {
    /**
     * @hide
     */
    public static final String VERSION = "ConstraintLayout-2.1.0";
    private static final String TAG = "ConstraintLayout";

    private static final boolean USE_CONSTRAINTS_HELPER = true;
    private static final boolean DEBUG = LinearSystem.FULL_DEBUG;
    private static final boolean DEBUG_DRAW_CONSTRAINTS = false;
    private static final boolean MEASURE = false;
    private static final boolean OPTIMIZE_HEIGHT_CHANGE = false;

    SparseArray<View> mChildrenByIds = new SparseArray<>();

    // This array keep a list of helper objects if they are present
    private ArrayList<ConstraintHelper> mConstraintHelpers = new ArrayList<>(4);

    protected ConstraintWidgetContainer mLayoutWidget = new ConstraintWidgetContainer();

    private int mMinWidth = 0;
    private int mMinHeight = 0;
    private int mMaxWidth = Integer.MAX_VALUE;
    private int mMaxHeight = Integer.MAX_VALUE;

    protected boolean mDirtyHierarchy = true;
    private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD;
    private ConstraintSet mConstraintSet = null;
    protected ConstraintLayoutStates mConstraintLayoutSpec = null;

    private int mConstraintSetId = -1;

    private HashMap<String, Integer> mDesignIds = new HashMap<>();

    // Cache last measure
    private int mLastMeasureWidth = -1;
    private int mLastMeasureHeight = -1;
    int mLastMeasureWidthSize = -1;
    int mLastMeasureHeightSize = -1;
    int mLastMeasureWidthMode = MeasureSpec.UNSPECIFIED;
    int mLastMeasureHeightMode = MeasureSpec.UNSPECIFIED;
    private SparseArray<ConstraintWidget> mTempMapIdToWidget = new SparseArray<>();

    /**
     * @hide
     */
    public final static int DESIGN_INFO_ID = 0;
    private ConstraintsChangedListener mConstraintsChangedListener;
    private Metrics mMetrics;

    private static SharedValues sSharedValues = null;

    /**
     * Returns the SharedValues instance, creating it if it doesn't exist.
     *
     * @return the SharedValues instance
     */
    public static SharedValues getSharedValues() {
        if (sSharedValues == null) {
            sSharedValues = new SharedValues();
        }
        return sSharedValues;
    }

    /**
     * @hide
     */
    public void setDesignInformation(int type, Object value1, Object value2) {
        if (type == DESIGN_INFO_ID && value1 instanceof String && value2 instanceof Integer) {
            if (mDesignIds == null) {
                mDesignIds = new HashMap<>();
            }
            String name = (String) value1;
            int index = name.indexOf("/");
            if (index != -1) {
                name = name.substring(index + 1);
            }
            int id = (Integer) value2;
            mDesignIds.put(name, id);
        }
    }

    /**
     * @hide
     */
    public Object getDesignInformation(int type, Object value) {
        if (type == DESIGN_INFO_ID && value instanceof String) {
            String name = (String) value;
            if (mDesignIds != null && mDesignIds.containsKey(name)) {
                return mDesignIds.get(name);
            }
        }
        return null;
    }

    public ConstraintLayout(@NonNull Context context) {
        super(context);
        init(null, 0, 0);
    }

    public ConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    public ConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ConstraintLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    /**
     * {@hide}
     */
    @Override
    public void setId(int id) {
        mChildrenByIds.remove(getId());
        super.setId(id);
        mChildrenByIds.put(getId(), this);
    }

    // -------------------------------------------------------------------------------------------
    // Measure widgets callbacks
    // -------------------------------------------------------------------------------------------


    // -------------------------------------------------------------------------------------------

    class Measurer implements BasicMeasure.Measurer {
        ConstraintLayout layout;
        int paddingTop;
        int paddingBottom;
        int paddingWidth;
        int paddingHeight;
        int layoutWidthSpec;
        int layoutHeightSpec;

        public void captureLayoutInfo(int widthSpec, int heightSpec, int top, int bottom, int width, int height) {
            paddingTop = top;
            paddingBottom = bottom;
            paddingWidth = width;
            paddingHeight = height;
            layoutWidthSpec = widthSpec;
            layoutHeightSpec = heightSpec;
        }

        public Measurer(ConstraintLayout l) {
            layout = l;
        }

        @SuppressLint("WrongCall")
        @Override
        public final void measure(ConstraintWidget widget,
                                  BasicMeasure.Measure measure) {
            if (widget == null) {
                return;
            }
            if (widget.getVisibility() == GONE && !widget.isInPlaceholder()) {
                measure.measuredWidth = 0;
                measure.measuredHeight = 0;
                measure.measuredBaseline = 0;
                return;
            }
            if (widget.getParent() == null) {
                return;
            }

            long startMeasure;
            long endMeasure;

            if (MEASURE) {
                startMeasure = System.nanoTime();
            }

            ConstraintWidget.DimensionBehaviour horizontalBehavior = measure.horizontalBehavior;
            ConstraintWidget.DimensionBehaviour verticalBehavior = measure.verticalBehavior;

            int horizontalDimension = measure.horizontalDimension;
            int verticalDimension = measure.verticalDimension;

            int horizontalSpec = 0;
            int verticalSpec = 0;

            int heightPadding = paddingTop + paddingBottom;
            int widthPadding = paddingWidth;

            View child = (View) widget.getCompanionWidget();

            switch (horizontalBehavior) {
                case FIXED: {
                    horizontalSpec = MeasureSpec.makeMeasureSpec(horizontalDimension, MeasureSpec.EXACTLY);
                }
                break;
                case WRAP_CONTENT: {
                    horizontalSpec = getChildMeasureSpec(layoutWidthSpec, widthPadding, WRAP_CONTENT);
                }
                break;
                case MATCH_PARENT: {
                    // Horizontal spec must account for margin as well as padding here.
                    horizontalSpec = getChildMeasureSpec(layoutWidthSpec,
                            widthPadding + widget.getHorizontalMargin(), LayoutParams.MATCH_PARENT);
                }
                break;
                case MATCH_CONSTRAINT: {
                    horizontalSpec = getChildMeasureSpec(layoutWidthSpec, widthPadding, WRAP_CONTENT);
                    boolean shouldDoWrap = widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP;
                    if (measure.measureStrategy == BasicMeasure.Measure.TRY_GIVEN_DIMENSIONS
                        || measure.measureStrategy == BasicMeasure.Measure.USE_GIVEN_DIMENSIONS) {
                        // the solver gives us our new dimension, but if we previously had it measured with
                        // a wrap, it can be incorrect if the other side was also variable.
                        // So in that case, we have to double-check the other side is stable (else we can't
                        // just assume the wrap value will be correct).
                        boolean otherDimensionStable = child.getMeasuredHeight() == widget.getHeight();
                        boolean useCurrent = measure.measureStrategy == BasicMeasure.Measure.USE_GIVEN_DIMENSIONS
                                            || !shouldDoWrap
                                            || (shouldDoWrap && otherDimensionStable)
                                            || (child instanceof Placeholder)
                                            || (widget.isResolvedHorizontally());
                        if (useCurrent) {
                            horizontalSpec = MeasureSpec.makeMeasureSpec(widget.getWidth(), MeasureSpec.EXACTLY);
                        }
                    }
                }
                break;
            }

            switch (verticalBehavior) {
                case FIXED: {
                    verticalSpec = MeasureSpec.makeMeasureSpec(verticalDimension, MeasureSpec.EXACTLY);
                }
                break;
                case WRAP_CONTENT: {
                    verticalSpec = getChildMeasureSpec(layoutHeightSpec,
                            heightPadding, WRAP_CONTENT);
                }
                break;
                case MATCH_PARENT: {
                    // Vertical spec must account for margin as well as padding here.
                    verticalSpec = getChildMeasureSpec(layoutHeightSpec,
                            heightPadding + widget.getVerticalMargin(), LayoutParams.MATCH_PARENT);
                }
                break;
                case MATCH_CONSTRAINT: {
                    verticalSpec = getChildMeasureSpec(layoutHeightSpec,
                            heightPadding, WRAP_CONTENT);
                    boolean shouldDoWrap = widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP;
                    if (measure.measureStrategy == BasicMeasure.Measure.TRY_GIVEN_DIMENSIONS
                            || measure.measureStrategy == BasicMeasure.Measure.USE_GIVEN_DIMENSIONS) {
                        // the solver gives us our new dimension, but if we previously had it measured with
                        // a wrap, it can be incorrect if the other side was also variable.
                        // So in that case, we have to double-check the other side is stable (else we can't
                        // just assume the wrap value will be correct).
                        boolean otherDimensionStable = child.getMeasuredWidth() == widget.getWidth();
                        boolean useCurrent = measure.measureStrategy == BasicMeasure.Measure.USE_GIVEN_DIMENSIONS
                                            || !shouldDoWrap
                                            || (shouldDoWrap && otherDimensionStable)
                                            || (child instanceof Placeholder)
                                            || (widget.isResolvedVertically());
                        if (useCurrent) {
                            verticalSpec = MeasureSpec.makeMeasureSpec(widget.getHeight(), MeasureSpec.EXACTLY);
                        }
                    }
                }
                break;
            }

            ConstraintWidgetContainer container = (ConstraintWidgetContainer) widget.getParent();
            if (container != null && Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_CACHE_MEASURES)) {
                if (child.getMeasuredWidth() == widget.getWidth()
                        // note: the container check replicates legacy behavior, but we might want
                        // to not enforce that in 3.0
                        && child.getMeasuredWidth() < container.getWidth()
                        && child.getMeasuredHeight() == widget.getHeight()
                        && child.getMeasuredHeight() < container.getHeight()
                        && child.getBaseline() == widget.getBaselineDistance()
                        && !widget.isMeasureRequested()
                ) {
                    boolean similar = isSimilarSpec(widget.getLastHorizontalMeasureSpec(), horizontalSpec, widget.getWidth())
                            && isSimilarSpec(widget.getLastVerticalMeasureSpec(), verticalSpec, widget.getHeight());
                    if (similar) {
                        measure.measuredWidth = widget.getWidth();
                        measure.measuredHeight = widget.getHeight();
                        measure.measuredBaseline = widget.getBaselineDistance();
                        // if the dimensions of the solver widget are already the same as the real view, no need to remeasure.
                        if (DEBUG) {
                            System.out.println("SKIPPED " + widget);
                        }
                        return;
                    }
                }
            }

            boolean horizontalMatchConstraints = (horizontalBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
            boolean verticalMatchConstraints = (verticalBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);

            boolean verticalDimensionKnown = (verticalBehavior == ConstraintWidget.DimensionBehaviour.MATCH_PARENT
                    || verticalBehavior == ConstraintWidget.DimensionBehaviour.FIXED);
            boolean horizontalDimensionKnown = (horizontalBehavior == ConstraintWidget.DimensionBehaviour.MATCH_PARENT
                    || horizontalBehavior == ConstraintWidget.DimensionBehaviour.FIXED);
            boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0;
            boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0;

            if (child == null) {
                return;
            }
            LayoutParams params = (LayoutParams) child.getLayoutParams();

            int width = 0;
            int height = 0;
            int baseline = 0;

            if ((measure.measureStrategy == BasicMeasure.Measure.TRY_GIVEN_DIMENSIONS
                    || measure.measureStrategy == BasicMeasure.Measure.USE_GIVEN_DIMENSIONS) ||
                    !(horizontalMatchConstraints && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
                            && verticalMatchConstraints && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD)) {

                if (child instanceof VirtualLayout && widget instanceof androidx.constraintlayout.core.widgets.VirtualLayout) {
                    androidx.constraintlayout.core.widgets.VirtualLayout layout = (androidx.constraintlayout.core.widgets.VirtualLayout) widget;
                    ((VirtualLayout) child).onMeasure(layout, horizontalSpec, verticalSpec);
                } else {
                    child.measure(horizontalSpec, verticalSpec);
                }
                widget.setLastMeasureSpec(horizontalSpec, verticalSpec);

                int w = child.getMeasuredWidth();
                int h = child.getMeasuredHeight();
                baseline = child.getBaseline();

                width = w;
                height = h;

                if (DEBUG) {
                    String measurement = MeasureSpec.toString(horizontalSpec) + " x " + MeasureSpec.toString(verticalSpec) + " => " + width + " x " + height;
                    System.out.println("    (M) measure " + " (" + widget.getDebugName() + ") : " + measurement);
                }

                if (widget.mMatchConstraintMinWidth > 0) {
                    width = Math.max(widget.mMatchConstraintMinWidth, width);
                }
                if (widget.mMatchConstraintMaxWidth > 0) {
                    width = Math.min(widget.mMatchConstraintMaxWidth, width);
                }
                if (widget.mMatchConstraintMinHeight > 0) {
                    height = Math.max(widget.mMatchConstraintMinHeight, height);
                }
                if (widget.mMatchConstraintMaxHeight > 0) {
                    height = Math.min(widget.mMatchConstraintMaxHeight, height);
                }

                boolean optimizeDirect = Optimizer.enabled(mOptimizationLevel, Optimizer.OPTIMIZATION_DIRECT);
                if (!optimizeDirect) {
                    if (horizontalUseRatio && verticalDimensionKnown) {
                        float ratio = widget.mDimensionRatio;
                        width = (int) (0.5f + height * ratio);
                    } else if (verticalUseRatio && horizontalDimensionKnown) {
                        float ratio = widget.mDimensionRatio;
                        height = (int) (0.5f + width / ratio);
                    }
                }

                if (w != width || h != height) {
                    if (w != width) {
                        horizontalSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
                    }
                    if (h != height) {
                        verticalSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
                    }
                    child.measure(horizontalSpec, verticalSpec);

                    widget.setLastMeasureSpec(horizontalSpec, verticalSpec);
                    width = child.getMeasuredWidth();
                    height = child.getMeasuredHeight();
                    baseline = child.getBaseline();
                    if (DEBUG) {
                        String measurement2 = MeasureSpec.toString(horizontalSpec) + " x " + MeasureSpec.toString(verticalSpec) + " => " + width + " x " + height;
                        System.out.println("measure (b) " + widget.getDebugName() + " : " + measurement2);
                    }
                }

            }

            boolean hasBaseline = baseline != -1;

            measure.measuredNeedsSolverPass = (width != measure.horizontalDimension)
                    || (height != measure.verticalDimension);
            if (params.needsBaseline) {
                hasBaseline = true;
            }
            if (hasBaseline && baseline != -1 && widget.getBaselineDistance() != baseline) {
                measure.measuredNeedsSolverPass = true;
            }
            measure.measuredWidth = width;
            measure.measuredHeight = height;
            measure.measuredHasBaseline = hasBaseline;
            measure.measuredBaseline = baseline;
            if (MEASURE) {
                endMeasure = System.nanoTime();
                if (mMetrics != null) {
                    mMetrics.measuresWidgetsDuration += (endMeasure - startMeasure);
                }
            }
        }

        /**
         * Returns true if the previous measure spec is equivalent to the new one.
         * - if it's the same...
         * - if it's not, but the previous was AT_MOST or UNSPECIFIED and the new one
         *   is EXACTLY with the same size.
         *
         * @param lastMeasureSpec
         * @param spec
         * @param widgetSize
         * @return
         */
        private boolean isSimilarSpec(int lastMeasureSpec, int spec, int widgetSize) {
            if (lastMeasureSpec == spec) {
                return true;
            }
            int lastMode = MeasureSpec.getMode(lastMeasureSpec);
            int lastSize = MeasureSpec.getSize(lastMeasureSpec);
            int mode = MeasureSpec.getMode(spec);
            int size = MeasureSpec.getSize(spec);
            if (mode == MeasureSpec.EXACTLY
                    && (lastMode == MeasureSpec.AT_MOST || lastMode == MeasureSpec.UNSPECIFIED)
                    && widgetSize == size) {
                return true;
            }
            return false;
        }

        @Override
        public final void didMeasures() {
            final int widgetsCount = layout.getChildCount();
            for (int i = 0; i < widgetsCount; i++) {
                final View child = layout.getChildAt(i);
                if (child instanceof Placeholder) {
                    ((Placeholder) child).updatePostMeasure(layout);
                }
            }
            // TODO refactor into an updatePostMeasure interface
            final int helperCount = layout.mConstraintHelpers.size();
            if (helperCount > 0) {
                for (int i = 0; i < helperCount; i++) {
                    ConstraintHelper helper = layout.mConstraintHelpers.get(i);
                    helper.updatePostMeasure(layout);
                }
            }
        }
    }

    Measurer mMeasurer = new Measurer(this);

    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        mLayoutWidget.setCompanionWidget(this);
        mLayoutWidget.setMeasurer(mMeasurer);
        mChildrenByIds.put(getId(), this);
        mConstraintSet = null;
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ConstraintLayout_Layout, defStyleAttr, defStyleRes);
            final int N = a.getIndexCount();
            for (int i = 0; i < N; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.ConstraintLayout_Layout_android_minWidth) {
                    mMinWidth = a.getDimensionPixelOffset(attr, mMinWidth);
                } else if (attr == R.styleable.ConstraintLayout_Layout_android_minHeight) {
                    mMinHeight = a.getDimensionPixelOffset(attr, mMinHeight);
                } else if (attr == R.styleable.ConstraintLayout_Layout_android_maxWidth) {
                    mMaxWidth = a.getDimensionPixelOffset(attr, mMaxWidth);
                } else if (attr == R.styleable.ConstraintLayout_Layout_android_maxHeight) {
                    mMaxHeight = a.getDimensionPixelOffset(attr, mMaxHeight);
                } else if (attr == R.styleable.ConstraintLayout_Layout_layout_optimizationLevel) {
                    mOptimizationLevel = a.getInt(attr, mOptimizationLevel);
                } else if (attr == R.styleable.ConstraintLayout_Layout_layoutDescription) {
                    int id = a.getResourceId(attr, 0);
                    if (id != 0) {
                        try {
                            parseLayoutDescription(id);
                        } catch (Resources.NotFoundException e) {
                            mConstraintLayoutSpec = null;
                        }
                    }
                } else if (attr == R.styleable.ConstraintLayout_Layout_constraintSet) {
                    int id = a.getResourceId(attr, 0);
                    try {
                        mConstraintSet = new ConstraintSet();
                        mConstraintSet.load(getContext(), id);
                    } catch (Resources.NotFoundException e) {
                        mConstraintSet = null;
                    }
                    mConstraintSetId = id;
                }
            }
            a.recycle();
        }
        mLayoutWidget.setOptimizationLevel(mOptimizationLevel);
    }

    /**
     * Subclasses can override the handling of layoutDescription
     *
     * @param id
     */
    protected void parseLayoutDescription(int id) {
        mConstraintLayoutSpec = new ConstraintLayoutStates(getContext(), this, id);
    }

    /**
     * {@hide}
     */
    @Override
    public void onViewAdded(View view) {
        super.onViewAdded(view);
        ConstraintWidget widget = getViewWidget(view);
        if (view instanceof androidx.constraintlayout.widget.Guideline) {
            if (!(widget instanceof Guideline)) {
                LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
                layoutParams.widget = new Guideline();
                layoutParams.isGuideline = true;
                ((Guideline) layoutParams.widget).setOrientation(layoutParams.orientation);
            }
        }
        if (view instanceof ConstraintHelper) {
            ConstraintHelper helper = (ConstraintHelper) view;
            helper.validateParams();
            LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
            layoutParams.isHelper = true;
            if (!mConstraintHelpers.contains(helper)) {
                mConstraintHelpers.add(helper);
            }
        }
        mChildrenByIds.put(view.getId(), view);
        mDirtyHierarchy = true;
    }

    /**
     * {@hide}
     */
    @Override
    public void onViewRemoved(View view) {
        super.onViewRemoved(view);
        mChildrenByIds.remove(view.getId());
        ConstraintWidget widget = getViewWidget(view);
        mLayoutWidget.remove(widget);
        mConstraintHelpers.remove(view);
        mDirtyHierarchy = true;
    }

    /**
     * Set the min width for this view
     *
     * @param value
     */
    public void setMinWidth(int value) {
        if (value == mMinWidth) {
            return;
        }
        mMinWidth = value;
        requestLayout();
    }

    /**
     * Set the min height for this view
     *
     * @param value
     */
    public void setMinHeight(int value) {
        if (value == mMinHeight) {
            return;
        }
        mMinHeight = value;
        requestLayout();
    }

    /**
     * The minimum width of this view.
     *
     * @return The minimum width of this view
     * @see #setMinWidth(int)
     */
    public int getMinWidth() {
        return mMinWidth;
    }

    /**
     * The minimum height of this view.
     *
     * @return The minimum height of this view
     * @see #setMinHeight(int)
     */
    public int getMinHeight() {
        return mMinHeight;
    }

    /**
     * Set the max width for this view
     *
     * @param value
     */
    public void setMaxWidth(int value) {
        if (value == mMaxWidth) {
            return;
        }
        mMaxWidth = value;
        requestLayout();
    }

    /**
     * Set the max height for this view
     *
     * @param value
     */
    public void setMaxHeight(int value) {
        if (value == mMaxHeight) {
            return;
        }
        mMaxHeight = value;
        requestLayout();
    }

    /*
     * The maximum width of this view.
     *
     * @return The maximum width of this view
     *
     * @see #setMaxWidth(int)
     */
    public int getMaxWidth() {
        return mMaxWidth;
    }

    /**
     * The maximum height of this view.
     *
     * @return The maximum height of this view
     * @see #setMaxHeight(int)
     */
    public int getMaxHeight() {
        return mMaxHeight;
    }

    private boolean updateHierarchy() {
        final int count = getChildCount();

        boolean recompute = false;
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.isLayoutRequested()) {
                recompute = true;
                break;
            }
        }
        if (recompute) {
            setChildrenConstraints();
        }
        return recompute;
    }

    private void setChildrenConstraints() {
        final boolean isInEditMode = DEBUG || isInEditMode();

        final int count = getChildCount();

        // Make sure everything is fully reset before anything else
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            ConstraintWidget widget = getViewWidget(child);
            if (widget == null) {
                continue;
            }
            widget.reset();
        }

        if (isInEditMode) {
            // In design mode, let's make sure we keep track of the ids; in Studio, a build step
            // might not have been done yet, so asking the system for ids can break. So to be safe,
            // we save the current ids, which helpers can ask for.
            for (int i = 0; i < count; i++) {
                final View view = getChildAt(i);
                try {
                    String IdAsString = getResources().getResourceName(view.getId());
                    setDesignInformation(DESIGN_INFO_ID, IdAsString, view.getId());
                    int slashIndex = IdAsString.indexOf('/');
                    if (slashIndex != -1) {
                        IdAsString = IdAsString.substring(slashIndex + 1);
                    }
                    getTargetWidget(view.getId()).setDebugName(IdAsString);
                } catch (Resources.NotFoundException e) {
                    // nothing
                }
            }
        } else if (DEBUG) {
            mLayoutWidget.setDebugName("root");
            for (int i = 0; i < count; i++) {
                final View view = getChildAt(i);
                try {
                    String IdAsString = getResources().getResourceName(view.getId());
                    setDesignInformation(DESIGN_INFO_ID, IdAsString, view.getId());
                    int slashIndex = IdAsString.indexOf('/');
                    if (slashIndex != -1) {
                        IdAsString = IdAsString.substring(slashIndex + 1);
                    }
                    getTargetWidget(view.getId()).setDebugName(IdAsString);
                } catch (Resources.NotFoundException e) {
                    // nothing
                }
            }
        }

        if (USE_CONSTRAINTS_HELPER && mConstraintSetId != -1) {
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getId() == mConstraintSetId && child instanceof Constraints) {
                    mConstraintSet = ((Constraints) child).getConstraintSet();
                }
            }
        }

        if (mConstraintSet != null) {
            mConstraintSet.applyToInternal(this, true);
        }

        mLayoutWidget.removeAllChildren();

        final int helperCount = mConstraintHelpers.size();
        if (helperCount > 0) {
            for (int i = 0; i < helperCount; i++) {
                ConstraintHelper helper = mConstraintHelpers.get(i);
                helper.updatePreLayout(this);
            }
        }

        // TODO refactor into an updatePreLayout interface
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child instanceof Placeholder) {
                ((Placeholder) child).updatePreLayout(this);
            }
        }

        mTempMapIdToWidget.clear();
        mTempMapIdToWidget.put(PARENT_ID, mLayoutWidget);
        mTempMapIdToWidget.put(getId(), mLayoutWidget);
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            ConstraintWidget widget = getViewWidget(child);
            mTempMapIdToWidget.put(child.getId(), widget);
        }

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            ConstraintWidget widget = getViewWidget(child);
            if (widget == null) {
                continue;
            }
            final LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
            mLayoutWidget.add(widget);
            applyConstraintsFromLayoutParams(isInEditMode, child, widget, layoutParams, mTempMapIdToWidget);
        }
    }


    protected void applyConstraintsFromLayoutParams(boolean isInEditMode,
                                                    View child,
                                                    ConstraintWidget widget, LayoutParams layoutParams,
                                                    SparseArray<ConstraintWidget> idToWidget) {

        layoutParams.validate();
        layoutParams.helped = false;

        widget.setVisibility(child.getVisibility());
        if (layoutParams.isInPlaceholder) {
            widget.setInPlaceholder(true);
            widget.setVisibility(View.GONE);
        }
        widget.setCompanionWidget(child);

        if (child instanceof ConstraintHelper) {
            ConstraintHelper helper = (ConstraintHelper) child;
            helper.resolveRtl(widget, mLayoutWidget.isRtl());
        }
        if (layoutParams.isGuideline) {
            Guideline guideline = (Guideline) widget;
            int resolvedGuideBegin = layoutParams.resolvedGuideBegin;
            int resolvedGuideEnd = layoutParams.resolvedGuideEnd;
            float resolvedGuidePercent = layoutParams.resolvedGuidePercent;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
                resolvedGuideBegin = layoutParams.guideBegin;
                resolvedGuideEnd = layoutParams.guideEnd;
                resolvedGuidePercent = layoutParams.guidePercent;
            }
            if (resolvedGuidePercent != UNSET) {
                guideline.setGuidePercent(resolvedGuidePercent);
            } else if (resolvedGuideBegin != UNSET) {
                guideline.setGuideBegin(resolvedGuideBegin);
            } else if (resolvedGuideEnd != UNSET) {
                guideline.setGuideEnd(resolvedGuideEnd);
            }
        } else {
            // Get the left/right constraints resolved for RTL
            int resolvedLeftToLeft = layoutParams.resolvedLeftToLeft;
            int resolvedLeftToRight = layoutParams.resolvedLeftToRight;
            int resolvedRightToLeft = layoutParams.resolvedRightToLeft;
            int resolvedRightToRight = layoutParams.resolvedRightToRight;
            int resolveGoneLeftMargin = layoutParams.resolveGoneLeftMargin;
            int resolveGoneRightMargin = layoutParams.resolveGoneRightMargin;
            float resolvedHorizontalBias = layoutParams.resolvedHorizontalBias;

            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
                // Pre JB MR1, left/right should take precedence, unless they are
                // not defined and somehow a corresponding start/end constraint exists
                resolvedLeftToLeft = layoutParams.leftToLeft;
                resolvedLeftToRight = layoutParams.leftToRight;
                resolvedRightToLeft = layoutParams.rightToLeft;
                resolvedRightToRight = layoutParams.rightToRight;
                resolveGoneLeftMargin = layoutParams.goneLeftMargin;
                resolveGoneRightMargin = layoutParams.goneRightMargin;
                resolvedHorizontalBias = layoutParams.horizontalBias;

                if (resolvedLeftToLeft == UNSET && resolvedLeftToRight == UNSET) {
                    if (layoutParams.startToStart != UNSET) {
                        resolvedLeftToLeft = layoutParams.startToStart;
                    } else if (layoutParams.startToEnd != UNSET) {
                        resolvedLeftToRight = layoutParams.startToEnd;
                    }
                }
                if (resolvedRightToLeft == UNSET && resolvedRightToRight == UNSET) {
                    if (layoutParams.endToStart != UNSET) {
                        resolvedRightToLeft = layoutParams.endToStart;
                    } else if (layoutParams.endToEnd != UNSET) {
                        resolvedRightToRight = layoutParams.endToEnd;
                    }
                }
            }

            // Circular constraint
            if (layoutParams.circleConstraint != UNSET) {
                ConstraintWidget target = idToWidget.get(layoutParams.circleConstraint);
                if (target != null) {
                    widget.connectCircularConstraint(target, layoutParams.circleAngle, layoutParams.circleRadius);
                }
            } else {
                // Left constraint
                if (resolvedLeftToLeft != UNSET) {
                    ConstraintWidget target = idToWidget.get(resolvedLeftToLeft);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.LEFT, target,
                                ConstraintAnchor.Type.LEFT, layoutParams.leftMargin,
                                resolveGoneLeftMargin);
                    }
                } else if (resolvedLeftToRight != UNSET) {
                    ConstraintWidget target = idToWidget.get(resolvedLeftToRight);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.LEFT, target,
                                ConstraintAnchor.Type.RIGHT, layoutParams.leftMargin,
                                resolveGoneLeftMargin);
                    }
                }

                // Right constraint
                if (resolvedRightToLeft != UNSET) {
                    ConstraintWidget target = idToWidget.get(resolvedRightToLeft);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.RIGHT, target,
                                ConstraintAnchor.Type.LEFT, layoutParams.rightMargin,
                                resolveGoneRightMargin);
                    }
                } else if (resolvedRightToRight != UNSET) {
                    ConstraintWidget target = idToWidget.get(resolvedRightToRight);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.RIGHT, target,
                                ConstraintAnchor.Type.RIGHT, layoutParams.rightMargin,
                                resolveGoneRightMargin);
                    }
                }

                // Top constraint
                if (layoutParams.topToTop != UNSET) {
                    ConstraintWidget target = idToWidget.get(layoutParams.topToTop);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.TOP, target,
                                ConstraintAnchor.Type.TOP, layoutParams.topMargin,
                                layoutParams.goneTopMargin);
                    }
                } else if (layoutParams.topToBottom != UNSET) {
                    ConstraintWidget target = idToWidget.get(layoutParams.topToBottom);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.TOP, target,
                                ConstraintAnchor.Type.BOTTOM, layoutParams.topMargin,
                                layoutParams.goneTopMargin);
                    }
                }

                // Bottom constraint
                if (layoutParams.bottomToTop != UNSET) {
                    ConstraintWidget target = idToWidget.get(layoutParams.bottomToTop);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.BOTTOM, target,
                                ConstraintAnchor.Type.TOP, layoutParams.bottomMargin,
                                layoutParams.goneBottomMargin);
                    }
                } else if (layoutParams.bottomToBottom != UNSET) {
                    ConstraintWidget target = idToWidget.get(layoutParams.bottomToBottom);
                    if (target != null) {
                        widget.immediateConnect(ConstraintAnchor.Type.BOTTOM, target,
                                ConstraintAnchor.Type.BOTTOM, layoutParams.bottomMargin,
                                layoutParams.goneBottomMargin);
                    }
                }

                // Baseline constraint
                if (layoutParams.baselineToBaseline != UNSET) {
                    setWidgetBaseline(widget, layoutParams, idToWidget,
                            layoutParams.baselineToBaseline, ConstraintAnchor.Type.BASELINE);
                } else if (layoutParams.baselineToTop != UNSET) {
                    setWidgetBaseline(widget, layoutParams, idToWidget,
                            layoutParams.baselineToTop, ConstraintAnchor.Type.TOP);
                } else if (layoutParams.baselineToBottom != UNSET) {
                    setWidgetBaseline(widget, layoutParams, idToWidget,
                            layoutParams.baselineToBottom, ConstraintAnchor.Type.BOTTOM);
                }

                if (resolvedHorizontalBias >= 0) {
                    widget.setHorizontalBiasPercent(resolvedHorizontalBias);
                }
                if (layoutParams.verticalBias >= 0) {
                    widget.setVerticalBiasPercent(layoutParams.verticalBias);
                }
            }

            if (isInEditMode && ((layoutParams.editorAbsoluteX != UNSET)
                    || (layoutParams.editorAbsoluteY != UNSET))) {
                widget.setOrigin(layoutParams.editorAbsoluteX, layoutParams.editorAbsoluteY);
            }

            // FIXME: need to agree on the correct magic value for this rather than simply using zero.
            if (!layoutParams.horizontalDimensionFixed) {
                if (layoutParams.width == MATCH_PARENT) {
                    if (layoutParams.constrainedWidth) {
                        widget.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
                    } else {
                        widget.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_PARENT);
                    }
                    widget.getAnchor(ConstraintAnchor.Type.LEFT).mMargin = layoutParams.leftMargin;
                    widget.getAnchor(ConstraintAnchor.Type.RIGHT).mMargin = layoutParams.rightMargin;
                } else {
                    widget.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
                    widget.setWidth(0);
                }
            } else {
                widget.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED);
                widget.setWidth(layoutParams.width);
                if (layoutParams.width == WRAP_CONTENT) {
                    widget.setHorizontalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.WRAP_CONTENT);
                }
            }
            if (!layoutParams.verticalDimensionFixed) {
                if (layoutParams.height == MATCH_PARENT) {
                    if (layoutParams.constrainedHeight) {
                        widget.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
                    } else {
                        widget.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_PARENT);
                    }
                    widget.getAnchor(ConstraintAnchor.Type.TOP).mMargin = layoutParams.topMargin;
                    widget.getAnchor(ConstraintAnchor.Type.BOTTOM).mMargin = layoutParams.bottomMargin;
                } else {
                    widget.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
                    widget.setHeight(0);
                }
            } else {
                widget.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.FIXED);
                widget.setHeight(layoutParams.height);
                if (layoutParams.height == WRAP_CONTENT) {
                    widget.setVerticalDimensionBehaviour(ConstraintWidget.DimensionBehaviour.WRAP_CONTENT);
                }
            }

            widget.setDimensionRatio(layoutParams.dimensionRatio);
            widget.setHorizontalWeight(layoutParams.horizontalWeight);
            widget.setVerticalWeight(layoutParams.verticalWeight);
            widget.setHorizontalChainStyle(layoutParams.horizontalChainStyle);
            widget.setVerticalChainStyle(layoutParams.verticalChainStyle);
            widget.setWrapBehaviorInParent(layoutParams.wrapBehaviorInParent);
            widget.setHorizontalMatchStyle(layoutParams.matchConstraintDefaultWidth,
                    layoutParams.matchConstraintMinWidth, layoutParams.matchConstraintMaxWidth,
                    layoutParams.matchConstraintPercentWidth);
            widget.setVerticalMatchStyle(layoutParams.matchConstraintDefaultHeight,
                    layoutParams.matchConstraintMinHeight, layoutParams.matchConstraintMaxHeight,
                    layoutParams.matchConstraintPercentHeight);
        }
    }

    private void setWidgetBaseline(ConstraintWidget widget, LayoutParams layoutParams, SparseArray<ConstraintWidget> idToWidget, int baselineTarget, ConstraintAnchor.Type type) {
        View view = mChildrenByIds.get(baselineTarget);
        ConstraintWidget target = idToWidget.get(baselineTarget);
        if (target != null && view != null && view.getLayoutParams() instanceof LayoutParams) {
            layoutParams.needsBaseline = true;
            if (type == ConstraintAnchor.Type.BASELINE) { // baseline to baseline
                LayoutParams targetParams = (LayoutParams) view.getLayoutParams();
                targetParams.needsBaseline = true;
                targetParams.widget.setHasBaseline(true);
            }
            ConstraintAnchor baseline = widget.getAnchor(ConstraintAnchor.Type.BASELINE);
            ConstraintAnchor targetAnchor = target.getAnchor(type);
            baseline.connect(targetAnchor, layoutParams.baselineMargin, layoutParams.goneBaselineMargin, true);
            widget.setHasBaseline(true);
            widget.getAnchor(ConstraintAnchor.Type.TOP).reset();
            widget.getAnchor(ConstraintAnchor.Type.BOTTOM).reset();
        }
    }

    private final ConstraintWidget getTargetWidget(int id) {
        if (id == LayoutParams.PARENT_ID) {
            return mLayoutWidget;
        } else {
            View view = mChildrenByIds.get(id);
            if (view == null) {
                view = findViewById(id);
                if (view != null && view != this && view.getParent() == this) {
                    onViewAdded(view);
                }
            }
            if (view == this) {
                return mLayoutWidget;
            }
            return view == null ? null : ((LayoutParams) view.getLayoutParams()).widget;
        }
    }

    /**
     * @param view
     * @return
     * @hide
     */
    public final ConstraintWidget getViewWidget(View view) {
        if (view == this) {
            return mLayoutWidget;
        }
        if (view != null) {
            if (view.getLayoutParams() instanceof LayoutParams) {
                return ((LayoutParams) view.getLayoutParams()).widget;
            }
            view.setLayoutParams(generateLayoutParams(view.getLayoutParams()));
            if (view.getLayoutParams() instanceof LayoutParams) {
                return ((LayoutParams) view.getLayoutParams()).widget;
            }
        }
        return null;
    }

    /**
     * @param metrics
     * @hide Fills metrics object
     */
    public void fillMetrics(Metrics metrics) {
        mMetrics = metrics;
        mLayoutWidget.fillMetrics(metrics);
    }

    private int mOnMeasureWidthMeasureSpec = 0;
    private int mOnMeasureHeightMeasureSpec = 0;

    /**
     * Handles measuring a layout
     *
     * @param layout
     * @param optimizationLevel
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    protected void resolveSystem(ConstraintWidgetContainer layout,
                                 int optimizationLevel, int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int paddingY = Math.max(0, getPaddingTop());
        int paddingBottom = Math.max(0, getPaddingBottom());
        int paddingHeight = paddingY + paddingBottom;
        int paddingWidth = getPaddingWidth();
        int paddingX;
        mMeasurer.captureLayoutInfo(widthMeasureSpec, heightMeasureSpec, paddingY, paddingBottom, paddingWidth, paddingHeight);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            int paddingStart = Math.max(0, getPaddingStart());
            int paddingEnd = Math.max(0, getPaddingEnd());
            if (paddingStart > 0 || paddingEnd > 0) {
                if (isRtl()) {
                    paddingX = paddingEnd;
                } else {
                    paddingX = paddingStart;
                }
            } else {
                paddingX = Math.max(0, getPaddingLeft());
            }
        } else {
            paddingX = Math.max(0, getPaddingLeft());
        }

        widthSize -= paddingWidth;
        heightSize -= paddingHeight;

        setSelfDimensionBehaviour(layout, widthMode, widthSize, heightMode, heightSize);
        layout.measure(optimizationLevel, widthMode, widthSize, heightMode, heightSize,
                mLastMeasureWidth, mLastMeasureHeight, paddingX, paddingY);
    }

    /**
     * Handles calling setMeasuredDimension()
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     * @param measuredWidth
     * @param measuredHeight
     * @param isWidthMeasuredTooSmall
     * @param isHeightMeasuredTooSmall
     */
    protected void resolveMeasuredDimension(int widthMeasureSpec, int heightMeasureSpec,
                                            int measuredWidth, int measuredHeight,
                                            boolean isWidthMeasuredTooSmall, boolean isHeightMeasuredTooSmall) {
        int childState = 0;
        int heightPadding = mMeasurer.paddingHeight;
        int widthPadding = mMeasurer.paddingWidth;

        int androidLayoutWidth = measuredWidth + widthPadding;
        int androidLayoutHeight = measuredHeight + heightPadding;

        int resolvedWidthSize = resolveSizeAndState(androidLayoutWidth, widthMeasureSpec, childState);
        int resolvedHeightSize = resolveSizeAndState(androidLayoutHeight, heightMeasureSpec,
                childState << MEASURED_HEIGHT_STATE_SHIFT);
        resolvedWidthSize &= MEASURED_SIZE_MASK;
        resolvedHeightSize &= MEASURED_SIZE_MASK;
        resolvedWidthSize = Math.min(mMaxWidth, resolvedWidthSize);
        resolvedHeightSize = Math.min(mMaxHeight, resolvedHeightSize);
        if (isWidthMeasuredTooSmall) {
            resolvedWidthSize |= MEASURED_STATE_TOO_SMALL;
        }
        if (isHeightMeasuredTooSmall) {
            resolvedHeightSize |= MEASURED_STATE_TOO_SMALL;
        }
        setMeasuredDimension(resolvedWidthSize, resolvedHeightSize);
        mLastMeasureWidth = resolvedWidthSize;
        mLastMeasureHeight = resolvedHeightSize;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        long time = 0;
        if (DEBUG) {
            time = System.currentTimeMillis();
        }

        boolean sameSpecsAsPreviousMeasure = (mOnMeasureWidthMeasureSpec == widthMeasureSpec
                && mOnMeasureHeightMeasureSpec == heightMeasureSpec);
        sameSpecsAsPreviousMeasure = false; //TODO re-enable
        if (!mDirtyHierarchy && !sameSpecsAsPreviousMeasure) {
            // it's possible that, if we are already marked for a relayout, a view would not call to request a layout;
            // in that case we'd miss updating the hierarchy correctly (window insets change may do that -- we receive
            // a second onMeasure before onLayout).
            // We have to iterate on our children to verify that none set a request layout flag...
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.isLayoutRequested()) {
                    if (DEBUG) {
                        System.out.println("### CHILD " + child + " REQUESTED LAYOUT, FORCE DIRTY HIERARCHY");
                    }
                    mDirtyHierarchy = true;
                    break;
                }
            }
        }

        if (!mDirtyHierarchy) {
            if (sameSpecsAsPreviousMeasure) {
                resolveMeasuredDimension(widthMeasureSpec, heightMeasureSpec, mLayoutWidget.getWidth(), mLayoutWidget.getHeight(),
                        mLayoutWidget.isWidthMeasuredTooSmall(), mLayoutWidget.isHeightMeasuredTooSmall());
                return;
            }
            if (OPTIMIZE_HEIGHT_CHANGE
                    && mOnMeasureWidthMeasureSpec == widthMeasureSpec
                    && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                    && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST
                    && MeasureSpec.getMode(mOnMeasureHeightMeasureSpec) == MeasureSpec.AT_MOST) {
                int newSize = MeasureSpec.getSize(heightMeasureSpec);
                if (DEBUG) {
                    System.out.println("### COMPATIBLE REQ " + newSize + " >= ? " + mLayoutWidget.getHeight());
                }
                if (newSize >= mLayoutWidget.getHeight() && !mLayoutWidget.isHeightMeasuredTooSmall()) {
                    mOnMeasureWidthMeasureSpec = widthMeasureSpec;
                    mOnMeasureHeightMeasureSpec = heightMeasureSpec;
                    resolveMeasuredDimension(widthMeasureSpec, heightMeasureSpec, mLayoutWidget.getWidth(), mLayoutWidget.getHeight(),
                            mLayoutWidget.isWidthMeasuredTooSmall(), mLayoutWidget.isHeightMeasuredTooSmall());
                    return;
                }
            }
        }
        mOnMeasureWidthMeasureSpec = widthMeasureSpec;
        mOnMeasureHeightMeasureSpec = heightMeasureSpec;

        if (DEBUG) {
            System.out.println("### ON MEASURE " + mDirtyHierarchy + " of " + mLayoutWidget.getDebugName() + " onMeasure width: " + MeasureSpec.toString(widthMeasureSpec)
                    + " height: " + MeasureSpec.toString(heightMeasureSpec) + this);
        }

        mLayoutWidget.setRtl(isRtl());

        if (mDirtyHierarchy) {
            mDirtyHierarchy = false;
            if (updateHierarchy()) {
                mLayoutWidget.updateHierarchy();
            }
        }

        resolveSystem(mLayoutWidget, mOptimizationLevel, widthMeasureSpec, heightMeasureSpec);
        resolveMeasuredDimension(widthMeasureSpec, heightMeasureSpec, mLayoutWidget.getWidth(), mLayoutWidget.getHeight(),
                mLayoutWidget.isWidthMeasuredTooSmall(), mLayoutWidget.isHeightMeasuredTooSmall());

        if (DEBUG) {
            time = System.currentTimeMillis() - time;
            System.out.println(mLayoutWidget.getDebugName() + " (" + getChildCount() + ") DONE onMeasure width: " + MeasureSpec.toString(widthMeasureSpec)
                    + " height: " + MeasureSpec.toString(heightMeasureSpec) + " => " + mLastMeasureWidth + " x " + mLastMeasureHeight
                    + " lasted " + time
            );
        }
    }

    protected boolean isRtl() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            boolean isRtlSupported = (getContext().getApplicationInfo().flags & ApplicationInfo.FLAG_SUPPORTS_RTL) != 0;
            return isRtlSupported && (View.LAYOUT_DIRECTION_RTL == getLayoutDirection());
        }
        return false;
    }

    /**
     * Compute the padding width, taking in account RTL start/end padding if available and present.
     * @return padding width
     */
    private int getPaddingWidth() {
        int widthPadding = Math.max(0, getPaddingLeft()) + Math.max(0, getPaddingRight());
        int rtlPadding = 0;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            rtlPadding = Math.max(0, getPaddingStart()) + Math.max(0, getPaddingEnd());
        }
        if (rtlPadding > 0) {
            widthPadding = rtlPadding;
        }
        return widthPadding;
    }

    protected void setSelfDimensionBehaviour(ConstraintWidgetContainer layout, int widthMode, int widthSize, int heightMode, int heightSize) {

        int heightPadding = mMeasurer.paddingHeight;
        int widthPadding = mMeasurer.paddingWidth;

        ConstraintWidget.DimensionBehaviour widthBehaviour = ConstraintWidget.DimensionBehaviour.FIXED;
        ConstraintWidget.DimensionBehaviour heightBehaviour = ConstraintWidget.DimensionBehaviour.FIXED;

        int desiredWidth = 0;
        int desiredHeight = 0;
        final int childCount = getChildCount();

        switch (widthMode) {
            case MeasureSpec.AT_MOST: {
                widthBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
                desiredWidth = widthSize;
                if (childCount == 0) {
                    desiredWidth = Math.max(0, mMinWidth);
                }
            }
            break;
            case MeasureSpec.UNSPECIFIED: {
                widthBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
                if (childCount == 0) {
                    desiredWidth = Math.max(0, mMinWidth);
                }
            }
            break;
            case MeasureSpec.EXACTLY: {
                desiredWidth = Math.min(mMaxWidth - widthPadding, widthSize);
            }
        }
        switch (heightMode) {
            case MeasureSpec.AT_MOST: {
                heightBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
                desiredHeight = heightSize;
                if (childCount == 0) {
                    desiredHeight = Math.max(0, mMinHeight);
                }
            }
            break;
            case MeasureSpec.UNSPECIFIED: {
                heightBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
                if (childCount == 0) {
                    desiredHeight = Math.max(0, mMinHeight);
                }
            }
            break;
            case MeasureSpec.EXACTLY: {
                desiredHeight = Math.min(mMaxHeight - heightPadding, heightSize);
            }
        }

        if (desiredWidth != layout.getWidth() || desiredHeight != layout.getHeight()) {
            layout.invalidateMeasures();
        }
        layout.setX(0);
        layout.setY(0);
        layout.setMaxWidth(mMaxWidth - widthPadding);
        layout.setMaxHeight(mMaxHeight - heightPadding);
        layout.setMinWidth(0);
        layout.setMinHeight(0);
        layout.setHorizontalDimensionBehaviour(widthBehaviour);
        layout.setWidth(desiredWidth);
        layout.setVerticalDimensionBehaviour(heightBehaviour);
        layout.setHeight(desiredHeight);
        layout.setMinWidth(mMinWidth - widthPadding);
        layout.setMinHeight(mMinHeight - heightPadding);
    }

    /**
     * Set the State of the ConstraintLayout, causing it to load a particular ConstraintSet.
     * For states with variants the variant with matching width and height constraintSet will be chosen
     *
     * @param id           the constraint set state
     * @param screenWidth  the width of the screen in pixels
     * @param screenHeight the height of the screen in pixels
     */
    public void setState(int id, int screenWidth, int screenHeight) {
        if (mConstraintLayoutSpec != null) {
            mConstraintLayoutSpec.updateConstraints(id, screenWidth, screenHeight);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (DEBUG) {
            System.out.println(mLayoutWidget.getDebugName() + " onLayout changed: " + changed + " left: " + left + " top: " + top
                    + " right: " + right + " bottom: " + bottom + " (" + (right - left) + " x " + (bottom - top) + ")");
        }
        final int widgetsCount = getChildCount();
        final boolean isInEditMode = isInEditMode();
        for (int i = 0; i < widgetsCount; i++) {
            final View child = getChildAt(i);
            LayoutParams params = (LayoutParams) child.getLayoutParams();
            ConstraintWidget widget = params.widget;

            if (child.getVisibility() == GONE && !params.isGuideline && !params.isHelper && !params.isVirtualGroup && !isInEditMode) {
                // If we are in edit mode, let's layout the widget so that they are at "the right place"
                // visually in the editor (as we get our positions from layoutlib)
                continue;
            }
            if (params.isInPlaceholder) {
                continue;
            }
            int l = widget.getX();
            int t = widget.getY();
            int r = l + widget.getWidth();
            int b = t + widget.getHeight();

            if (DEBUG) {
                if (child.getVisibility() != View.GONE
                        && (child.getMeasuredWidth() != widget.getWidth()
                        || child.getMeasuredHeight() != widget.getHeight())) {
                    int deltaX = Math.abs(child.getMeasuredWidth() - widget.getWidth());
                    int deltaY = Math.abs(child.getMeasuredHeight() - widget.getHeight());
                    if (deltaX > 1 || deltaY > 1) {
                        System.out.println("child " + child + " measuredWidth " + child.getMeasuredWidth()
                                + " vs " + widget.getWidth() + " x measureHeight " + child.getMeasuredHeight()
                                + " vs " + widget.getHeight());
                    }
                }
            }

            child.layout(l, t, r, b);
            if (child instanceof Placeholder) {
                Placeholder holder = (Placeholder) child;
                View content = holder.getContent();
                if (content != null) {
                    content.setVisibility(VISIBLE);
                    content.layout(l, t, r, b);
                }
            }
        }
        final int helperCount = mConstraintHelpers.size();
        if (helperCount > 0) {
            for (int i = 0; i < helperCount; i++) {
                ConstraintHelper helper = mConstraintHelpers.get(i);
                helper.updatePostLayout(this);
            }
        }
    }

    /**
     * Set the optimization for the layout resolution.
     * <p>
     * The optimization can be any of the following:
     * <ul>
     * <li>Optimizer.OPTIMIZATION_NONE</li>
     * <li>Optimizer.OPTIMIZATION_STANDARD</li>
     * <li>a mask composed of specific optimizations</li>
     * </ul>
     * The mask can be composed of any combination of the following:
     * <ul>
     * <li>Optimizer.OPTIMIZATION_DIRECT  </li>
     * <li>Optimizer.OPTIMIZATION_BARRIER  </li>
     * <li>Optimizer.OPTIMIZATION_CHAIN  (experimental) </li>
     * <li>Optimizer.OPTIMIZATION_DIMENSIONS  (experimental) </li>
     * </ul>
     * Note that the current implementation of Optimizer.OPTIMIZATION_STANDARD is as a mask of DIRECT and BARRIER.
     * </p>
     *
     * @param level optimization level
     * @since 1.1
     */
    public void setOptimizationLevel(int level) {
        mOptimizationLevel = level;
        mLayoutWidget.setOptimizationLevel(level);
    }

    /**
     * Return the current optimization level for the layout resolution
     *
     * @return the current level
     * @since 1.1
     */
    public int getOptimizationLevel() {
        return mLayoutWidget.getOptimizationLevel();
    }

    /**
     * {@hide}
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    /**
     * Sets a ConstraintSet object to manage constraints. The ConstraintSet overrides LayoutParams of child views.
     *
     * @param set Layout children using ConstraintSet
     */
    public void setConstraintSet(ConstraintSet set) {
        mConstraintSet = set;
    }

    /**
     * @param id the view id
     * @return the child view, can return null
     * @hide Return a direct child view by its id if it exists
     */
    public View getViewById(int id) {
        return mChildrenByIds.get(id);
    }

    /**
     * @hide
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mConstraintHelpers != null) {
            final int helperCount = mConstraintHelpers.size();
            if (helperCount > 0) {
                for (int i = 0; i < helperCount; i++) {
                    ConstraintHelper helper = mConstraintHelpers.get(i);
                    helper.updatePreDraw(this);
                }
            }
        }

        super.dispatchDraw(canvas);

        if (DEBUG || isInEditMode()) {
            float cw = getWidth();
            float ch = getHeight();
            float ow = 1080;
            float oh = 1920;
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() == GONE) {
                    continue;
                }
                Object tag = child.getTag();
                if (tag != null && tag instanceof String) {
                    String coordinates = (String) tag;
                    String[] split = coordinates.split(",");
                    if (split.length == 4) {
                        int x = Integer.parseInt(split[0]);
                        int y = Integer.parseInt(split[1]);
                        int w = Integer.parseInt(split[2]);
                        int h = Integer.parseInt(split[3]);
                        x = (int) ((x / ow) * cw);
                        y = (int) ((y / oh) * ch);
                        w = (int) ((w / ow) * cw);
                        h = (int) ((h / oh) * ch);
                        Paint paint = new Paint();
                        paint.setColor(Color.RED);
                        canvas.drawLine(x, y, x + w, y, paint);
                        canvas.drawLine(x + w, y, x + w, y + h, paint);
                        canvas.drawLine(x + w, y + h, x, y + h, paint);
                        canvas.drawLine(x, y + h, x, y, paint);
                        paint.setColor(Color.GREEN);
                        canvas.drawLine(x, y, x + w, y + h, paint);
                        canvas.drawLine(x, y + h, x + w, y, paint);
                    }
                }
            }
        }
        if (DEBUG_DRAW_CONSTRAINTS) {
            final int count = getChildCount();
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                if (child.getVisibility() == GONE) {
                    continue;
                }
                ConstraintWidget widget = getViewWidget(child);
                if (widget.mTop.isConnected()) {
                    ConstraintWidget target = widget.mTop.mTarget.mOwner;
                    int x1 = widget.getX() + widget.getWidth() / 2;
                    int y1 = widget.getY();
                    int x2 = target.getX() + target.getWidth() / 2;
                    int y2 = 0;
                    if (widget.mTop.mTarget.mType == ConstraintAnchor.Type.TOP) {
                        y2 = target.getY();
                    } else {
                        y2 = target.getY() + target.getHeight();
                    }
                    Paint paint = new Paint();
                    paint.setColor(Color.RED);
                    paint.setStrokeWidth(4);
                    canvas.drawLine(x1, y1, x2, y2, paint);
                }
                if (widget.mBottom.isConnected()) {
                    ConstraintWidget target = widget.mBottom.mTarget.mOwner;
                    int x1 = widget.getX() + widget.getWidth() / 2;
                    int y1 = widget.getY() + widget.getHeight();
                    int x2 = target.getX() + target.getWidth() / 2;
                    int y2 = 0;
                    if (widget.mBottom.mTarget.mType == ConstraintAnchor.Type.TOP) {
                        y2 = target.getY();
                    } else {
                        y2 = target.getY() + target.getHeight();
                    }
                    Paint paint = new Paint();
                    paint.setStrokeWidth(4);
                    paint.setColor(Color.RED);
                    canvas.drawLine(x1, y1, x2, y2, paint);
                }
            }
        }
    }

    public void setOnConstraintsChanged(ConstraintsChangedListener constraintsChangedListener) {
        this.mConstraintsChangedListener = constraintsChangedListener;
        if (mConstraintLayoutSpec != null) {
            mConstraintLayoutSpec.setOnConstraintsChanged(constraintsChangedListener);
        }
    }

    /**
     * Load a layout description file from the resources.
     *
     * @param layoutDescription The resource id, or 0 to reset the layout description.
     */
    public void loadLayoutDescription(int layoutDescription) {
        if (layoutDescription != 0) {
            try {
                mConstraintLayoutSpec = new ConstraintLayoutStates(getContext(), this, layoutDescription);
            } catch (Resources.NotFoundException e) {
                mConstraintLayoutSpec = null;
            }
        } else {
            mConstraintLayoutSpec = null;
        }
    }

    /**
     * This class contains the different attributes specifying how a view want to be laid out inside
     * a {@link ConstraintLayout}. For building up constraints at run time, using {@link ConstraintSet} is recommended.
     */
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        /**
         * Dimension will be controlled by constraints.
         */
        public static final int MATCH_CONSTRAINT = 0;

        /**
         * References the id of the parent.
         */
        public static final int PARENT_ID = 0;

        /**
         * Defines an id that is not set.
         */
        public static final int UNSET = -1;


        /**
         * Defines an id that is not set.
         */
        public static final int GONE_UNSET = Integer.MIN_VALUE;


        /**
         * The horizontal orientation.
         */
        public static final int HORIZONTAL = ConstraintWidget.HORIZONTAL;

        /**
         * The vertical orientation.
         */
        public static final int VERTICAL = ConstraintWidget.VERTICAL;

        /**
         * The left side of a view.
         */
        public static final int LEFT = 1;

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

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

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

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

        /**
         * 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 = 6;

        /**
         * 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 = 7;

        /**
         * Circle reference from a view.
         */
        public static final int CIRCLE = 8;

        /**
         * Set matchConstraintDefault* default to the wrap content size.
         * Use to set the matchConstraintDefaultWidth and matchConstraintDefaultHeight
         */
        public static final int MATCH_CONSTRAINT_WRAP = ConstraintWidget.MATCH_CONSTRAINT_WRAP;

        /**
         * Set matchConstraintDefault* spread as much as possible within its constraints.
         * Use to set the matchConstraintDefaultWidth and matchConstraintDefaultHeight
         */
        public static final int MATCH_CONSTRAINT_SPREAD = ConstraintWidget.MATCH_CONSTRAINT_SPREAD;

        /**
         * Set matchConstraintDefault* percent to be based on a percent of another dimension (by default, the parent)
         * Use to set the matchConstraintDefaultWidth and matchConstraintDefaultHeight
         */
        public static final int MATCH_CONSTRAINT_PERCENT = ConstraintWidget.MATCH_CONSTRAINT_PERCENT;

        /**
         * Chain spread style
         */
        public static final int CHAIN_SPREAD = ConstraintWidget.CHAIN_SPREAD;

        /**
         * Chain spread inside style
         */
        public static final int CHAIN_SPREAD_INSIDE = ConstraintWidget.CHAIN_SPREAD_INSIDE;

        /**
         * Chain packed style
         */
        public static final int CHAIN_PACKED = ConstraintWidget.CHAIN_PACKED;

        /**
         * The distance of child (guideline) to the top or left edge of its parent.
         */
        public int guideBegin = UNSET;

        /**
         * The distance of child (guideline) to the bottom or right edge of its parent.
         */
        public int guideEnd = UNSET;

        /**
         * The ratio of the distance to the parent's sides
         */
        public float guidePercent = UNSET;

        /**
         * Constrains the left side of a child to the left side of a target child (contains the target child id).
         */
        public int leftToLeft = UNSET;

        /**
         * Constrains the left side of a child to the right side of a target child (contains the target child id).
         */
        public int leftToRight = UNSET;

        /**
         * Constrains the right side of a child to the left side of a target child (contains the target child id).
         */
        public int rightToLeft = UNSET;

        /**
         * Constrains the right side of a child to the right side of a target child (contains the target child id).
         */
        public int rightToRight = UNSET;

        /**
         * Constrains the top side of a child to the top side of a target child (contains the target child id).
         */
        public int topToTop = UNSET;

        /**
         * Constrains the top side of a child to the bottom side of a target child (contains the target child id).
         */
        public int topToBottom = UNSET;

        /**
         * Constrains the bottom side of a child to the top side of a target child (contains the target child id).
         */
        public int bottomToTop = UNSET;

        /**
         * Constrains the bottom side of a child to the bottom side of a target child (contains the target child id).
         */
        public int bottomToBottom = UNSET;

        /**
         * Constrains the baseline of a child to the baseline of a target child (contains the target child id).
         */
        public int baselineToBaseline = UNSET;

        /**
         * Constrains the baseline of a child to the top of a target child (contains the target child id).
         */
        public int baselineToTop = UNSET;

        /**
         * Constrains the baseline of a child to the bottom of a target child (contains the target child id).
         */
        public int baselineToBottom = UNSET;

        /**
         * Constrains the center of a child to the center of a target child (contains the target child id).
         */
        public int circleConstraint = UNSET;

        /**
         * The radius used for a circular constraint
         */
        public int circleRadius = 0;

        /**
         * The angle used for a circular constraint]
         */
        public float circleAngle = 0;

        /**
         * Constrains the start side of a child to the end side of a target child (contains the target child id).
         */
        public int startToEnd = UNSET;

        /**
         * Constrains the start side of a child to the start side of a target child (contains the target child id).
         */
        public int startToStart = UNSET;

        /**
         * Constrains the end side of a child to the start side of a target child (contains the target child id).
         */
        public int endToStart = UNSET;

        /**
         * Constrains the end side of a child to the end side of a target child (contains the target child id).
         */
        public int endToEnd = UNSET;

        /**
         * The left margin to use when the target is gone.
         */
        public int goneLeftMargin = GONE_UNSET;

        /**
         * The top margin to use when the target is gone.
         */
        public int goneTopMargin = GONE_UNSET;

        /**
         * The right margin to use when the target is gone
         */
        public int goneRightMargin = GONE_UNSET;

        /**
         * The bottom margin to use when the target is gone.
         */
        public int goneBottomMargin = GONE_UNSET;

        /**
         * The start margin to use when the target is gone.
         */
        public int goneStartMargin = GONE_UNSET;

        /**
         * The end margin to use when the target is gone.
         */
        public int goneEndMargin = GONE_UNSET;

        /**
         * The baseline margin to use when the target is gone.
         */
        public int goneBaselineMargin = GONE_UNSET;

        /**
         * The baseline margin.
         */
        public int baselineMargin = 0;

        ///////////////////////////////////////////////////////////////////////////////////////////
        // Layout margins handling TODO: re-activate in 3.0
        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * The left margin.
         */
        // public int leftMargin = 0;

        /**
         * The right margin.
         */
        // public int rightMargin = 0;

        // int originalLeftMargin = 0;
        // int originalRightMargin = 0;

        /**
         * The top margin.
         */
        // public int topMargin = 0;

        /**
         * The bottom margin.
         */
        // public int bottomMargin = 0;

        /**
         * The start margin.
         */
        // public int startMargin = UNSET;

        /**
         * The end margin.
         */
        // public int endMargin = UNSET;

        // boolean isRtl = false;
        // int layoutDirection = ViewCompat.LAYOUT_DIRECTION_LTR;

        boolean widthSet = true; // need to be set to false when we reactivate this in 3.0
        boolean heightSet = true; // need to be set to false when we reactivate this in 3.0

        ///////////////////////////////////////////////////////////////////////////////////////////

        /**
         * The ratio between two connections when the left and right (or start and end) sides are constrained.
         */
        public float horizontalBias = 0.5f;

        /**
         * The ratio between two connections when the top and bottom sides are constrained.
         */
        public float verticalBias = 0.5f;

        /**
         * The ratio information.
         */
        public String dimensionRatio = null;

        /**
         * The ratio between the width and height of the child.
         */
        float dimensionRatioValue = 0;

        /**
         * The child's side to constrain using dimensRatio.
         */
        int dimensionRatioSide = VERTICAL;

        /**
         * 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
         */
        public float horizontalWeight = UNSET;

        /**
         * 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
         */
        public float verticalWeight = UNSET;

        /**
         * If the child is the start of a horizontal chain, this attribute will drive how
         * the elements of the chain will be positioned. The possible values are:
         * <ul>
         * <li>{@link #CHAIN_SPREAD} -- the elements will be spread out</li>
         * <li>{@link #CHAIN_SPREAD_INSIDE} -- similar, but the endpoints of the chain will not
         * be spread out</li>
         * <li>{@link #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>
         */
        public int horizontalChainStyle = CHAIN_SPREAD;

        /**
         * If the child is the start of a vertical chain, this attribute will drive how
         * the elements of the chain will be positioned. The possible values are:
         * <ul>
         * <li>{@link #CHAIN_SPREAD} -- the elements will be spread out</li>
         * <li>{@link #CHAIN_SPREAD_INSIDE} -- similar, but the endpoints of the chain will not
         * be spread out</li>
         * <li>{@link #CHAIN_PACKED} -- the elements of the chain will be packed together. The
         * vertical bias attribute of the child will then affect the positioning of the packed
         * elements</li>
         * </ul>
         */
        public int verticalChainStyle = CHAIN_SPREAD;

        /**
         * Define how the widget horizontal dimension is handled when set to MATCH_CONSTRAINT
         * <ul>
         * <li>{@link #MATCH_CONSTRAINT_SPREAD} -- the default. The dimension will expand up to
         * the constraints, minus margins</li>
         * <li>{@link #MATCH_CONSTRAINT_WRAP} -- DEPRECATED -- use instead WRAP_CONTENT and
         * constrainedWidth=true<br>
         * The dimension will be the same as WRAP_CONTENT, unless the size ends
         * up too large for the constraints; in that case the dimension will expand up to the constraints, minus margins</li>
         * This attribute may not be applied if the widget is part of a chain in that dimension.
         * <li>{@link #MATCH_CONSTRAINT_PERCENT} -- The dimension will be a percent of another
         * widget (by default, the parent)</li>
         * </ul>
         */
        public int matchConstraintDefaultWidth = MATCH_CONSTRAINT_SPREAD;

        /**
         * Define how the widget vertical dimension is handled when set to MATCH_CONSTRAINT
         * <ul>
         * <li>{@link #MATCH_CONSTRAINT_SPREAD} -- the default. The dimension will expand up to
         * the constraints, minus margins</li>
         * <li>{@link #MATCH_CONSTRAINT_WRAP} -- DEPRECATED -- use instead WRAP_CONTENT and
         * constrainedWidth=true<br>
         * The dimension will be the same as WRAP_CONTENT, unless the size ends
         * up too large for the constraints; in that case the dimension will expand up to the constraints, minus margins</li>
         * This attribute may not be applied if the widget is part of a chain in that dimension.
         * <li>{@link #MATCH_CONSTRAINT_PERCENT} -- The dimension will be a percent of another
         * widget (by default, the parent)</li>
         * </ul>
         */
        public int matchConstraintDefaultHeight = MATCH_CONSTRAINT_SPREAD;

        /**
         * Specify a minimum width size for the widget. It will only apply if the size of the widget
         * is set to MATCH_CONSTRAINT. Don't apply if the widget is part of a horizontal chain.
         */
        public int matchConstraintMinWidth = 0;

        /**
         * Specify a minimum height size for the widget. It will only apply if the size of the widget
         * is set to MATCH_CONSTRAINT. Don't apply if the widget is part of a vertical chain.
         */
        public int matchConstraintMinHeight = 0;

        /**
         * Specify a maximum width size for the widget. It will only apply if the size of the widget
         * is set to MATCH_CONSTRAINT. Don't apply if the widget is part of a horizontal chain.
         */
        public int matchConstraintMaxWidth = 0;

        /**
         * Specify a maximum height size for the widget. It will only apply if the size of the widget
         * is set to MATCH_CONSTRAINT. Don't apply if the widget is part of a vertical chain.
         */
        public int matchConstraintMaxHeight = 0;

        /**
         * Specify the percentage when using the match constraint percent mode. From 0 to 1.
         */
        public float matchConstraintPercentWidth = 1;

        /**
         * Specify the percentage when using the match constraint percent mode. From 0 to 1.
         */
        public float matchConstraintPercentHeight = 1;

        /**
         * The design time location of the left side of the child.
         * Used at design time for a horizontally unconstrained child.
         */
        public int editorAbsoluteX = UNSET;

        /**
         * The design time location of the right side of the child.
         * Used at design time for a vertically unconstrained child.
         */
        public int editorAbsoluteY = UNSET;

        public int orientation = UNSET;

        /**
         * Specify if the horizontal dimension is constrained in case both left & right constraints are set
         * and the widget dimension is not a fixed dimension. By default, if a widget is set to WRAP_CONTENT,
         * we will treat that dimension as a fixed dimension, meaning the dimension will not change regardless
         * of constraints. Setting this attribute to true allows the dimension to change in order to respect
         * constraints.
         */
        public boolean constrainedWidth = false;

        /**
         * Specify if the vertical dimension is constrained in case both top & bottom constraints are set
         * and the widget dimension is not a fixed dimension. By default, if a widget is set to WRAP_CONTENT,
         * we will treat that dimension as a fixed dimension, meaning the dimension will not change regardless
         * of constraints. Setting this attribute to true allows the dimension to change in order to respect
         * constraints.
         */
        public boolean constrainedHeight = false;

        /**
         * Define a category of view to be used by helpers and motionLayout
         */
        public String constraintTag = null;

        public static final int WRAP_BEHAVIOR_INCLUDED = ConstraintWidget.WRAP_BEHAVIOR_INCLUDED;
        public static final int WRAP_BEHAVIOR_HORIZONTAL_ONLY = ConstraintWidget.WRAP_BEHAVIOR_HORIZONTAL_ONLY;
        public static final int WRAP_BEHAVIOR_VERTICAL_ONLY = ConstraintWidget.WRAP_BEHAVIOR_VERTICAL_ONLY;
        public static final int WRAP_BEHAVIOR_SKIPPED = ConstraintWidget.WRAP_BEHAVIOR_SKIPPED;

        /**
         * Specify how this view is taken in account during the parent's wrap computation
         *
         * Can be either of:
         * WRAP_BEHAVIOR_INCLUDED the widget is taken in account for the wrap (default)
         * WRAP_BEHAVIOR_HORIZONTAL_ONLY the widget will be included in the wrap only horizontally
         * WRAP_BEHAVIOR_VERTICAL_ONLY the widget will be included in the wrap only vertically
         * WRAP_BEHAVIOR_SKIPPED the widget is not part of the wrap computation
         */
        public int wrapBehaviorInParent = WRAP_BEHAVIOR_INCLUDED;

        // Internal use only
        boolean horizontalDimensionFixed = true;
        boolean verticalDimensionFixed = true;

        boolean needsBaseline = false;
        boolean isGuideline = false;
        boolean isHelper = false;
        boolean isInPlaceholder = false;
        boolean isVirtualGroup = false;

        int resolvedLeftToLeft = UNSET;
        int resolvedLeftToRight = UNSET;
        int resolvedRightToLeft = UNSET;
        int resolvedRightToRight = UNSET;
        int resolveGoneLeftMargin = GONE_UNSET;
        int resolveGoneRightMargin = GONE_UNSET;
        float resolvedHorizontalBias = 0.5f;

        int resolvedGuideBegin;
        int resolvedGuideEnd;
        float resolvedGuidePercent;

        ConstraintWidget widget = new ConstraintWidget();

        /**
         * @hide
         */
        public ConstraintWidget getConstraintWidget() {
            return widget;
        }

        /**
         * @param text
         * @hide
         */
        public void setWidgetDebugName(String text) {
            widget.setDebugName(text);
        }

        public void reset() {
            if (widget != null) {
                widget.reset();
            }
        }

        public boolean helped = false;

        /**
         * Create a LayoutParams base on an existing layout Params
         *
         * @param source the Layout Params to be copied
         */
        public LayoutParams(LayoutParams source) {
            super(source);

            ///////////////////////////////////////////////////////////////////////////////////////////
            // Layout margins handling TODO: re-activate in 3.0
            ///////////////////////////////////////////////////////////////////////////////////////////
            // this.layoutDirection = source.layoutDirection;
            // this.isRtl = source.isRtl;
            // this.originalLeftMargin = source.originalLeftMargin;
            // this.originalRightMargin = source.originalRightMargin;
            // this.startMargin = source.startMargin;
            // this.endMargin = source.endMargin;
            // this.leftMargin = source.leftMargin;
            // this.rightMargin = source.rightMargin;
            // this.topMargin = source.topMargin;
            // this.bottomMargin = source.bottomMargin;
            ///////////////////////////////////////////////////////////////////////////////////////////

            this.guideBegin = source.guideBegin;
            this.guideEnd = source.guideEnd;
            this.guidePercent = source.guidePercent;
            this.leftToLeft = source.leftToLeft;
            this.leftToRight = source.leftToRight;
            this.rightToLeft = source.rightToLeft;
            this.rightToRight = source.rightToRight;
            this.topToTop = source.topToTop;
            this.topToBottom = source.topToBottom;
            this.bottomToTop = source.bottomToTop;
            this.bottomToBottom = source.bottomToBottom;
            this.baselineToBaseline = source.baselineToBaseline;
            this.baselineToTop = source.baselineToTop;
            this.baselineToBottom = source.baselineToBottom;
            this.circleConstraint = source.circleConstraint;
            this.circleRadius = source.circleRadius;
            this.circleAngle = source.circleAngle;
            this.startToEnd = source.startToEnd;
            this.startToStart = source.startToStart;
            this.endToStart = source.endToStart;
            this.endToEnd = source.endToEnd;
            this.goneLeftMargin = source.goneLeftMargin;
            this.goneTopMargin = source.goneTopMargin;
            this.goneRightMargin = source.goneRightMargin;
            this.goneBottomMargin = source.goneBottomMargin;
            this.goneStartMargin = source.goneStartMargin;
            this.goneEndMargin = source.goneEndMargin;
            this.goneBaselineMargin = source.goneBaselineMargin;
            this.baselineMargin = source.baselineMargin;
            this.horizontalBias = source.horizontalBias;
            this.verticalBias = source.verticalBias;
            this.dimensionRatio = source.dimensionRatio;
            this.dimensionRatioValue = source.dimensionRatioValue;
            this.dimensionRatioSide = source.dimensionRatioSide;
            this.horizontalWeight = source.horizontalWeight;
            this.verticalWeight = source.verticalWeight;
            this.horizontalChainStyle = source.horizontalChainStyle;
            this.verticalChainStyle = source.verticalChainStyle;
            this.constrainedWidth = source.constrainedWidth;
            this.constrainedHeight = source.constrainedHeight;
            this.matchConstraintDefaultWidth = source.matchConstraintDefaultWidth;
            this.matchConstraintDefaultHeight = source.matchConstraintDefaultHeight;
            this.matchConstraintMinWidth = source.matchConstraintMinWidth;
            this.matchConstraintMaxWidth = source.matchConstraintMaxWidth;
            this.matchConstraintMinHeight = source.matchConstraintMinHeight;
            this.matchConstraintMaxHeight = source.matchConstraintMaxHeight;
            this.matchConstraintPercentWidth = source.matchConstraintPercentWidth;
            this.matchConstraintPercentHeight = source.matchConstraintPercentHeight;
            this.editorAbsoluteX = source.editorAbsoluteX;
            this.editorAbsoluteY = source.editorAbsoluteY;
            this.orientation = source.orientation;
            this.horizontalDimensionFixed = source.horizontalDimensionFixed;
            this.verticalDimensionFixed = source.verticalDimensionFixed;
            this.needsBaseline = source.needsBaseline;
            this.isGuideline = source.isGuideline;
            this.resolvedLeftToLeft = source.resolvedLeftToLeft;
            this.resolvedLeftToRight = source.resolvedLeftToRight;
            this.resolvedRightToLeft = source.resolvedRightToLeft;
            this.resolvedRightToRight = source.resolvedRightToRight;
            this.resolveGoneLeftMargin = source.resolveGoneLeftMargin;
            this.resolveGoneRightMargin = source.resolveGoneRightMargin;
            this.resolvedHorizontalBias = source.resolvedHorizontalBias;
            this.constraintTag = source.constraintTag;
            this.wrapBehaviorInParent = source.wrapBehaviorInParent;
            this.widget = source.widget;
            this.widthSet = source.widthSet;
            this.heightSet = source.heightSet;
        }

        private static class Table {
            public static final int UNUSED = 0;
            public static final int ANDROID_ORIENTATION = 1;
            public static final int LAYOUT_CONSTRAINT_CIRCLE = 2;
            public static final int LAYOUT_CONSTRAINT_CIRCLE_RADIUS = 3;
            public static final int LAYOUT_CONSTRAINT_CIRCLE_ANGLE = 4;
            public static final int LAYOUT_CONSTRAINT_GUIDE_BEGIN = 5;
            public static final int LAYOUT_CONSTRAINT_GUIDE_END = 6;
            public static final int LAYOUT_CONSTRAINT_GUIDE_PERCENT = 7;
            public static final int LAYOUT_CONSTRAINT_LEFT_TO_LEFT_OF = 8;
            public static final int LAYOUT_CONSTRAINT_LEFT_TO_RIGHT_OF = 9;
            public static final int LAYOUT_CONSTRAINT_RIGHT_TO_LEFT_OF = 10;
            public static final int LAYOUT_CONSTRAINT_RIGHT_TO_RIGHT_OF = 11;
            public static final int LAYOUT_CONSTRAINT_TOP_TO_TOP_OF = 12;
            public static final int LAYOUT_CONSTRAINT_TOP_TO_BOTTOM_OF = 13;
            public static final int LAYOUT_CONSTRAINT_BOTTOM_TO_TOP_OF = 14;
            public static final int LAYOUT_CONSTRAINT_BOTTOM_TO_BOTTOM_OF = 15;
            public static final int LAYOUT_CONSTRAINT_BASELINE_TO_BASELINE_OF = 16;
            public static final int LAYOUT_CONSTRAINT_START_TO_END_OF = 17;
            public static final int LAYOUT_CONSTRAINT_START_TO_START_OF = 18;
            public static final int LAYOUT_CONSTRAINT_END_TO_START_OF = 19;
            public static final int LAYOUT_CONSTRAINT_END_TO_END_OF = 20;
            public static final int LAYOUT_GONE_MARGIN_LEFT = 21;
            public static final int LAYOUT_GONE_MARGIN_TOP = 22;
            public static final int LAYOUT_GONE_MARGIN_RIGHT = 23;
            public static final int LAYOUT_GONE_MARGIN_BOTTOM = 24;
            public static final int LAYOUT_GONE_MARGIN_START = 25;
            public static final int LAYOUT_GONE_MARGIN_END = 26;
            public static final int LAYOUT_CONSTRAINED_WIDTH = 27;
            public static final int LAYOUT_CONSTRAINED_HEIGHT = 28;
            public static final int LAYOUT_CONSTRAINT_HORIZONTAL_BIAS = 29;
            public static final int LAYOUT_CONSTRAINT_VERTICAL_BIAS = 30;
            public static final int LAYOUT_CONSTRAINT_WIDTH_DEFAULT = 31;
            public static final int LAYOUT_CONSTRAINT_HEIGHT_DEFAULT = 32;
            public static final int LAYOUT_CONSTRAINT_WIDTH_MIN = 33;
            public static final int LAYOUT_CONSTRAINT_WIDTH_MAX = 34;
            public static final int LAYOUT_CONSTRAINT_WIDTH_PERCENT = 35;
            public static final int LAYOUT_CONSTRAINT_HEIGHT_MIN = 36;
            public static final int LAYOUT_CONSTRAINT_HEIGHT_MAX = 37;
            public static final int LAYOUT_CONSTRAINT_HEIGHT_PERCENT = 38;
            public static final int LAYOUT_CONSTRAINT_LEFT_CREATOR = 39;
            public static final int LAYOUT_CONSTRAINT_TOP_CREATOR = 40;
            public static final int LAYOUT_CONSTRAINT_RIGHT_CREATOR = 41;
            public static final int LAYOUT_CONSTRAINT_BOTTOM_CREATOR = 42;
            public static final int LAYOUT_CONSTRAINT_BASELINE_CREATOR = 43;
            public static final int LAYOUT_CONSTRAINT_DIMENSION_RATIO = 44;
            public static final int LAYOUT_CONSTRAINT_HORIZONTAL_WEIGHT = 45;
            public static final int LAYOUT_CONSTRAINT_VERTICAL_WEIGHT = 46;
            public static final int LAYOUT_CONSTRAINT_HORIZONTAL_CHAINSTYLE = 47;
            public static final int LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE = 48;
            public static final int LAYOUT_EDITOR_ABSOLUTEX = 49;
            public static final int LAYOUT_EDITOR_ABSOLUTEY = 50;
            public static final int LAYOUT_CONSTRAINT_TAG = 51;
            public static final int LAYOUT_CONSTRAINT_BASELINE_TO_TOP_OF = 52;
            public static final int LAYOUT_CONSTRAINT_BASELINE_TO_BOTTOM_OF = 53;
            public static final int LAYOUT_MARGIN_BASELINE = 54;
            public static final int LAYOUT_GONE_MARGIN_BASELINE = 55;
            ///////////////////////////////////////////////////////////////////////////////////////////
            // Layout margins handling TODO: re-activate in 3.0
            ///////////////////////////////////////////////////////////////////////////////////////////
            // public static final int LAYOUT_MARGIN_LEFT = 56;
            // public static final int LAYOUT_MARGIN_RIGHT = 57;
            // public static final int LAYOUT_MARGIN_TOP = 58;
            // public static final int LAYOUT_MARGIN_BOTTOM = 59;
            // public static final int LAYOUT_MARGIN_START = 60;
            // public static final int LAYOUT_MARGIN_END = 61;
            // public static final int LAYOUT_WIDTH = 62;
            // public static final int LAYOUT_HEIGHT = 63;
            ///////////////////////////////////////////////////////////////////////////////////////////
            public static final int LAYOUT_CONSTRAINT_WIDTH = 64;
            public static final int LAYOUT_CONSTRAINT_HEIGHT = 65;
            public static final int LAYOUT_WRAP_BEHAVIOR_IN_PARENT = 66;

            public final static SparseIntArray map = new SparseIntArray();

            static {
                ///////////////////////////////////////////////////////////////////////////////////////////
                // Layout margins handling TODO: re-activate in 3.0
                ///////////////////////////////////////////////////////////////////////////////////////////
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_width, LAYOUT_WIDTH);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_height, LAYOUT_HEIGHT);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_marginLeft, LAYOUT_MARGIN_LEFT);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_marginRight, LAYOUT_MARGIN_RIGHT);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_marginTop, LAYOUT_MARGIN_TOP);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_marginBottom, LAYOUT_MARGIN_BOTTOM);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_marginStart, LAYOUT_MARGIN_START);
                // map.append(R.styleable.ConstraintLayout_Layout_android_layout_marginEnd, LAYOUT_MARGIN_END);
                ///////////////////////////////////////////////////////////////////////////////////////////
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintWidth, LAYOUT_CONSTRAINT_WIDTH);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHeight, LAYOUT_CONSTRAINT_HEIGHT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintLeft_toLeftOf, LAYOUT_CONSTRAINT_LEFT_TO_LEFT_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintLeft_toRightOf, LAYOUT_CONSTRAINT_LEFT_TO_RIGHT_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintRight_toLeftOf, LAYOUT_CONSTRAINT_RIGHT_TO_LEFT_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintRight_toRightOf, LAYOUT_CONSTRAINT_RIGHT_TO_RIGHT_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintTop_toTopOf, LAYOUT_CONSTRAINT_TOP_TO_TOP_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintTop_toBottomOf, LAYOUT_CONSTRAINT_TOP_TO_BOTTOM_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBottom_toTopOf, LAYOUT_CONSTRAINT_BOTTOM_TO_TOP_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBottom_toBottomOf, LAYOUT_CONSTRAINT_BOTTOM_TO_BOTTOM_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBaseline_toBaselineOf, LAYOUT_CONSTRAINT_BASELINE_TO_BASELINE_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBaseline_toTopOf, LAYOUT_CONSTRAINT_BASELINE_TO_TOP_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBaseline_toBottomOf, LAYOUT_CONSTRAINT_BASELINE_TO_BOTTOM_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintCircle, LAYOUT_CONSTRAINT_CIRCLE);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintCircleRadius, LAYOUT_CONSTRAINT_CIRCLE_RADIUS);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintCircleAngle, LAYOUT_CONSTRAINT_CIRCLE_ANGLE);
                map.append(R.styleable.ConstraintLayout_Layout_layout_editor_absoluteX, LAYOUT_EDITOR_ABSOLUTEX);
                map.append(R.styleable.ConstraintLayout_Layout_layout_editor_absoluteY, LAYOUT_EDITOR_ABSOLUTEY);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintGuide_begin, LAYOUT_CONSTRAINT_GUIDE_BEGIN);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintGuide_end, LAYOUT_CONSTRAINT_GUIDE_END);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintGuide_percent, LAYOUT_CONSTRAINT_GUIDE_PERCENT);
                map.append(R.styleable.ConstraintLayout_Layout_android_orientation, ANDROID_ORIENTATION);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintStart_toEndOf, LAYOUT_CONSTRAINT_START_TO_END_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintStart_toStartOf, LAYOUT_CONSTRAINT_START_TO_START_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintEnd_toStartOf, LAYOUT_CONSTRAINT_END_TO_START_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintEnd_toEndOf, LAYOUT_CONSTRAINT_END_TO_END_OF);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginLeft, LAYOUT_GONE_MARGIN_LEFT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginTop, LAYOUT_GONE_MARGIN_TOP);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginRight, LAYOUT_GONE_MARGIN_RIGHT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginBottom, LAYOUT_GONE_MARGIN_BOTTOM);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginStart, LAYOUT_GONE_MARGIN_START);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginEnd, LAYOUT_GONE_MARGIN_END);
                map.append(R.styleable.ConstraintLayout_Layout_layout_goneMarginBaseline, LAYOUT_GONE_MARGIN_BASELINE);
                map.append(R.styleable.ConstraintLayout_Layout_layout_marginBaseline, LAYOUT_MARGIN_BASELINE);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHorizontal_bias, LAYOUT_CONSTRAINT_HORIZONTAL_BIAS);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintVertical_bias, LAYOUT_CONSTRAINT_VERTICAL_BIAS);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintDimensionRatio, LAYOUT_CONSTRAINT_DIMENSION_RATIO);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHorizontal_weight, LAYOUT_CONSTRAINT_HORIZONTAL_WEIGHT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintVertical_weight, LAYOUT_CONSTRAINT_VERTICAL_WEIGHT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHorizontal_chainStyle, LAYOUT_CONSTRAINT_HORIZONTAL_CHAINSTYLE);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintVertical_chainStyle, LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constrainedWidth, LAYOUT_CONSTRAINED_WIDTH);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constrainedHeight, LAYOUT_CONSTRAINED_HEIGHT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintWidth_default, LAYOUT_CONSTRAINT_WIDTH_DEFAULT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHeight_default, LAYOUT_CONSTRAINT_HEIGHT_DEFAULT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintWidth_min, LAYOUT_CONSTRAINT_WIDTH_MIN);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintWidth_max, LAYOUT_CONSTRAINT_WIDTH_MAX);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintWidth_percent, LAYOUT_CONSTRAINT_WIDTH_PERCENT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHeight_min, LAYOUT_CONSTRAINT_HEIGHT_MIN);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHeight_max, LAYOUT_CONSTRAINT_HEIGHT_MAX);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintHeight_percent, LAYOUT_CONSTRAINT_HEIGHT_PERCENT);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintLeft_creator, LAYOUT_CONSTRAINT_LEFT_CREATOR);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintTop_creator, LAYOUT_CONSTRAINT_TOP_CREATOR);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintRight_creator, LAYOUT_CONSTRAINT_RIGHT_CREATOR);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBottom_creator, LAYOUT_CONSTRAINT_BOTTOM_CREATOR);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintBaseline_creator, LAYOUT_CONSTRAINT_BASELINE_CREATOR);
                map.append(R.styleable.ConstraintLayout_Layout_layout_constraintTag, LAYOUT_CONSTRAINT_TAG);
                map.append(R.styleable.ConstraintLayout_Layout_layout_wrapBehaviorInParent, LAYOUT_WRAP_BEHAVIOR_IN_PARENT);
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        // Layout margins handling TODO: re-activate in 3.0
        ///////////////////////////////////////////////////////////////////////////////////////////
        /*
        public void setMarginStart(int start) {
            startMargin = start;
        }

        public void setMarginEnd(int end) {
            endMargin = end;
        }

        public int getMarginStart() {
            return startMargin;
        }

        public int getMarginEnd() {
            return endMargin;
        }

        public int getLayoutDirection() {
            return layoutDirection;
        }
        */
        ///////////////////////////////////////////////////////////////////////////////////////////

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ConstraintLayout_Layout);
            final int N = a.getIndexCount();

            ///////////////////////////////////////////////////////////////////////////////////////////
            // Layout margins handling TODO: re-activate in 3.0
            ///////////////////////////////////////////////////////////////////////////////////////////
            // super(WRAP_CONTENT, WRAP_CONTENT);
            /*
            if (N == 0) {
               // check if it's an include
               throw new IllegalArgumentException("Invalid LayoutParams supplied to " + this);
            }

            // let's first apply full margins if they are present.
            int margin = a.getDimensionPixelSize(R.styleable.ConstraintLayout_Layout_android_layout_margin, -1);
            int horizontalMargin = -1;
            int verticalMargin = -1;
            if (margin >= 0) {
                originalLeftMargin = margin;
                originalRightMargin = margin;
                topMargin = margin;
                bottomMargin = margin;
            } else {
                horizontalMargin = a.getDimensionPixelSize(R.styleable.ConstraintLayout_Layout_android_layout_marginHorizontal, -1);
                verticalMargin = a.getDimensionPixelSize(R.styleable.ConstraintLayout_Layout_android_layout_marginVertical, -1);
                if (horizontalMargin >= 0) {
                    originalLeftMargin = horizontalMargin;
                    originalRightMargin = horizontalMargin;
                }
                if (verticalMargin >= 0) {
                    topMargin = verticalMargin;
                    bottomMargin = verticalMargin;
                }
            }
            */
            ///////////////////////////////////////////////////////////////////////////////////////////

            for (int i = 0; i < N; i++) {
                int attr = a.getIndex(i);
                int look = Table.map.get(attr);
                switch (look) {
                    case Table.UNUSED: {
                        // Skip
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_WIDTH: {
                        ConstraintSet.parseDimensionConstraints(this, a, attr, HORIZONTAL);
                        widthSet = true;
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HEIGHT: {
                        ConstraintSet.parseDimensionConstraints(this, a, attr, VERTICAL);
                        heightSet = true;
                        break;
                    }
                    ///////////////////////////////////////////////////////////////////////////////////////////
                    // Layout margins handling TODO: re-activate in 3.0
                    ///////////////////////////////////////////////////////////////////////////////////////////
                    /*
                    case Table.LAYOUT_WIDTH: {
                        width = a.getLayoutDimension(R.styleable.ConstraintLayout_Layout_android_layout_width, "layout_width");
                        widthSet = true;
                        break;
                    }
                    case Table.LAYOUT_HEIGHT: {
                        height = a.getLayoutDimension(R.styleable.ConstraintLayout_Layout_android_layout_height, "layout_height");
                        heightSet = true;
                        break;
                    }
                    */
                    ///////////////////////////////////////////////////////////////////////////////////////////
                    case Table.LAYOUT_WRAP_BEHAVIOR_IN_PARENT: {
                        wrapBehaviorInParent = a.getInt(attr, wrapBehaviorInParent);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_LEFT_TO_LEFT_OF: {
                        leftToLeft = a.getResourceId(attr, leftToLeft);
                        if (leftToLeft == UNSET) {
                            leftToLeft = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_LEFT_TO_RIGHT_OF: {
                        leftToRight = a.getResourceId(attr, leftToRight);
                        if (leftToRight == UNSET) {
                            leftToRight = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_RIGHT_TO_LEFT_OF: {
                        rightToLeft = a.getResourceId(attr, rightToLeft);
                        if (rightToLeft == UNSET) {
                            rightToLeft = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_RIGHT_TO_RIGHT_OF: {
                        rightToRight = a.getResourceId(attr, rightToRight);
                        if (rightToRight == UNSET) {
                            rightToRight = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_TOP_TO_TOP_OF: {
                        topToTop = a.getResourceId(attr, topToTop);
                        if (topToTop == UNSET) {
                            topToTop = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_TOP_TO_BOTTOM_OF: {
                        topToBottom = a.getResourceId(attr, topToBottom);
                        if (topToBottom == UNSET) {
                            topToBottom = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BOTTOM_TO_TOP_OF: {
                        bottomToTop = a.getResourceId(attr, bottomToTop);
                        if (bottomToTop == UNSET) {
                            bottomToTop = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BOTTOM_TO_BOTTOM_OF: {
                        bottomToBottom = a.getResourceId(attr, bottomToBottom);
                        if (bottomToBottom == UNSET) {
                            bottomToBottom = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BASELINE_TO_BASELINE_OF: {
                        baselineToBaseline = a.getResourceId(attr, baselineToBaseline);
                        if (baselineToBaseline == UNSET) {
                            baselineToBaseline = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BASELINE_TO_TOP_OF: {
                        baselineToTop = a.getResourceId(attr, baselineToTop);
                        if (baselineToTop == UNSET) {
                            baselineToTop = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BASELINE_TO_BOTTOM_OF: {
                        baselineToBottom = a.getResourceId(attr, baselineToBottom);
                        if (baselineToBottom == UNSET) {
                            baselineToBottom = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_CIRCLE: {
                        circleConstraint = a.getResourceId(attr, circleConstraint);
                        if (circleConstraint == UNSET) {
                            circleConstraint = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_CIRCLE_RADIUS: {
                        circleRadius = a.getDimensionPixelSize(attr, circleRadius);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_CIRCLE_ANGLE: {
                        circleAngle = a.getFloat(attr, circleAngle) % 360;
                        if (circleAngle < 0) {
                            circleAngle = (360 - circleAngle) % 360;
                        }
                        break;
                    }
                    case Table.LAYOUT_EDITOR_ABSOLUTEX: {
                        editorAbsoluteX = a.getDimensionPixelOffset(attr, editorAbsoluteX);
                        break;
                    }
                    case Table.LAYOUT_EDITOR_ABSOLUTEY: {
                        editorAbsoluteY = a.getDimensionPixelOffset(attr, editorAbsoluteY);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_GUIDE_BEGIN: {
                        guideBegin = a.getDimensionPixelOffset(attr, guideBegin);
                        break;
                    }

                    case Table.LAYOUT_CONSTRAINT_GUIDE_END: {
                        guideEnd = a.getDimensionPixelOffset(attr, guideEnd);
                        break;
                    }

                    case Table.LAYOUT_CONSTRAINT_GUIDE_PERCENT: {
                        guidePercent = a.getFloat(attr, guidePercent);
                        break;
                    }

                    case Table.ANDROID_ORIENTATION: {
                        orientation = a.getInt(attr, orientation);
                        break;
                    }

                    case Table.LAYOUT_CONSTRAINT_START_TO_END_OF: {
                        startToEnd = a.getResourceId(attr, startToEnd);
                        if (startToEnd == UNSET) {
                            startToEnd = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_START_TO_START_OF: {
                        startToStart = a.getResourceId(attr, startToStart);
                        if (startToStart == UNSET) {
                            startToStart = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_END_TO_START_OF: {
                        endToStart = a.getResourceId(attr, endToStart);
                        if (endToStart == UNSET) {
                            endToStart = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_END_TO_END_OF: {
                        endToEnd = a.getResourceId(attr, endToEnd);
                        if (endToEnd == UNSET) {
                            endToEnd = a.getInt(attr, UNSET);
                        }
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_LEFT: {
                        goneLeftMargin = a.getDimensionPixelSize(attr, goneLeftMargin);
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_TOP: {
                        goneTopMargin = a.getDimensionPixelSize(attr, goneTopMargin);
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_RIGHT: {
                        goneRightMargin = a.getDimensionPixelSize(attr, goneRightMargin);
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_BOTTOM: {
                        goneBottomMargin = a.getDimensionPixelSize(attr, goneBottomMargin);
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_START: {
                        goneStartMargin = a.getDimensionPixelSize(attr, goneStartMargin);
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_END: {
                        goneEndMargin = a.getDimensionPixelSize(attr, goneEndMargin);
                        break;
                    }
                    case Table.LAYOUT_GONE_MARGIN_BASELINE: {
                        goneBaselineMargin = a.getDimensionPixelSize(attr, goneBaselineMargin);
                        break;
                    }
                    case Table.LAYOUT_MARGIN_BASELINE: {
                        baselineMargin = a.getDimensionPixelSize(attr, baselineMargin);
                        break;
                    }
                    ///////////////////////////////////////////////////////////////////////////////////////////
                    // Layout margins handling TODO: re-activate in 3.0
                    ///////////////////////////////////////////////////////////////////////////////////////////
                    /*
                    case Table.LAYOUT_MARGIN_LEFT: {
                        if (margin == -1 && horizontalMargin == -1) {
                            originalLeftMargin = a.getDimensionPixelSize(attr, originalLeftMargin);
                        }
                        break;
                    }
                    case Table.LAYOUT_MARGIN_RIGHT: {
                        if (margin == -1 && horizontalMargin == -1) {
                            originalRightMargin = a.getDimensionPixelSize(attr, originalRightMargin);
                        }
                        break;
                    }
                    case Table.LAYOUT_MARGIN_TOP: {
                        if (margin == -1 && verticalMargin == -1) {
                            topMargin = a.getDimensionPixelSize(attr, topMargin);
                        }
                        break;
                    }
                    case Table.LAYOUT_MARGIN_BOTTOM: {
                        if (margin == -1 && verticalMargin == -1) {
                            bottomMargin = a.getDimensionPixelSize(attr, bottomMargin);
                        }
                        break;
                    }
                    case Table.LAYOUT_MARGIN_START: {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                            if (margin == -1 && horizontalMargin == -1) {
                                startMargin = a.getDimensionPixelSize(attr, startMargin);
                            }
                        }
                        break;
                    }
                    case Table.LAYOUT_MARGIN_END: {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                            if (margin == -1 && horizontalMargin == -1) {
                                endMargin = a.getDimensionPixelSize(attr, endMargin);
                            }
                        }
                        break;
                    }
                    */
                    ///////////////////////////////////////////////////////////////////////////////////////////
                    case Table.LAYOUT_CONSTRAINT_HORIZONTAL_BIAS: {
                        horizontalBias = a.getFloat(attr, horizontalBias);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_VERTICAL_BIAS: {
                        verticalBias = a.getFloat(attr, verticalBias);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_DIMENSION_RATIO: {
                        ConstraintSet.parseDimensionRatioString(this, a.getString(attr));
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HORIZONTAL_WEIGHT: {
                        horizontalWeight = a.getFloat(attr, horizontalWeight);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_VERTICAL_WEIGHT: {
                        verticalWeight = a.getFloat(attr, verticalWeight);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HORIZONTAL_CHAINSTYLE: {
                        horizontalChainStyle = a.getInt(attr, CHAIN_SPREAD);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE: {
                        verticalChainStyle = a.getInt(attr, CHAIN_SPREAD);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINED_WIDTH: {
                        constrainedWidth = a.getBoolean(attr, constrainedWidth);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINED_HEIGHT: {
                        constrainedHeight = a.getBoolean(attr, constrainedHeight);
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_WIDTH_DEFAULT: {
                        matchConstraintDefaultWidth = a.getInt(attr, MATCH_CONSTRAINT_SPREAD);
                        if (matchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
                            Log.e(TAG, "layout_constraintWidth_default=\"wrap\" is deprecated." +
                                    "\nUse layout_width=\"WRAP_CONTENT\" and layout_constrainedWidth=\"true\" instead.");
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HEIGHT_DEFAULT: {
                        matchConstraintDefaultHeight = a.getInt(attr, MATCH_CONSTRAINT_SPREAD);
                        if (matchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
                            Log.e(TAG, "layout_constraintHeight_default=\"wrap\" is deprecated." +
                                    "\nUse layout_height=\"WRAP_CONTENT\" and layout_constrainedHeight=\"true\" instead.");
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_WIDTH_MIN: {
                        try {
                            matchConstraintMinWidth = a.getDimensionPixelSize(attr, matchConstraintMinWidth);
                        } catch (Exception e) {
                            int value = a.getInt(attr, matchConstraintMinWidth);
                            if (value == WRAP_CONTENT) {
                                matchConstraintMinWidth = WRAP_CONTENT;
                            }
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_WIDTH_MAX: {
                        try {
                            matchConstraintMaxWidth = a.getDimensionPixelSize(attr, matchConstraintMaxWidth);
                        } catch (Exception e) {
                            int value = a.getInt(attr, matchConstraintMaxWidth);
                            if (value == WRAP_CONTENT) {
                                matchConstraintMaxWidth = WRAP_CONTENT;
                            }
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_WIDTH_PERCENT: {
                        matchConstraintPercentWidth = Math.max(0, a.getFloat(attr, matchConstraintPercentWidth));
                        matchConstraintDefaultWidth = MATCH_CONSTRAINT_PERCENT;
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HEIGHT_MIN: {
                        try {
                            matchConstraintMinHeight = a.getDimensionPixelSize(attr, matchConstraintMinHeight);
                        } catch (Exception e) {
                            int value = a.getInt(attr, matchConstraintMinHeight);
                            if (value == WRAP_CONTENT) {
                                matchConstraintMinHeight = WRAP_CONTENT;
                            }
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HEIGHT_MAX: {
                        try {
                            matchConstraintMaxHeight = a.getDimensionPixelSize(attr, matchConstraintMaxHeight);
                        } catch (Exception e) {
                            int value = a.getInt(attr, matchConstraintMaxHeight);
                            if (value == WRAP_CONTENT) {
                                matchConstraintMaxHeight = WRAP_CONTENT;
                            }
                        }
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_HEIGHT_PERCENT: {
                        matchConstraintPercentHeight = Math.max(0, a.getFloat(attr, matchConstraintPercentHeight));
                        matchConstraintDefaultHeight = MATCH_CONSTRAINT_PERCENT;
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_TAG:
                        constraintTag = a.getString(attr);
                        break;
                    case Table.LAYOUT_CONSTRAINT_LEFT_CREATOR: {
                        // Skip
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_TOP_CREATOR: {
                        // Skip
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_RIGHT_CREATOR: {
                        // Skip
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BOTTOM_CREATOR: {
                        // Skip
                        break;
                    }
                    case Table.LAYOUT_CONSTRAINT_BASELINE_CREATOR: {
                        // Skip
                        break;
                    }
                }
            }

            ///////////////////////////////////////////////////////////////////////////////////////////
            // Layout margins handling TODO: re-activate in 3.0
            ///////////////////////////////////////////////////////////////////////////////////////////
            /*
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
                leftMargin = originalLeftMargin;
                rightMargin = originalRightMargin;
            }
            */
            ///////////////////////////////////////////////////////////////////////////////////////////

            a.recycle();
            validate();
        }

        public void validate() {
            isGuideline = false;
            horizontalDimensionFixed = true;
            verticalDimensionFixed = true;
            ///////////////////////////////////////////////////////////////////////////////////////////
            // Layout margins handling TODO: re-activate in 3.0
            ///////////////////////////////////////////////////////////////////////////////////////////
            /*
            if (dimensionRatio != null && !widthSet && !heightSet) {
                width = MATCH_CONSTRAINT;
                height = MATCH_CONSTRAINT;
            }
            */
            ///////////////////////////////////////////////////////////////////////////////////////////

            if (width == WRAP_CONTENT && constrainedWidth) {
                horizontalDimensionFixed = false;
                if (matchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
                    matchConstraintDefaultWidth = MATCH_CONSTRAINT_WRAP;
                }
            }
            if (height == WRAP_CONTENT && constrainedHeight) {
                verticalDimensionFixed = false;
                if (matchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
                    matchConstraintDefaultHeight = MATCH_CONSTRAINT_WRAP;
                }
            }
            if (width == MATCH_CONSTRAINT || width == MATCH_PARENT) {
                horizontalDimensionFixed = false;
                // We have to reset LayoutParams width/height to WRAP_CONTENT here, as some widgets like TextView
                // will use the layout params directly as a hint to know if they need to request a layout
                // when their content change (e.g. during setTextView)
                if (width == MATCH_CONSTRAINT && matchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
                    width = WRAP_CONTENT;
                    constrainedWidth = true;
                }
            }
            if (height == MATCH_CONSTRAINT || height == MATCH_PARENT) {
                verticalDimensionFixed = false;
                // We have to reset LayoutParams width/height to WRAP_CONTENT here, as some widgets like TextView
                // will use the layout params directly as a hint to know if they need to request a layout
                // when their content change (e.g. during setTextView)
                if (height == MATCH_CONSTRAINT && matchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
                    height = WRAP_CONTENT;
                    constrainedHeight = true;
                }
            }
            if (guidePercent != UNSET || guideBegin != UNSET || guideEnd != UNSET) {
                isGuideline = true;
                horizontalDimensionFixed = true;
                verticalDimensionFixed = true;
                if (!(widget instanceof Guideline)) {
                    widget = new Guideline();
                }
                ((Guideline) widget).setOrientation(orientation);
            }
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
        public void resolveLayoutDirection(int layoutDirection) {
            ///////////////////////////////////////////////////////////////////////////////////////////
            // Layout margins handling TODO: re-activate in 3.0
            ///////////////////////////////////////////////////////////////////////////////////////////
            /*
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                this.layoutDirection = layoutDirection;
                isRtl = (View.LAYOUT_DIRECTION_RTL == layoutDirection);
            }

            // First apply margins.
            leftMargin = originalLeftMargin;
            rightMargin = originalRightMargin;

            if (isRtl) {
                leftMargin = originalRightMargin;
                rightMargin = originalLeftMargin;
                if (startMargin != UNSET) {
                    rightMargin = startMargin;
                }
                if (endMargin != UNSET) {
                    leftMargin = endMargin;
                }
            } else {
                if (startMargin != UNSET) {
                    leftMargin = startMargin;
                }
                if (endMargin != UNSET) {
                    rightMargin = endMargin;
                }
            }
            */
            ///////////////////////////////////////////////////////////////////////////////////////////
            int originalLeftMargin = leftMargin;
            int originalRightMargin = rightMargin;

            boolean isRtl = false;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                super.resolveLayoutDirection(layoutDirection);
                isRtl = (View.LAYOUT_DIRECTION_RTL == getLayoutDirection());
            }
            ///////////////////////////////////////////////////////////////////////////////////////////

            resolvedRightToLeft = UNSET;
            resolvedRightToRight = UNSET;
            resolvedLeftToLeft = UNSET;
            resolvedLeftToRight = UNSET;

            resolveGoneLeftMargin = UNSET;
            resolveGoneRightMargin = UNSET;
            resolveGoneLeftMargin = goneLeftMargin;
            resolveGoneRightMargin = goneRightMargin;
            resolvedHorizontalBias = horizontalBias;

            resolvedGuideBegin = guideBegin;
            resolvedGuideEnd = guideEnd;
            resolvedGuidePercent = guidePercent;

            // Post JB MR1, if start/end are defined, they take precedence over left/right
            if (isRtl) {
                boolean startEndDefined = false;
                if (startToEnd != UNSET) {
                    resolvedRightToLeft = startToEnd;
                    startEndDefined = true;
                } else if (startToStart != UNSET) {
                    resolvedRightToRight = startToStart;
                    startEndDefined = true;
                }
                if (endToStart != UNSET) {
                    resolvedLeftToRight = endToStart;
                    startEndDefined = true;
                }
                if (endToEnd != UNSET) {
                    resolvedLeftToLeft = endToEnd;
                    startEndDefined = true;
                }
                if (goneStartMargin != GONE_UNSET) {
                    resolveGoneRightMargin = goneStartMargin;
                }
                if (goneEndMargin != GONE_UNSET) {
                    resolveGoneLeftMargin = goneEndMargin;
                }
                if (startEndDefined) {
                    resolvedHorizontalBias = 1 - horizontalBias;
                }

                // Only apply to vertical guidelines
                if (isGuideline && orientation == Guideline.VERTICAL) {
                    if (guidePercent != UNSET) {
                        resolvedGuidePercent = 1 - guidePercent;
                        resolvedGuideBegin = UNSET;
                        resolvedGuideEnd = UNSET;
                    } else if (guideBegin != UNSET) {
                        resolvedGuideEnd = guideBegin;
                        resolvedGuideBegin = UNSET;
                        resolvedGuidePercent = UNSET;
                    } else if (guideEnd != UNSET) {
                        resolvedGuideBegin = guideEnd;
                        resolvedGuideEnd = UNSET;
                        resolvedGuidePercent = UNSET;
                    }
                }
            } else {
                if (startToEnd != UNSET) {
                    resolvedLeftToRight = startToEnd;
                }
                if (startToStart != UNSET) {
                    resolvedLeftToLeft = startToStart;
                }
                if (endToStart != UNSET) {
                    resolvedRightToLeft = endToStart;
                }
                if (endToEnd != UNSET) {
                    resolvedRightToRight = endToEnd;
                }
                if (goneStartMargin != GONE_UNSET) {
                    resolveGoneLeftMargin = goneStartMargin;
                }
                if (goneEndMargin != GONE_UNSET) {
                    resolveGoneRightMargin = goneEndMargin;
                }
            }
            // if no constraint is defined via RTL attributes, use left/right if present
            if (endToStart == UNSET && endToEnd == UNSET
                    && startToStart == UNSET && startToEnd == UNSET) {
                if (rightToLeft != UNSET) {
                    resolvedRightToLeft = rightToLeft;
                    if (rightMargin <= 0 && originalRightMargin > 0) {
                        rightMargin = originalRightMargin;
                    }
                } else if (rightToRight != UNSET) {
                    resolvedRightToRight = rightToRight;
                    if (rightMargin <= 0 && originalRightMargin > 0) {
                        rightMargin = originalRightMargin;
                    }
                }
                if (leftToLeft != UNSET) {
                    resolvedLeftToLeft = leftToLeft;
                    if (leftMargin <= 0 && originalLeftMargin > 0) {
                        leftMargin = originalLeftMargin;
                    }
                } else if (leftToRight != UNSET) {
                    resolvedLeftToRight = leftToRight;
                    if (leftMargin <= 0 && originalLeftMargin > 0) {
                        leftMargin = originalLeftMargin;
                    }
                }
            }
        }

        /**
         * Tag that can be used to identify a view as being a member of a group.
         * Which can be used for Helpers or in MotionLayout
         *
         * @return tag string or null if not defined
         */
        public String getConstraintTag() {
            return constraintTag;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void requestLayout() {
        markHierarchyDirty();
        super.requestLayout();
    }

    @Override
    public void forceLayout() {
        markHierarchyDirty();
        super.forceLayout();
    }

    private void markHierarchyDirty() {
        mDirtyHierarchy = true;
        // reset measured cache
        mLastMeasureWidth = -1;
        mLastMeasureHeight = -1;
        mLastMeasureWidthSize = -1;
        mLastMeasureHeightSize = -1;
        mLastMeasureWidthMode = MeasureSpec.UNSPECIFIED;
        mLastMeasureHeightMode = MeasureSpec.UNSPECIFIED;
    }

    /**
     * {@hide}
     *
     * @return
     */
    @Override
    public boolean shouldDelayChildPressedState() {
        return false;
    }
}