blob: aadad1bec155ff5cee141348847dab8ed0e954e5 [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.solver.widgets;
import android.support.constraint.solver.LinearSystem;
import android.support.constraint.solver.Metrics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.support.constraint.solver.LinearSystem.FULL_DEBUG;
import static android.support.constraint.solver.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
/**
* A container of ConstraintWidget that can layout its children
*/
public class ConstraintWidgetContainer extends WidgetContainer {
private static final boolean USE_SNAPSHOT = true;
private static final int MAX_ITERATIONS = 8;
private static final boolean DEBUG = FULL_DEBUG;
private static final boolean DEBUG_LAYOUT = false;
static final boolean DEBUG_GRAPH = false;
private boolean mIsRtl = false;
public void fillMetrics(Metrics metrics) {
mSystem.fillMetrics(metrics);
}
protected LinearSystem mSystem = new LinearSystem();
private Snapshot mSnapshot;
int mPaddingLeft;
int mPaddingTop;
int mPaddingRight;
int mPaddingBottom;
int mHorizontalChainsSize = 0;
int mVerticalChainsSize = 0;
ChainHead[] mVerticalChainsArray = new ChainHead[4];
ChainHead[] mHorizontalChainsArray = new ChainHead[4];
public List<ConstraintWidgetGroup> mWidgetGroups = new ArrayList<>();
public boolean mGroupsWrapOptimized = false;
public boolean mHorizontalWrapOptimized = false;
public boolean mVerticalWrapOptimized = false;
public int mWrapFixedWidth = 0;
public int mWrapFixedHeight = 0;
private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD;
public boolean mSkipSolver = false;
private boolean mWidthMeasuredTooSmall = false;
private boolean mHeightMeasuredTooSmall = false;
/*-----------------------------------------------------------------------*/
// Construction
/*-----------------------------------------------------------------------*/
/**
* Default constructor
*/
public ConstraintWidgetContainer() {
}
/**
* Constructor
*
* @param x x position
* @param y y position
* @param width width of the layout
* @param height height of the layout
*/
public ConstraintWidgetContainer(int x, int y, int width, int height) {
super(x, y, width, height);
}
/**
* Constructor
*
* @param width width of the layout
* @param height height of the layout
*/
public ConstraintWidgetContainer(int width, int height) {
super(width, height);
}
/**
* Resolves the system directly when possible
*
* @param value optimization level
*/
public void setOptimizationLevel(int value) {
mOptimizationLevel = value;
}
/**
* Returns the current optimization level
*
* @return
*/
public int getOptimizationLevel() {
return mOptimizationLevel;
}
/**
* Returns true if the given feature should be optimized
* @param feature
* @return
*/
public boolean optimizeFor(int feature) {
return (mOptimizationLevel & feature) == feature;
}
/**
* Specify the xml type for the container
*
* @return
*/
@Override
public String getType() {
return "ConstraintLayout";
}
@Override
public void reset() {
mSystem.reset();
mPaddingLeft = 0;
mPaddingRight = 0;
mPaddingTop = 0;
mPaddingBottom = 0;
mWidgetGroups.clear();
mSkipSolver = false;
super.reset();
}
/**
* Return true if the width given is too small for the content layed out
*/
public boolean isWidthMeasuredTooSmall() { return mWidthMeasuredTooSmall; }
/**
* Return true if the height given is too small for the content layed out
*/
public boolean isHeightMeasuredTooSmall() { return mHeightMeasuredTooSmall; }
int mDebugSolverPassCount = 0;
/**
* Add this widget to the solver
*
* @param system the solver we want to add the widget to
*/
public boolean addChildrenToSolver(LinearSystem system) {
if (DEBUG) {
System.out.println("\n#######################################");
System.out.println("## ADD CHILDREN TO SOLVER (" + mDebugSolverPassCount + ") ##");
System.out.println("#######################################\n");
mDebugSolverPassCount++;
}
addToSolver(system);
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget instanceof ConstraintWidgetContainer) {
DimensionBehaviour horizontalBehaviour = widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL];
DimensionBehaviour verticalBehaviour = widget.mListDimensionBehaviors[DIMENSION_VERTICAL];
if (horizontalBehaviour == WRAP_CONTENT) {
widget.setHorizontalDimensionBehaviour(DimensionBehaviour.FIXED);
}
if (verticalBehaviour == WRAP_CONTENT) {
widget.setVerticalDimensionBehaviour(DimensionBehaviour.FIXED);
}
widget.addToSolver(system);
if (horizontalBehaviour == WRAP_CONTENT) {
widget.setHorizontalDimensionBehaviour(horizontalBehaviour);
}
if (verticalBehaviour == WRAP_CONTENT) {
widget.setVerticalDimensionBehaviour(verticalBehaviour);
}
} else {
Optimizer.checkMatchParent(this, system, widget);
widget.addToSolver(system);
}
}
if (mHorizontalChainsSize > 0) {
Chain.applyChainConstraints(this, system, HORIZONTAL);
}
if (mVerticalChainsSize > 0) {
Chain.applyChainConstraints(this, system, VERTICAL);
}
return true;
}
/**
* Update the frame of the layout and its children from the solver
*
* @param system the solver we get the values from.
*/
public void updateChildrenFromSolver(LinearSystem system, boolean flags[]) {
flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = false;
updateFromSolver(system);
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.updateFromSolver(system);
if (widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL] == DimensionBehaviour.MATCH_CONSTRAINT
&& widget.getWidth() < widget.getWrapWidth()) {
flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
}
if (widget.mListDimensionBehaviors[DIMENSION_VERTICAL] == DimensionBehaviour.MATCH_CONSTRAINT
&& widget.getHeight() < widget.getWrapHeight()) {
flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
}
}
}
/**
* Set the padding on this container. It will apply to the position of the children.
*
* @param left left padding
* @param top top padding
* @param right right padding
* @param bottom bottom padding
*/
public void setPadding(int left, int top, int right, int bottom) {
mPaddingLeft = left;
mPaddingTop = top;
mPaddingRight = right;
mPaddingBottom = bottom;
}
/**
* Set the rtl status. This has implications for Chains.
* @param isRtl true if we are in RTL.
*/
public void setRtl(boolean isRtl) {
mIsRtl = isRtl;
}
/**
* Returns the rtl status.
* @return true if in RTL, false otherwise.
*/
public boolean isRtl() {
return mIsRtl;
}
/*-----------------------------------------------------------------------*/
// Overloaded methods from ConstraintWidget
/*-----------------------------------------------------------------------*/
/**
* Graph analysis
* @param optimizationLevel the current optimisation level
*/
@Override
public void analyze(int optimizationLevel) {
super.analyze(optimizationLevel);
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
mChildren.get(i).analyze(optimizationLevel);
}
}
/**
* Layout the tree of widgets
*/
@Override
public void layout() {
if (DEBUG) {
System.out.println("\n#####################################");
System.out.println("## LAYOUT PASS ##");
System.out.println("#####################################\n");
mDebugSolverPassCount = 0;
}
int prex = mX;
int prey = mY;
int prew = Math.max(0, getWidth());
int preh = Math.max(0, getHeight());
mWidthMeasuredTooSmall = false;
mHeightMeasuredTooSmall = false;
if (mParent != null && USE_SNAPSHOT) {
if (mSnapshot == null) {
mSnapshot = new Snapshot(this);
}
mSnapshot.updateFrom(this);
// We clear ourselves of external anchors as
// well as repositioning us to (0, 0)
// before inserting us in the solver, so that our
// children's positions get computed relative to us.
setX(mPaddingLeft);
setY(mPaddingTop);
resetAnchors();
resetSolverVariables(mSystem.getCache());
} else {
mX = 0;
mY = 0;
}
if (mOptimizationLevel != Optimizer.OPTIMIZATION_NONE) {
if (!optimizeFor(Optimizer.OPTIMIZATION_DIMENSIONS)) {
optimizeReset();
}
if (!optimizeFor(Optimizer.OPTIMIZATION_GROUPS)) {
optimize();
}
mSystem.graphOptimizer = true;
} else {
mSystem.graphOptimizer = false;
}
boolean wrap_override = false;
DimensionBehaviour originalVerticalDimensionBehaviour = mListDimensionBehaviors[DIMENSION_VERTICAL];
DimensionBehaviour originalHorizontalDimensionBehaviour = mListDimensionBehaviors[DIMENSION_HORIZONTAL];
if (DEBUG_LAYOUT) {
System.out.println("layout with prew: " + prew + " (" + mListDimensionBehaviors[DIMENSION_HORIZONTAL]
+ ") preh: " + preh + " (" + mListDimensionBehaviors[DIMENSION_VERTICAL] + ")");
}
// Reset the chains before iterating on our children
resetChains();
if (mWidgetGroups.size() == 0){
mWidgetGroups.clear();
mWidgetGroups.add(0, new ConstraintWidgetGroup(mChildren));
}
int countSolve = 0;
final int groupSize = mWidgetGroups.size();
final List<ConstraintWidget> allChildren = mChildren;
boolean hasWrapContent = getHorizontalDimensionBehaviour() == WRAP_CONTENT || getVerticalDimensionBehaviour() == WRAP_CONTENT;
for (int groupIndex = 0; groupIndex < groupSize && !mSkipSolver; groupIndex++) {
if (mWidgetGroups.get(groupIndex).mSkipSolver) {
continue;
}
if (optimizeFor(Optimizer.OPTIMIZATION_GROUPS)) {
if (getHorizontalDimensionBehaviour() == DimensionBehaviour.FIXED && getVerticalDimensionBehaviour() == DimensionBehaviour.FIXED) {
mChildren = (ArrayList<ConstraintWidget>) mWidgetGroups.get(groupIndex).getWidgetsToSolve();
} else {
mChildren = (ArrayList<ConstraintWidget>) mWidgetGroups.get(groupIndex).mConstrainedGroup;
}
}
resetChains();
final int count = mChildren.size();
countSolve = 0;
// Before we solve our system, we should call layout() on any
// of our children that is a container.
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget instanceof WidgetContainer) {
((WidgetContainer) widget).layout();
}
}
// Now let's solve our system as usual
boolean needsSolving = true;
while (needsSolving) {
countSolve++;
try {
mSystem.reset();
resetChains();
if (DEBUG) {
setDebugSolverName(mSystem, getDebugName());
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget.getDebugName() != null) {
widget.setDebugSolverName(mSystem, widget.getDebugName());
}
}
} else {
createObjectVariables(mSystem);
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
widget.createObjectVariables(mSystem);
}
}
needsSolving = addChildrenToSolver(mSystem);
if (needsSolving) {
mSystem.minimize();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("EXCEPTION : " + e);
}
if (needsSolving) {
updateChildrenFromSolver(mSystem, Optimizer.flags);
} else {
updateFromSolver(mSystem);
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
if (widget.mListDimensionBehaviors[DIMENSION_HORIZONTAL]
== DimensionBehaviour.MATCH_CONSTRAINT
&& widget.getWidth() < widget.getWrapWidth()) {
Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
break;
}
if (widget.mListDimensionBehaviors[DIMENSION_VERTICAL]
== DimensionBehaviour.MATCH_CONSTRAINT
&& widget.getHeight() < widget.getWrapHeight()) {
Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS] = true;
break;
}
}
}
needsSolving = false;
if (hasWrapContent && countSolve < MAX_ITERATIONS
&& Optimizer.flags[Optimizer.FLAG_RECOMPUTE_BOUNDS]) {
// let's get the new bounds
int maxX = 0;
int maxY = 0;
for (int i = 0; i < count; i++) {
ConstraintWidget widget = mChildren.get(i);
maxX = Math.max(maxX, widget.mX + widget.getWidth());
maxY = Math.max(maxY, widget.mY + widget.getHeight());
}
maxX = Math.max(mMinWidth, maxX);
maxY = Math.max(mMinHeight, maxY);
if (originalHorizontalDimensionBehaviour == WRAP_CONTENT) {
if (getWidth() < maxX) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override width from " + getWidth() + " vs " + maxX);
}
setWidth(maxX);
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = WRAP_CONTENT; // force using the solver
wrap_override = true;
needsSolving = true;
}
}
if (originalVerticalDimensionBehaviour == WRAP_CONTENT) {
if (getHeight() < maxY) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override height from " + getHeight() + " vs " + maxY);
}
setHeight(maxY);
mListDimensionBehaviors[DIMENSION_VERTICAL] = WRAP_CONTENT; // force using the solver
wrap_override = true;
needsSolving = true;
}
}
}
if (true) {
int width = Math.max(mMinWidth, getWidth());
if (width > getWidth()) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 2, width from " + getWidth() + " vs " + width);
}
setWidth(width);
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
wrap_override = true;
needsSolving = true;
}
int height = Math.max(mMinHeight, getHeight());
if (height > getHeight()) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 2, height from " + getHeight() + " vs " + height);
}
setHeight(height);
mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
wrap_override = true;
needsSolving = true;
}
if (!wrap_override) {
if (mListDimensionBehaviors[DIMENSION_HORIZONTAL] == WRAP_CONTENT
&& prew > 0) {
if (getWidth() > prew) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 3, width from " + getWidth() + " vs "
+ prew);
}
mWidthMeasuredTooSmall = true;
wrap_override = true;
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = DimensionBehaviour.FIXED;
setWidth(prew);
needsSolving = true;
}
}
if (mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT
&& preh > 0) {
if (getHeight() > preh) {
if (DEBUG_LAYOUT) {
System.out.println(
"layout override 3, height from " + getHeight() + " vs "
+ preh);
}
mHeightMeasuredTooSmall = true;
wrap_override = true;
mListDimensionBehaviors[DIMENSION_VERTICAL] = DimensionBehaviour.FIXED;
setHeight(preh);
needsSolving = true;
}
}
}
}
}
if (DEBUG_LAYOUT) {
System.out.println(
"Solved system in " + countSolve + " iterations (" + getWidth() + " x "
+ getHeight() + ")");
}
// Update UnresolvedWidgets that did not need solver.
mWidgetGroups.get(groupIndex).updateUnresolvedWidgets();
}
mChildren = (ArrayList<ConstraintWidget>)allChildren;
if (mParent != null && USE_SNAPSHOT) {
int width = Math.max(mMinWidth, getWidth());
int height = Math.max(mMinHeight, getHeight());
// Let's restore our state...
mSnapshot.applyTo(this);
setWidth(width + mPaddingLeft + mPaddingRight);
setHeight(height + mPaddingTop + mPaddingBottom);
} else {
mX = prex;
mY = prey;
}
if (wrap_override) {
mListDimensionBehaviors[DIMENSION_HORIZONTAL] = originalHorizontalDimensionBehaviour;
mListDimensionBehaviors[DIMENSION_VERTICAL] = originalVerticalDimensionBehaviour;
}
if (DEBUG_GRAPH) {
for (int i = 0; i < mChildren.size(); i++) {
ConstraintWidget widget = mChildren.get(i);
System.out.println(
"final child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft
.getResolutionNode()
+ ", " + widget.mTop.getResolutionNode()
+ ", " + widget.mRight.getResolutionNode()
+ ", " + widget.mBottom.getResolutionNode());
}
}
resetSolverVariables(mSystem.getCache());
if (this == getRootConstraintContainer()) {
updateDrawPosition();
}
}
public void preOptimize() {
optimizeReset();
analyze(mOptimizationLevel);
}
public void solveGraph() {
ResolutionAnchor leftNode = getAnchor(ConstraintAnchor.Type.LEFT).getResolutionNode();
ResolutionAnchor topNode = getAnchor(ConstraintAnchor.Type.TOP).getResolutionNode();
if (DEBUG_GRAPH) {
System.out.println("### RESOLUTION ###");
}
leftNode.resolve(null, 0);
topNode.resolve(null, 0);
}
public void resetGraph() {
ResolutionAnchor leftNode = getAnchor(ConstraintAnchor.Type.LEFT).getResolutionNode();
ResolutionAnchor topNode = getAnchor(ConstraintAnchor.Type.TOP).getResolutionNode();
if (DEBUG_GRAPH) {
System.out.println("### RESET ###");
}
leftNode.invalidateAnchors();
topNode.invalidateAnchors();
leftNode.resolve(null, 0);
topNode.resolve(null, 0);
}
public void optimizeForDimensions(int width, int height) {
if (mListDimensionBehaviors[HORIZONTAL] != WRAP_CONTENT && mResolutionWidth != null) {
mResolutionWidth.resolve(width);
}
if (mListDimensionBehaviors[VERTICAL] != WRAP_CONTENT && mResolutionHeight != null) {
mResolutionHeight.resolve(height);
}
}
public void optimizeReset() {
final int count = mChildren.size();
resetResolutionNodes();
for (int i = 0; i < count; i++) {
mChildren.get(i).resetResolutionNodes();
}
}
public void optimize() {
if (DEBUG_GRAPH) {
System.out.println("### Graph resolution... " + mWidth + " x " + mHeight + " ###");
}
if (DEBUG_GRAPH) {
System.out.println("### Update Constraints Graph ###");
setDebugName("Root");
}
if (!optimizeFor(Optimizer.OPTIMIZATION_DIMENSIONS)) {
analyze(mOptimizationLevel);
}
if (DEBUG_GRAPH) {
for (int i = 0; i < mChildren.size(); i++) {
ConstraintWidget widget = mChildren.get(i);
System.out.println("(pre) child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode()
+ ", " + widget.mTop.getResolutionNode()
+ ", " + widget.mRight.getResolutionNode()
+ ", " + widget.mBottom.getResolutionNode());
}
}
solveGraph();
if (DEBUG_GRAPH) {
for (int i = 0; i < mChildren.size(); i++) {
ConstraintWidget widget = mChildren.get(i);
System.out.println("child [" + i + "/" + mChildren.size() + "] - " + widget.mLeft.getResolutionNode()
+ ", " + widget.mTop.getResolutionNode()
+ ", " + widget.mRight.getResolutionNode()
+ ", " + widget.mBottom.getResolutionNode());
}
}
}
/**
* Indicates if the container knows how to layout its content on its own
*
* @return true if the container does the layout, false otherwise
*/
public boolean handlesInternalConstraints() {
return false;
}
/*-----------------------------------------------------------------------*/
// Guidelines
/*-----------------------------------------------------------------------*/
/**
* Accessor to the vertical guidelines contained in the table.
*
* @return array of guidelines
*/
public ArrayList<Guideline> getVerticalGuidelines() {
ArrayList<Guideline> guidelines = new ArrayList<>();
for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) {
final ConstraintWidget widget = mChildren.get(i);
if (widget instanceof Guideline) {
Guideline guideline = (Guideline) widget;
if (guideline.getOrientation() == Guideline.VERTICAL) {
guidelines.add(guideline);
}
}
}
return guidelines;
}
/**
* Accessor to the horizontal guidelines contained in the table.
*
* @return array of guidelines
*/
public ArrayList<Guideline> getHorizontalGuidelines() {
ArrayList<Guideline> guidelines = new ArrayList<>();
for (int i = 0, mChildrenSize = mChildren.size(); i < mChildrenSize; i++) {
final ConstraintWidget widget = mChildren.get(i);
if (widget instanceof Guideline) {
Guideline guideline = (Guideline) widget;
if (guideline.getOrientation() == Guideline.HORIZONTAL) {
guidelines.add(guideline);
}
}
}
return guidelines;
}
public LinearSystem getSystem() {
return mSystem;
}
/*-----------------------------------------------------------------------*/
// Chains
/*-----------------------------------------------------------------------*/
/**
* Reset the chains array. Need to be called before layout.
*/
private void resetChains() {
mHorizontalChainsSize = 0;
mVerticalChainsSize = 0;
}
/**
* Add the chain which constraintWidget is part of. Called by ConstraintWidget::addToSolver()
*
* @param constraintWidget
* @param type HORIZONTAL or VERTICAL chain
*/
void addChain(ConstraintWidget constraintWidget, int type) {
ConstraintWidget widget = constraintWidget;
if (type == HORIZONTAL) {
addHorizontalChain(widget);
} else if (type == VERTICAL) {
addVerticalChain(widget);
}
}
/**
* Add a widget to the list of horizontal chains. The widget is the left-most widget
* of the chain which doesn't have a left dual connection.
*
* @param widget widget starting the chain
*/
private void addHorizontalChain(ConstraintWidget widget) {
if (mHorizontalChainsSize + 1 >= mHorizontalChainsArray.length) {
mHorizontalChainsArray = Arrays
.copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2);
}
mHorizontalChainsArray[mHorizontalChainsSize] = new ChainHead(widget, HORIZONTAL, isRtl());
mHorizontalChainsSize++;
}
/**
* Add a widget to the list of vertical chains. The widget is the top-most widget
* of the chain which doesn't have a top dual connection.
*
* @param widget widget starting the chain
*/
private void addVerticalChain(ConstraintWidget widget) {
if (mVerticalChainsSize + 1 >= mVerticalChainsArray.length) {
mVerticalChainsArray = Arrays
.copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2);
}
mVerticalChainsArray[mVerticalChainsSize] = new ChainHead(widget, VERTICAL, isRtl());
mVerticalChainsSize++;
}
/*-----------------------------------------------------------------------*/
// Widgets
/*-----------------------------------------------------------------------*/
/**
* {@link #mWidgetGroups} getter.
*
* @return The list of independently constrained widget groups.
*/
public List<ConstraintWidgetGroup> getWidgetGroups() {
return mWidgetGroups;
}
}