blob: ec787b7e89c88fbbc6b25964eb633c2657a80187 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.constraint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.constraint.solver.widgets.ConstraintAnchor;
import android.support.constraint.solver.widgets.ConstraintWidget;
import android.support.constraint.solver.widgets.ConstraintWidgetContainer;
import android.support.constraint.solver.widgets.Guideline;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.HashMap;
import static android.support.constraint.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 in enriching its API and capabilities over time.
* This documentation will reflects the 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="#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>
* </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 block 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 takes 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 margins 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>
* </ul>
* <p>Note that a margin can only be positive or equals 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 indicates 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>
* </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" constrains. For example, if
* we have something like:
* <pre>{@code
* <android.support.constraint.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
* <android.support.constraint.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="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 if 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. 6 - 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. 6), 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. 6 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 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>
* </ul>
* Those minimum 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. 7 - 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. 7, (a) is wrap_content, (b) is 0dp). If margins are set, they will be taken in account
* in the computation (Fig. 7, (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>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_constraintDimentionRatio} 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 the 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 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 a linked together via a bi-directional connection (see Fig. 8, showing a minimal chain, with two widgets).
* </p>
* <p><div align="center" >
* <img width="325px" src="resources/images/chains.png">
* <br><b><i>Fig. 8 - 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. 9 - 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 in 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. 10 - 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 exemple, 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>
*
* <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.</p>
* </div>
*/
public class ConstraintLayout extends ViewGroup {
// For now, disallow embedded (single-layer resolution) situations.
// While it works, the constraints of the layout have the same importance as any other
// constraint of the overall layout, which can cause issues. Let's revisit this
// after implementing priorities/hierarchy of constraints.
static final boolean ALLOWS_EMBEDDED = false;
/** @hide */
public static final String VERSION="ConstraintLayout-1.1.0-beta2";
private static final String TAG = "ConstraintLayout";
private static final boolean SIMPLE_LAYOUT = true;
private static final boolean USE_CONSTRAINTS_HELPER = true;
SparseArray<View> mChildrenByIds = new SparseArray<>();
// This array keep a list of helpers objects if they are present
private ArrayList<ConstraintHelper> mConstraintHelpers = new ArrayList<>(4);
// This array will keep a list of the widget with one or two dimensions that are
// set to MATCH_CONSTRAINT (i.e. they depend on the solver result, not from
// WRAP_CONTENT or a fixed dimension)
private final ArrayList<ConstraintWidget> mVariableDimensionsWidgets = new ArrayList<>(100);
ConstraintWidgetContainer mLayoutWidget = new ConstraintWidgetContainer();
private int mMinWidth = 0;
private int mMinHeight = 0;
private int mMaxWidth = Integer.MAX_VALUE;
private int mMaxHeight = Integer.MAX_VALUE;
private boolean mDirtyHierarchy = true;
private int mOptimizationLevel = 2; // all
private ConstraintSet mConstraintSet = null;
private String mTitle;
private int mConstraintSetId = -1;
private HashMap<String, Integer> mDesignIds = new HashMap<>();
/**
* @hide
*/
public final static int DESIGN_INFO_ID = 0;
/**
* @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(Context context) {
super(context);
init(null);
}
public ConstraintLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
@Override
public void setId(int id) {
mChildrenByIds.remove(getId());
super.setId(id);
mChildrenByIds.put(getId(), this);
}
public void setTitle(String title) {
mTitle = title;
}
public String getTitle() {
return mTitle;
}
private void init(AttributeSet attrs) {
mLayoutWidget.setCompanionWidget(this);
mChildrenByIds.put(getId(), this);
mConstraintSet = null;
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ConstraintLayout_Layout);
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_title) {
mTitle = a.getString(attr);
} 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);
}
/**
* {@inheritDoc}
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
onViewAdded(child);
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeView(View view) {
super.removeView(view);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
onViewRemoved(view);
}
}
/**
* {@inheritDoc}
*/
@Override
public void onViewAdded(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
super.onViewAdded(view);
}
ConstraintWidget widget = getViewWidget(view);
if (view instanceof android.support.constraint.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;
}
/**
* {@inheritDoc}
*/
@Override
public void onViewRemoved(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
super.onViewRemoved(view);
}
mChildrenByIds.remove(view.getId());
mLayoutWidget.remove(getViewWidget(view));
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 void 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) {
mVariableDimensionsWidgets.clear();
setChildrenConstraints();
}
}
private void setChildrenConstraints() {
final boolean isInEditMode = isInEditMode();
final int count = getChildCount();
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());
} 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);
}
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);
}
}
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();
layoutParams.validate();
if (layoutParams.helped) {
layoutParams.helped = false;
} else {
widget.reset();
}
widget.setVisibility(child.getVisibility());
if (layoutParams.isInPlaceholder) {
widget.setVisibility(View.GONE);
}
widget.setCompanionWidget(child);
mLayoutWidget.add(widget);
if (!layoutParams.verticalDimensionFixed || !layoutParams.horizontalDimensionFixed) {
mVariableDimensionsWidgets.add(widget);
}
if (layoutParams.isGuideline) {
Guideline guideline = (Guideline) widget;
if (layoutParams.guideBegin != -1) {
guideline.setGuideBegin(layoutParams.guideBegin);
}
if (layoutParams.guideEnd != -1) {
guideline.setGuideEnd(layoutParams.guideEnd);
}
if (layoutParams.guidePercent != -1) {
guideline.setGuidePercent(layoutParams.guidePercent);
}
} else if ((layoutParams.resolvedLeftToLeft != UNSET)
|| (layoutParams.resolvedLeftToRight != UNSET)
|| (layoutParams.resolvedRightToLeft != UNSET)
|| (layoutParams.resolvedRightToRight != UNSET)
|| (layoutParams.topToTop != UNSET)
|| (layoutParams.topToBottom != UNSET)
|| (layoutParams.bottomToTop != UNSET)
|| (layoutParams.bottomToBottom != UNSET)
|| (layoutParams.baselineToBaseline != UNSET)
|| (layoutParams.editorAbsoluteX != UNSET)
|| (layoutParams.editorAbsoluteY != UNSET)
|| (layoutParams.width == MATCH_PARENT)
|| (layoutParams.height == MATCH_PARENT)) {
// 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;
}
}
}
// Left constraint
if (resolvedLeftToLeft != UNSET) {
ConstraintWidget target = getTargetWidget(resolvedLeftToLeft);
if (target != null) {
widget.immediateConnect(ConstraintAnchor.Type.LEFT, target,
ConstraintAnchor.Type.LEFT, layoutParams.leftMargin,
resolveGoneLeftMargin);
}
} else if (resolvedLeftToRight != UNSET) {
ConstraintWidget target = getTargetWidget(resolvedLeftToRight);
if (target != null) {
widget.immediateConnect(ConstraintAnchor.Type.LEFT, target,
ConstraintAnchor.Type.RIGHT, layoutParams.leftMargin,
resolveGoneLeftMargin);
}
}
// Right constraint
if (resolvedRightToLeft != UNSET) {
ConstraintWidget target = getTargetWidget(resolvedRightToLeft);
if (target != null) {
widget.immediateConnect(ConstraintAnchor.Type.RIGHT, target,
ConstraintAnchor.Type.LEFT, layoutParams.rightMargin,
resolveGoneRightMargin);
}
} else if (resolvedRightToRight != UNSET) {
ConstraintWidget target = getTargetWidget(resolvedRightToRight);
if (target != null) {
widget.immediateConnect(ConstraintAnchor.Type.RIGHT, target,
ConstraintAnchor.Type.RIGHT, layoutParams.rightMargin,
resolveGoneRightMargin);
}
}
// Top constraint
if (layoutParams.topToTop != UNSET) {
ConstraintWidget target = getTargetWidget(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 = getTargetWidget(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 = getTargetWidget(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 = getTargetWidget(layoutParams.bottomToBottom);
if (target != null) {
widget.immediateConnect(ConstraintAnchor.Type.BOTTOM, target,
ConstraintAnchor.Type.BOTTOM, layoutParams.bottomMargin,
layoutParams.goneBottomMargin);
}
}
// Baseline constraint
if (layoutParams.baselineToBaseline != UNSET) {
View view = mChildrenByIds.get(layoutParams.baselineToBaseline);
ConstraintWidget target = getTargetWidget(layoutParams.baselineToBaseline);
if (target != null && view != null && view.getLayoutParams() instanceof LayoutParams) {
LayoutParams targetParams = (LayoutParams) view.getLayoutParams();
layoutParams.needsBaseline = true;
targetParams.needsBaseline = true;
ConstraintAnchor baseline = widget.getAnchor(ConstraintAnchor.Type.BASELINE);
ConstraintAnchor targetBaseline =
target.getAnchor(ConstraintAnchor.Type.BASELINE);
baseline.connect(targetBaseline, 0, -1, ConstraintAnchor.Strength.STRONG,
ConstraintAnchor.USER_CREATOR, true);
widget.getAnchor(ConstraintAnchor.Type.TOP).reset();
widget.getAnchor(ConstraintAnchor.Type.BOTTOM).reset();
}
}
if (resolvedHorizontalBias >= 0 && resolvedHorizontalBias != 0.5f) {
widget.setHorizontalBiasPercent(resolvedHorizontalBias);
}
if (layoutParams.verticalBias >= 0 && layoutParams.verticalBias != 0.5f) {
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 == LayoutParams.MATCH_PARENT) {
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.verticalDimensionFixed) {
if (layoutParams.height == LayoutParams.MATCH_PARENT) {
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.dimensionRatio != null) {
widget.setDimensionRatio(layoutParams.dimensionRatio);
}
widget.setHorizontalWeight(layoutParams.horizontalWeight);
widget.setVerticalWeight(layoutParams.verticalWeight);
widget.setHorizontalChainStyle(layoutParams.horizontalChainStyle);
widget.setVerticalChainStyle(layoutParams.verticalChainStyle);
widget.setHorizontalMatchStyle(layoutParams.matchConstraintDefaultWidth,
layoutParams.matchConstraintMinWidth, layoutParams.matchConstraintMaxWidth,
layoutParams.matchConstraintPercentWidth);
widget.setVerticalMatchStyle(layoutParams.matchConstraintDefaultHeight,
layoutParams.matchConstraintMinHeight, layoutParams.matchConstraintMaxHeight,
layoutParams.matchConstraintPercentHeight);
}
}
}
private final ConstraintWidget getTargetWidget(int id) {
if (id == LayoutParams.PARENT_ID) {
return mLayoutWidget;
} else {
View view = mChildrenByIds.get(id);
if (view == this) {
return mLayoutWidget;
}
return view == null ? null : ((LayoutParams) view.getLayoutParams()).widget;
}
}
public final ConstraintWidget getViewWidget(View view) {
if (view == this) {
return mLayoutWidget;
}
return view == null ? null : ((LayoutParams) view.getLayoutParams()).widget;
}
private void internalMeasureChildren(int parentWidthSpec, int parentHeightSpec) {
int heightPadding = getPaddingTop() + getPaddingBottom();
int widthPadding = getPaddingLeft() + getPaddingRight();
final int widgetsCount = getChildCount();
for (int i = 0; i < widgetsCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
LayoutParams params = (LayoutParams) child.getLayoutParams();
ConstraintWidget widget = params.widget;
if (params.isGuideline || params.isHelper) {
continue;
}
int width = params.width;
int height = params.height;
// Don't need to measure widgets that are MATCH_CONSTRAINT on both dimensions,
// unless they are marked as MATCH_CONSTRAINT_WRAP
boolean doMeasure =
(params.horizontalDimensionFixed
|| params.verticalDimensionFixed)
|| (!params.horizontalDimensionFixed
&& (params.matchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP)
|| params.width == MATCH_PARENT)
|| (!params.verticalDimensionFixed
&& (params.matchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP
|| params.height == MATCH_PARENT));
boolean didWrapMeasureWidth = false;
boolean didWrapMeasureHeight = false;
if (doMeasure) {
final int childWidthMeasureSpec;
final int childHeightMeasureSpec;
if (width == MATCH_CONSTRAINT || width == MATCH_PARENT) {
childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec,
widthPadding, LayoutParams.WRAP_CONTENT);
didWrapMeasureWidth = true;
} else {
childWidthMeasureSpec = getChildMeasureSpec(parentWidthSpec,
widthPadding, width);
}
if (height == MATCH_CONSTRAINT || height == MATCH_PARENT) {
childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec,
heightPadding, LayoutParams.WRAP_CONTENT);
didWrapMeasureHeight = true;
} else {
childHeightMeasureSpec = getChildMeasureSpec(parentHeightSpec,
heightPadding, height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
widget.setWidthWrapContent(width == WRAP_CONTENT);
widget.setHeightWrapContent(height == WRAP_CONTENT);
width = child.getMeasuredWidth();
height = child.getMeasuredHeight();
}
widget.setWidth(width);
widget.setHeight(height);
if (didWrapMeasureWidth) {
widget.setWrapWidth(width);
}
if (didWrapMeasureHeight) {
widget.setWrapHeight(height);
}
if (params.needsBaseline) {
int baseline = child.getBaseline();
if (baseline != -1) {
widget.setBaselineDistance(baseline);
}
}
}
for (int i = 0; i < widgetsCount; i++) {
final View child = getChildAt(i);
if (child instanceof Placeholder) {
((Placeholder) child).updatePostMeasure(this);
}
}
// TODO refactor into an updatePostMeasure interface
final int helperCount = mConstraintHelpers.size();
if (helperCount > 0) {
for (int i = 0; i < helperCount; i++) {
ConstraintHelper helper = mConstraintHelpers.get(i);
helper.updatePostMeasure(this);
}
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
mLayoutWidget.setX(paddingLeft);
mLayoutWidget.setY(paddingTop);
setSelfDimensionBehaviour(widthMeasureSpec, heightMeasureSpec);
int startingWidth = mLayoutWidget.getWidth();
int startingHeight = mLayoutWidget.getHeight();
if (mDirtyHierarchy) {
mDirtyHierarchy = false;
updateHierarchy();
}
internalMeasureChildren(widthMeasureSpec, heightMeasureSpec);
//noinspection PointlessBooleanExpression
if (ALLOWS_EMBEDDED && mLayoutWidget.getParent() != null) {
setVisibility(INVISIBLE);
return;
}
// let's solve the linear system.
if (getChildCount() > 0) {
solveLinearSystem(); // first pass
}
int childState = 0;
// let's update the size dependent widgets if any...
final int sizeDependentWidgetsCount = mVariableDimensionsWidgets.size();
int heightPadding = paddingTop + getPaddingBottom();
int widthPadding = paddingLeft + getPaddingRight();
if (sizeDependentWidgetsCount > 0) {
boolean needSolverPass = false;
boolean containerWrapWidth = mLayoutWidget.getHorizontalDimensionBehaviour()
== ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
boolean containerWrapHeight = mLayoutWidget.getVerticalDimensionBehaviour()
== ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
int minWidth = Math.max(mLayoutWidget.getWidth(), mMinWidth);
int minHeight = Math.max(mLayoutWidget.getHeight(), mMinHeight);
for (int i = 0; i < sizeDependentWidgetsCount; i++) {
ConstraintWidget widget = mVariableDimensionsWidgets.get(i);
View child = (View) widget.getCompanionWidget();
if (child == null) {
continue;
}
ConstraintLayout.LayoutParams params = (LayoutParams) child.getLayoutParams();
if (params.isHelper || params.isGuideline) {
continue;
}
if (child.getVisibility() == View.GONE) {
continue;
}
int widthSpec = 0;
int heightSpec = 0;
if (params.width == WRAP_CONTENT) {
widthSpec = getChildMeasureSpec(widthMeasureSpec, widthPadding, params.width);
} else {
widthSpec = MeasureSpec.makeMeasureSpec(widget.getWidth(), MeasureSpec.EXACTLY);
}
if (params.height == WRAP_CONTENT) {
heightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding, params.height);
} else {
heightSpec = MeasureSpec.makeMeasureSpec(widget.getHeight(), MeasureSpec.EXACTLY);
}
// we need to re-measure the child...
child.measure(widthSpec, heightSpec);
int measuredWidth = child.getMeasuredWidth();
int measuredHeight = child.getMeasuredHeight();
if (measuredWidth != widget.getWidth()) {
widget.setWidth(measuredWidth);
if (containerWrapWidth && widget.getRight() > minWidth) {
int w = widget.getRight()
+ widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin();
minWidth = Math.max(minWidth, w);
}
needSolverPass = true;
}
if (measuredHeight != widget.getHeight()) {
widget.setHeight(measuredHeight);
if (containerWrapHeight && widget.getBottom() > minHeight) {
int h = widget.getBottom()
+ widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getMargin();
minHeight = Math.max(minHeight, h);
}
needSolverPass = true;
}
if (params.needsBaseline) {
int baseline = child.getBaseline();
if (baseline != -1 && baseline != widget.getBaselineDistance()) {
widget.setBaselineDistance(baseline);
needSolverPass = true;
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
}
if (needSolverPass) {
mLayoutWidget.setWidth(startingWidth);
mLayoutWidget.setHeight(startingHeight);
solveLinearSystem();
needSolverPass = false;
if (mLayoutWidget.getWidth() < minWidth) {
mLayoutWidget.setWidth(minWidth);
needSolverPass = true;
}
if (mLayoutWidget.getHeight() < minHeight) {
mLayoutWidget.setHeight(minHeight);
needSolverPass = true;
}
if (needSolverPass) {
solveLinearSystem();
}
}
for (int i = 0; i < sizeDependentWidgetsCount; i++) {
ConstraintWidget widget = mVariableDimensionsWidgets.get(i);
View child = (View) widget.getCompanionWidget();
if (child == null) {
continue;
}
if (child.getWidth() != widget.getWidth() || child.getHeight() != widget.getHeight()) {
int widthSpec = MeasureSpec.makeMeasureSpec(widget.getWidth(), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(widget.getHeight(), MeasureSpec.EXACTLY);
child.measure(widthSpec, heightSpec);
}
}
}
int androidLayoutWidth = mLayoutWidget.getWidth() + widthPadding;
int androidLayoutHeight = mLayoutWidget.getHeight() + heightPadding;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
int resolvedWidthSize = resolveSizeAndState(androidLayoutWidth, widthMeasureSpec, childState);
int resolvedHeightSize = resolveSizeAndState(androidLayoutHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT);
resolvedWidthSize = Math.min(mMaxWidth, resolvedWidthSize);
resolvedHeightSize = Math.min(mMaxHeight, resolvedHeightSize);
resolvedWidthSize &= MEASURED_SIZE_MASK;
resolvedHeightSize &= MEASURED_SIZE_MASK;
if (mLayoutWidget.isWidthMeasuredTooSmall()) {
resolvedWidthSize |= MEASURED_STATE_TOO_SMALL;
}
if (mLayoutWidget.isHeightMeasuredTooSmall()) {
resolvedHeightSize |= MEASURED_STATE_TOO_SMALL;
}
setMeasuredDimension(resolvedWidthSize, resolvedHeightSize);
} else {
setMeasuredDimension(androidLayoutWidth, androidLayoutHeight);
}
}
private void setSelfDimensionBehaviour(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 heightPadding = getPaddingTop() + getPaddingBottom();
int widthPadding = getPaddingLeft() + getPaddingRight();
ConstraintWidget.DimensionBehaviour widthBehaviour = ConstraintWidget.DimensionBehaviour.FIXED;
ConstraintWidget.DimensionBehaviour heightBehaviour = ConstraintWidget.DimensionBehaviour.FIXED;
int desiredWidth = 0;
int desiredHeight = 0;
ViewGroup.LayoutParams params = getLayoutParams();
switch (widthMode) {
case MeasureSpec.AT_MOST: {
widthBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
desiredWidth = widthSize;
}
break;
case MeasureSpec.UNSPECIFIED: {
widthBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
}
break;
case MeasureSpec.EXACTLY: {
desiredWidth = Math.min(mMaxWidth, widthSize) - widthPadding;
}
}
switch (heightMode) {
case MeasureSpec.AT_MOST: {
heightBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
desiredHeight = heightSize;
}
break;
case MeasureSpec.UNSPECIFIED: {
heightBehaviour = ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
}
break;
case MeasureSpec.EXACTLY: {
desiredHeight = Math.min(mMaxHeight, heightSize) - heightPadding;
}
}
mLayoutWidget.setMinWidth(0);
mLayoutWidget.setMinHeight(0);
mLayoutWidget.setHorizontalDimensionBehaviour(widthBehaviour);
mLayoutWidget.setWidth(desiredWidth);
mLayoutWidget.setVerticalDimensionBehaviour(heightBehaviour);
mLayoutWidget.setHeight(desiredHeight);
mLayoutWidget.setMinWidth(mMinWidth - getPaddingLeft() - getPaddingRight());
mLayoutWidget.setMinHeight(mMinHeight - getPaddingTop() - getPaddingBottom());
}
/**
* @hide
*
* Solve the linear system
*/
protected void solveLinearSystem() {
if (SIMPLE_LAYOUT) {
mLayoutWidget.layout();
} else {
int groups = mLayoutWidget.layoutFindGroupsSimple();
mLayoutWidget.layoutWithGroup(groups);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int widgetsCount = getChildCount();
final boolean isInEditMode = isInEditMode();
final int helperCount = mConstraintHelpers.size();
if (helperCount > 0) {
for (int i = 0; i < helperCount; i++) {
ConstraintHelper helper = mConstraintHelpers.get(i);
helper.updatePostLayout(this);
}
}
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 && !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.getDrawX();
int t = widget.getDrawY();
int r = l + widget.getWidth();
int b = t + widget.getHeight();
if (ALLOWS_EMBEDDED) {
if (getParent() instanceof ConstraintLayout) {
int dx = 0;
int dy = 0;
ConstraintWidget item = mLayoutWidget; // start with ourselves
while (item != null) {
dx += item.getDrawX();
dy += item.getDrawY();
item = item.getParent();
}
l -= dx;
t -= dy;
r -= dx;
b -= dy;
}
}
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);
}
}
}
}
/**
* @hide
*
* Set the optimization level for the layout resolution
* The level can be one of:
* <ul>
* <li>ConstraintWidgetContainer.OPTIMIZATION_NONE</li>
* <li>ConstraintWidgetContainer.OPTIMIZATION_ALL</li>
* <li>ConstraintWidgetContainer.OPTIMIZATION_BASIC</li>
* <li>ConstraintWidgetContainer.OPTIMIZATION_CHAIN </li>
* </ul>
* @param level optimization level
*/
public void setOptimizationLevel(int level) {
mLayoutWidget.setOptimizationLevel(level);
}
/**
* {@inheritDoc}
*/
@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;
}
/**
* Return a direct child view by its id if it exists
*
* @param id the view id
* @return the child view, can return null
*/
public View getViewById(int id) {
return mChildrenByIds.get(id);
}
/**
* 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;
/**
* 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;
/**
* 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 top or left 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 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 = UNSET;
/**
* The top margin to use when the target is gone.
*/
public int goneTopMargin = UNSET;
/**
* The right margin to use when the target is gone
*/
public int goneRightMargin = UNSET;
/**
* The bottom margin to use when the target is gone.
*/
public int goneBottomMargin = UNSET;
/**
* The start margin to use when the target is gone.
*/
public int goneStartMargin = UNSET;
/**
* The end margin to use when the target is gone.
*/
public int goneEndMargin = UNSET;
/**
* 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 = 0;
/**
* 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 = 0;
/**
* 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>{@see CHAIN_SPREAD} -- the elements will be spread out</li>
* <li>{@see CHAIN_SPREAD_INSIDE} -- similar, but the endpoints of the chain will not be spread out</li>
* <li>{@see 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>{@see CHAIN_SPREAD} -- the elements will be spread out</li>
* <li>{@see CHAIN_SPREAD_INSIDE} -- similar, but the endpoints of the chain will not be spread out</li>
* <li>{@see 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>{@see MATCH_CONSTRAINT_SPREAD} -- the default. The dimension will expand up to the constraints, minus margins</li>
* <li>{@see MATCH_CONSTRAINT_WRAP} -- 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>{@see 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>{@see MATCH_CONSTRAINT_SPREAD} -- the default. The dimension will expand up to the constraints, minus margins</li>
* <li>{@see MATCH_CONSTRAINT_WRAP} -- 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>{@see 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 an 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 an 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 an 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 an 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;
// Internal use only
boolean horizontalDimensionFixed = true;
boolean verticalDimensionFixed = true;
boolean needsBaseline = false;
boolean isGuideline = false;
boolean isHelper = false;
boolean isInPlaceholder = false;
int resolvedLeftToLeft = UNSET;
int resolvedLeftToRight = UNSET;
int resolvedRightToLeft = UNSET;
int resolvedRightToRight = UNSET;
int resolveGoneLeftMargin = UNSET;
int resolveGoneRightMargin = UNSET;
float resolvedHorizontalBias = 0.5f;
ConstraintWidget widget = new ConstraintWidget();
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);
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.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.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.matchConstraintDefaultWidth = source.matchConstraintDefaultWidth;
this.matchConstraintDefaultHeight = source.matchConstraintDefaultHeight;
this.matchConstraintMinWidth = source.matchConstraintMinWidth;
this.matchConstraintMaxWidth = source.matchConstraintMaxWidth;
this.matchConstraintMinHeight = source.matchConstraintMinHeight;
this.matchConstraintMaxHeight = source.matchConstraintMaxHeight;
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.widget = source.widget;
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ConstraintLayout_Layout);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintLeft_toLeftOf) {
leftToLeft = a.getResourceId(attr, leftToLeft);
if (leftToLeft == UNSET) {
leftToLeft = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintLeft_toRightOf) {
leftToRight = a.getResourceId(attr, leftToRight);
if (leftToRight == UNSET) {
leftToRight = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintRight_toLeftOf) {
rightToLeft = a.getResourceId(attr, rightToLeft);
if (rightToLeft == UNSET) {
rightToLeft = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintRight_toRightOf) {
rightToRight = a.getResourceId(attr, rightToRight);
if (rightToRight == UNSET) {
rightToRight = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintTop_toTopOf) {
topToTop = a.getResourceId(attr, topToTop);
if (topToTop == UNSET) {
topToTop = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintTop_toBottomOf) {
topToBottom = a.getResourceId(attr, topToBottom);
if (topToBottom == UNSET) {
topToBottom = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintBottom_toTopOf) {
bottomToTop = a.getResourceId(attr, bottomToTop);
if (bottomToTop == UNSET) {
bottomToTop = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintBottom_toBottomOf) {
bottomToBottom = a.getResourceId(attr, bottomToBottom);
if (bottomToBottom == UNSET) {
bottomToBottom = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintBaseline_toBaselineOf) {
baselineToBaseline = a.getResourceId(attr, baselineToBaseline);
if (baselineToBaseline == UNSET) {
baselineToBaseline = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_editor_absoluteX) {
editorAbsoluteX = a.getDimensionPixelOffset(attr, editorAbsoluteX);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_editor_absoluteY) {
editorAbsoluteY = a.getDimensionPixelOffset(attr, editorAbsoluteY);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintGuide_begin) {
guideBegin = a.getDimensionPixelOffset(attr, guideBegin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintGuide_end) {
guideEnd = a.getDimensionPixelOffset(attr, guideEnd);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintGuide_percent) {
guidePercent = a.getFloat(attr, guidePercent);
} else if (attr == R.styleable.ConstraintLayout_Layout_android_orientation) {
orientation = a.getInt(attr, orientation);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintStart_toEndOf) {
startToEnd = a.getResourceId(attr, startToEnd);
if (startToEnd == UNSET) {
startToEnd = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintStart_toStartOf) {
startToStart = a.getResourceId(attr, startToStart);
if (startToStart == UNSET) {
startToStart = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintEnd_toStartOf) {
endToStart = a.getResourceId(attr, endToStart);
if (endToStart == UNSET) {
endToStart = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintEnd_toEndOf) {
endToEnd = a.getResourceId(attr, endToEnd);
if (endToEnd == UNSET) {
endToEnd = a.getInt(attr, UNSET);
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_goneMarginLeft) {
goneLeftMargin = a.getDimensionPixelSize(attr, goneLeftMargin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_goneMarginTop) {
goneTopMargin = a.getDimensionPixelSize(attr, goneTopMargin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_goneMarginRight) {
goneRightMargin = a.getDimensionPixelSize(attr, goneRightMargin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_goneMarginBottom) {
goneBottomMargin = a.getDimensionPixelSize(attr, goneBottomMargin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_goneMarginStart) {
goneStartMargin = a.getDimensionPixelSize(attr, goneStartMargin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_goneMarginEnd) {
goneEndMargin = a.getDimensionPixelSize(attr, goneEndMargin);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHorizontal_bias) {
horizontalBias = a.getFloat(attr, horizontalBias);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintVertical_bias) {
verticalBias = a.getFloat(attr, verticalBias);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintDimensionRatio) {
dimensionRatio = a.getString(attr);
dimensionRatioValue = Float.NaN;
dimensionRatioSide = UNSET;
if (dimensionRatio != null) {
int len = dimensionRatio.length();
int commaIndex = dimensionRatio.indexOf(',');
if (commaIndex > 0 && commaIndex < len - 1) {
String dimension = dimensionRatio.substring(0, commaIndex);
if (dimension.equalsIgnoreCase("W")) {
dimensionRatioSide = HORIZONTAL;
} else if (dimension.equalsIgnoreCase("H")) {
dimensionRatioSide = VERTICAL;
}
commaIndex++;
} else {
commaIndex = 0;
}
int colonIndex = dimensionRatio.indexOf(':');
if (colonIndex >= 0 && colonIndex < len - 1) {
String nominator = dimensionRatio.substring(commaIndex, colonIndex);
String denominator = dimensionRatio.substring(colonIndex + 1);
if (nominator.length() > 0 && denominator.length() > 0) {
try {
float nominatorValue = Float.parseFloat(nominator);
float denominatorValue = Float.parseFloat(denominator);
if (nominatorValue > 0 && denominatorValue > 0) {
if (dimensionRatioSide == VERTICAL) {
dimensionRatioValue = Math.abs(denominatorValue / nominatorValue);
} else {
dimensionRatioValue = Math.abs(nominatorValue / denominatorValue);
}
}
} catch (NumberFormatException e) {
// Ignore
}
}
} else {
String r = dimensionRatio.substring(commaIndex);
if (r.length() > 0) {
try {
dimensionRatioValue = Float.parseFloat(r);
} catch (NumberFormatException e) {
// Ignore
}
}
}
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHorizontal_weight) {
horizontalWeight = a.getFloat(attr, 0);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintVertical_weight) {
verticalWeight = a.getFloat(attr, 0);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHorizontal_chainStyle) {
horizontalChainStyle = a.getInt(attr, CHAIN_SPREAD);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintVertical_chainStyle) {
verticalChainStyle = a.getInt(attr, CHAIN_SPREAD);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintWidth_default) {
matchConstraintDefaultWidth = a.getInt(attr, MATCH_CONSTRAINT_SPREAD);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHeight_default) {
matchConstraintDefaultHeight = a.getInt(attr, MATCH_CONSTRAINT_SPREAD);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintWidth_min) {
try {
matchConstraintMinWidth = a.getDimensionPixelSize(attr, matchConstraintMinWidth);
} catch (android.view.InflateException e) {
int value = a.getInt(attr, matchConstraintMinWidth);
if (value == WRAP_CONTENT) {
matchConstraintMinWidth = WRAP_CONTENT;
}
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintWidth_max) {
try {
matchConstraintMaxWidth = a.getDimensionPixelSize(attr, matchConstraintMaxWidth);
} catch (android.view.InflateException e) {
int value = a.getInt(attr, matchConstraintMaxWidth);
if (value == WRAP_CONTENT) {
matchConstraintMaxWidth = WRAP_CONTENT;
}
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintWidth_percent) {
matchConstraintPercentWidth = a.getFloat(attr, matchConstraintPercentWidth);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHeight_min) {
try {
matchConstraintMinHeight = a.getDimensionPixelSize(attr, matchConstraintMinHeight);
} catch (android.view.InflateException e) {
int value = a.getInt(attr, matchConstraintMinHeight);
if (value == WRAP_CONTENT) {
matchConstraintMinHeight = WRAP_CONTENT;
}
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHeight_max) {
try {
matchConstraintMaxHeight = a.getDimensionPixelSize(attr, matchConstraintMaxHeight);
} catch (android.view.InflateException e) {
int value = a.getInt(attr, matchConstraintMaxHeight);
if (value == WRAP_CONTENT) {
matchConstraintMaxHeight = WRAP_CONTENT;
}
}
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintHeight_percent) {
matchConstraintPercentHeight = a.getFloat(attr, matchConstraintPercentHeight);
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintLeft_creator) {
// Skip
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintTop_creator) {
// Skip
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintRight_creator) {
// Skip
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintBottom_creator) {
// Skip
} else if (attr == R.styleable.ConstraintLayout_Layout_layout_constraintBaseline_creator) {
// Skip
} else {
// Skip
}
}
a.recycle();
validate();
}
public void validate() {
isGuideline = false;
horizontalDimensionFixed = true;
verticalDimensionFixed = true;
if (width == MATCH_CONSTRAINT || width == MATCH_PARENT) {
horizontalDimensionFixed = false;
}
if (height == MATCH_CONSTRAINT || height == MATCH_PARENT) {
verticalDimensionFixed = false;
}
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) {
super.resolveLayoutDirection(layoutDirection);
resolvedRightToLeft = UNSET;
resolvedRightToRight = UNSET;
resolvedLeftToLeft = UNSET;
resolvedLeftToRight = UNSET;
resolveGoneLeftMargin = UNSET;
resolveGoneRightMargin = UNSET;
resolveGoneLeftMargin = goneLeftMargin;
resolveGoneRightMargin = goneRightMargin;
resolvedHorizontalBias = horizontalBias;
boolean isRtl = (View.LAYOUT_DIRECTION_RTL == getLayoutDirection());
// 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 != UNSET) {
resolveGoneRightMargin = goneStartMargin;
}
if (goneEndMargin != UNSET) {
resolveGoneLeftMargin = goneEndMargin;
}
if (startEndDefined) {
resolvedHorizontalBias = 1 - horizontalBias;
}
} else {
if (startToEnd != UNSET) {
resolvedLeftToRight = startToEnd;
}
if (startToStart != UNSET) {
resolvedLeftToLeft = startToStart;
}
if (endToStart != UNSET) {
resolvedRightToLeft = endToStart;
}
if (endToEnd != UNSET) {
resolvedRightToRight = endToEnd;
}
if (goneStartMargin != UNSET) {
resolveGoneLeftMargin = goneStartMargin;
}
if (goneEndMargin != UNSET) {
resolveGoneRightMargin = goneEndMargin;
}
}
// if no constraint is defined via RTL attributes, use left/right if present
if (endToStart == UNSET && endToEnd == UNSET) {
if (rightToLeft != UNSET) {
resolvedRightToLeft = rightToLeft;
} else if (rightToRight != UNSET) {
resolvedRightToRight = rightToRight;
}
}
if (startToStart == UNSET && startToEnd == UNSET) {
if (leftToLeft != UNSET) {
resolvedLeftToLeft = leftToLeft;
} else if (leftToRight != UNSET) {
resolvedLeftToRight = leftToRight;
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void requestLayout() {
super.requestLayout();
mDirtyHierarchy = true;
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
}