public class

DependencyGraph

extends java.lang.Object

 java.lang.Object

↳androidx.constraintlayout.core.widgets.analyzer.DependencyGraph

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout-core', version: '1.1.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout-core
  • version: 1.1.0-beta01

Artifact androidx.constraintlayout:constraintlayout-core:1.1.0-beta01 it located at Google repository (https://maven.google.com/)

Summary

Constructors
publicDependencyGraph(ConstraintWidgetContainer container)

Methods
public voidbuildGraph()

public voidbuildGraph(java.util.ArrayList<WidgetRun> runs)

public voiddefineTerminalWidgets(ConstraintWidget.DimensionBehaviour horizontalBehavior, ConstraintWidget.DimensionBehaviour verticalBehavior)

Find and mark terminal widgets (trailing widgets) -- they are the only ones we need to care for wrap_content checks

public booleandirectMeasure(boolean optimizeWrap)

Try to measure the layout by solving the graph of constraints directly

public booleandirectMeasureSetup(boolean optimizeWrap)

public booleandirectMeasureWithOrientation(boolean optimizeWrap, int orientation)

public voidinvalidateGraph()

Invalidate the graph of constraints

public voidinvalidateMeasures()

Mark the widgets as needing to be remeasured

public voidmeasureWidgets()

public voidsetMeasurer(BasicMeasure.Measurer measurer)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public DependencyGraph(ConstraintWidgetContainer container)

Methods

public void setMeasurer(BasicMeasure.Measurer measurer)

public void defineTerminalWidgets(ConstraintWidget.DimensionBehaviour horizontalBehavior, ConstraintWidget.DimensionBehaviour verticalBehavior)

Find and mark terminal widgets (trailing widgets) -- they are the only ones we need to care for wrap_content checks

public boolean directMeasure(boolean optimizeWrap)

Try to measure the layout by solving the graph of constraints directly

Parameters:

optimizeWrap: use the wrap_content optimizer

Returns:

true if all widgets have been resolved

public boolean directMeasureSetup(boolean optimizeWrap)

public boolean directMeasureWithOrientation(boolean optimizeWrap, int orientation)

public void measureWidgets()

public void invalidateGraph()

Invalidate the graph of constraints

public void invalidateMeasures()

Mark the widgets as needing to be remeasured

public void buildGraph()

public void buildGraph(java.util.ArrayList<WidgetRun> runs)

Source

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

package androidx.constraintlayout.core.widgets.analyzer;

import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.FIXED;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_PARENT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.UNKNOWN;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;

import androidx.constraintlayout.core.widgets.Barrier;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import androidx.constraintlayout.core.widgets.Guideline;
import androidx.constraintlayout.core.widgets.HelperWidget;

import java.util.ArrayList;
import java.util.HashSet;

public class DependencyGraph {
    private static final boolean USE_GROUPS = true;
    private ConstraintWidgetContainer mWidgetcontainer;
    private boolean mNeedBuildGraph = true;
    private boolean mNeedRedoMeasures = true;
    private ConstraintWidgetContainer mContainer;
    private ArrayList<WidgetRun> mRuns = new ArrayList<>();
    private static final boolean DEBUG = false;

    // TODO: Unused, should we delete?
    @SuppressWarnings("unused") private ArrayList<RunGroup> mRunGroups = new ArrayList<>();

    public DependencyGraph(ConstraintWidgetContainer container) {
        this.mWidgetcontainer = container;
        mContainer = container;
    }

    private BasicMeasure.Measurer mMeasurer = null;
    private BasicMeasure.Measure mMeasure = new BasicMeasure.Measure();

    public void setMeasurer(BasicMeasure.Measurer measurer) {
        mMeasurer = measurer;
    }

    private int computeWrap(ConstraintWidgetContainer container, int orientation) {
        final int count = mGroups.size();
        long wrapSize = 0;
        for (int i = 0; i < count; i++) {
            RunGroup run = mGroups.get(i);
            long size = run.computeWrapSize(container, orientation);
            wrapSize = Math.max(wrapSize, size);
        }
        return (int) wrapSize;
    }

    /**
     * Find and mark terminal widgets (trailing widgets) -- they are the only
     * ones we need to care for wrap_content checks
     */
    public void defineTerminalWidgets(ConstraintWidget.DimensionBehaviour horizontalBehavior,
            ConstraintWidget.DimensionBehaviour verticalBehavior) {
        if (mNeedBuildGraph) {
            buildGraph();

            if (USE_GROUPS) {
                boolean hasBarrier = false;
                for (ConstraintWidget widget : mWidgetcontainer.mChildren) {
                    widget.isTerminalWidget[HORIZONTAL] = true;
                    widget.isTerminalWidget[VERTICAL] = true;
                    if (widget instanceof Barrier) {
                        hasBarrier = true;
                    }
                }
                if (!hasBarrier) {
                    for (RunGroup group : mGroups) {
                        group.defineTerminalWidgets(horizontalBehavior == WRAP_CONTENT,
                                verticalBehavior == WRAP_CONTENT);
                    }
                }
            }
        }
    }

    /**
     * Try to measure the layout by solving the graph of constraints directly
     *
     * @param optimizeWrap use the wrap_content optimizer
     * @return true if all widgets have been resolved
     */
    public boolean directMeasure(boolean optimizeWrap) {
        optimizeWrap &= USE_GROUPS;

        if (mNeedBuildGraph || mNeedRedoMeasures) {
            for (ConstraintWidget widget : mWidgetcontainer.mChildren) {
                widget.ensureWidgetRuns();
                widget.measured = false;
                widget.mHorizontalRun.reset();
                widget.mVerticalRun.reset();
            }
            mWidgetcontainer.ensureWidgetRuns();
            mWidgetcontainer.measured = false;
            mWidgetcontainer.mHorizontalRun.reset();
            mWidgetcontainer.mVerticalRun.reset();
            mNeedRedoMeasures = false;
        }

        boolean avoid = basicMeasureWidgets(mContainer);
        if (avoid) {
            return false;
        }

        mWidgetcontainer.setX(0);
        mWidgetcontainer.setY(0);

        ConstraintWidget.DimensionBehaviour originalHorizontalDimension =
                mWidgetcontainer.getDimensionBehaviour(HORIZONTAL);
        ConstraintWidget.DimensionBehaviour originalVerticalDimension =
                mWidgetcontainer.getDimensionBehaviour(VERTICAL);

        if (mNeedBuildGraph) {
            buildGraph();
        }

        int x1 = mWidgetcontainer.getX();
        int y1 = mWidgetcontainer.getY();

        mWidgetcontainer.mHorizontalRun.start.resolve(x1);
        mWidgetcontainer.mVerticalRun.start.resolve(y1);

        // Let's do the easy steps first -- anything that can be immediately measured
        // Whatever is left for the dimension will be match_constraints.
        measureWidgets();

        // If we have to support wrap, let's see if we can compute it directly
        if (originalHorizontalDimension == WRAP_CONTENT
                || originalVerticalDimension == WRAP_CONTENT) {
            if (optimizeWrap) {
                for (WidgetRun run : mRuns) {
                    if (!run.supportsWrapComputation()) {
                        optimizeWrap = false;
                        break;
                    }
                }
            }

            if (optimizeWrap && originalHorizontalDimension == WRAP_CONTENT) {
                mWidgetcontainer.setHorizontalDimensionBehaviour(FIXED);
                mWidgetcontainer.setWidth(computeWrap(mWidgetcontainer, HORIZONTAL));
                mWidgetcontainer.mHorizontalRun.mDimension.resolve(mWidgetcontainer.getWidth());
            }
            if (optimizeWrap && originalVerticalDimension == WRAP_CONTENT) {
                mWidgetcontainer.setVerticalDimensionBehaviour(FIXED);
                mWidgetcontainer.setHeight(computeWrap(mWidgetcontainer, VERTICAL));
                mWidgetcontainer.mVerticalRun.mDimension.resolve(mWidgetcontainer.getHeight());
            }
        }

        boolean checkRoot = false;

        // Now, depending on our own dimension behavior, we may want to solve
        // one dimension before the other

        if (mWidgetcontainer.mListDimensionBehaviors[HORIZONTAL]
                == ConstraintWidget.DimensionBehaviour.FIXED
                || mWidgetcontainer.mListDimensionBehaviors[HORIZONTAL]
                == ConstraintWidget.DimensionBehaviour.MATCH_PARENT) {

            // solve horizontal dimension
            int x2 = x1 + mWidgetcontainer.getWidth();
            mWidgetcontainer.mHorizontalRun.end.resolve(x2);
            mWidgetcontainer.mHorizontalRun.mDimension.resolve(x2 - x1);
            measureWidgets();
            if (mWidgetcontainer.mListDimensionBehaviors[VERTICAL] == FIXED
                    || mWidgetcontainer.mListDimensionBehaviors[VERTICAL] == MATCH_PARENT) {
                int y2 = y1 + mWidgetcontainer.getHeight();
                mWidgetcontainer.mVerticalRun.end.resolve(y2);
                mWidgetcontainer.mVerticalRun.mDimension.resolve(y2 - y1);
            }
            measureWidgets();
            checkRoot = true;
        } else {
            // we'll bail out to the solver...
        }

        // Let's apply what we did resolve
        for (WidgetRun run : mRuns) {
            if (run.mWidget == mWidgetcontainer && !run.mResolved) {
                continue;
            }
            run.applyToWidget();
        }

        boolean allResolved = true;
        for (WidgetRun run : mRuns) {
            if (!checkRoot && run.mWidget == mWidgetcontainer) {
                continue;
            }
            if (!run.start.resolved) {
                allResolved = false;
                break;
            }
            if (!run.end.resolved && !(run instanceof GuidelineReference)) {
                allResolved = false;
                break;
            }
            if (!run.mDimension.resolved
                    && !(run instanceof ChainRun) && !(run instanceof GuidelineReference)) {
                allResolved = false;
                break;
            }
        }

        mWidgetcontainer.setHorizontalDimensionBehaviour(originalHorizontalDimension);
        mWidgetcontainer.setVerticalDimensionBehaviour(originalVerticalDimension);

        return allResolved;
    }

    // @TODO: add description
    public boolean directMeasureSetup(boolean optimizeWrap) {
        if (mNeedBuildGraph) {
            for (ConstraintWidget widget : mWidgetcontainer.mChildren) {
                widget.ensureWidgetRuns();
                widget.measured = false;
                widget.mHorizontalRun.mDimension.resolved = false;
                widget.mHorizontalRun.mResolved = false;
                widget.mHorizontalRun.reset();
                widget.mVerticalRun.mDimension.resolved = false;
                widget.mVerticalRun.mResolved = false;
                widget.mVerticalRun.reset();
            }
            mWidgetcontainer.ensureWidgetRuns();
            mWidgetcontainer.measured = false;
            mWidgetcontainer.mHorizontalRun.mDimension.resolved = false;
            mWidgetcontainer.mHorizontalRun.mResolved = false;
            mWidgetcontainer.mHorizontalRun.reset();
            mWidgetcontainer.mVerticalRun.mDimension.resolved = false;
            mWidgetcontainer.mVerticalRun.mResolved = false;
            mWidgetcontainer.mVerticalRun.reset();
            buildGraph();
        }

        boolean avoid = basicMeasureWidgets(mContainer);
        if (avoid) {
            return false;
        }

        mWidgetcontainer.setX(0);
        mWidgetcontainer.setY(0);
        mWidgetcontainer.mHorizontalRun.start.resolve(0);
        mWidgetcontainer.mVerticalRun.start.resolve(0);
        return true;
    }

    // @TODO: add description
    public boolean directMeasureWithOrientation(boolean optimizeWrap, int orientation) {
        optimizeWrap &= USE_GROUPS;

        ConstraintWidget.DimensionBehaviour originalHorizontalDimension =
                mWidgetcontainer.getDimensionBehaviour(HORIZONTAL);
        ConstraintWidget.DimensionBehaviour originalVerticalDimension =
                mWidgetcontainer.getDimensionBehaviour(VERTICAL);

        int x1 = mWidgetcontainer.getX();
        int y1 = mWidgetcontainer.getY();

        // If we have to support wrap, let's see if we can compute it directly
        if (optimizeWrap && (originalHorizontalDimension == WRAP_CONTENT
                || originalVerticalDimension == WRAP_CONTENT)) {
            for (WidgetRun run : mRuns) {
                if (run.orientation == orientation
                        && !run.supportsWrapComputation()) {
                    optimizeWrap = false;
                    break;
                }
            }

            if (orientation == HORIZONTAL) {
                if (optimizeWrap && originalHorizontalDimension == WRAP_CONTENT) {
                    mWidgetcontainer.setHorizontalDimensionBehaviour(FIXED);
                    mWidgetcontainer.setWidth(computeWrap(mWidgetcontainer, HORIZONTAL));
                    mWidgetcontainer.mHorizontalRun.mDimension.resolve(mWidgetcontainer.getWidth());
                }
            } else {
                if (optimizeWrap && originalVerticalDimension == WRAP_CONTENT) {
                    mWidgetcontainer.setVerticalDimensionBehaviour(FIXED);
                    mWidgetcontainer.setHeight(computeWrap(mWidgetcontainer, VERTICAL));
                    mWidgetcontainer.mVerticalRun.mDimension.resolve(mWidgetcontainer.getHeight());
                }
            }
        }

        boolean checkRoot = false;

        // Now, depending on our own dimension behavior, we may want to solve
        // one dimension before the other

        if (orientation == HORIZONTAL) {
            if (mWidgetcontainer.mListDimensionBehaviors[HORIZONTAL] == FIXED
                    || mWidgetcontainer.mListDimensionBehaviors[HORIZONTAL] == MATCH_PARENT) {
                int x2 = x1 + mWidgetcontainer.getWidth();
                mWidgetcontainer.mHorizontalRun.end.resolve(x2);
                mWidgetcontainer.mHorizontalRun.mDimension.resolve(x2 - x1);
                checkRoot = true;
            }
        } else {
            if (mWidgetcontainer.mListDimensionBehaviors[VERTICAL] == FIXED
                    || mWidgetcontainer.mListDimensionBehaviors[VERTICAL] == MATCH_PARENT) {
                int y2 = y1 + mWidgetcontainer.getHeight();
                mWidgetcontainer.mVerticalRun.end.resolve(y2);
                mWidgetcontainer.mVerticalRun.mDimension.resolve(y2 - y1);
                checkRoot = true;
            }
        }
        measureWidgets();

        // Let's apply what we did resolve
        for (WidgetRun run : mRuns) {
            if (run.orientation != orientation) {
                continue;
            }
            if (run.mWidget == mWidgetcontainer && !run.mResolved) {
                continue;
            }
            run.applyToWidget();
        }

        boolean allResolved = true;
        for (WidgetRun run : mRuns) {
            if (run.orientation != orientation) {
                continue;
            }
            if (!checkRoot && run.mWidget == mWidgetcontainer) {
                continue;
            }
            if (!run.start.resolved) {
                allResolved = false;
                break;
            }
            if (!run.end.resolved) {
                allResolved = false;
                break;
            }
            if (!(run instanceof ChainRun) && !run.mDimension.resolved) {
                allResolved = false;
                break;
            }
        }

        mWidgetcontainer.setHorizontalDimensionBehaviour(originalHorizontalDimension);
        mWidgetcontainer.setVerticalDimensionBehaviour(originalVerticalDimension);

        return allResolved;
    }

    /**
     * Convenience function to fill in the measure spec
     *
     * @param widget the widget to measure
     */
    private void measure(ConstraintWidget widget,
            ConstraintWidget.DimensionBehaviour horizontalBehavior,
            int horizontalDimension,
            ConstraintWidget.DimensionBehaviour verticalBehavior,
            int verticalDimension) {
        mMeasure.horizontalBehavior = horizontalBehavior;
        mMeasure.verticalBehavior = verticalBehavior;
        mMeasure.horizontalDimension = horizontalDimension;
        mMeasure.verticalDimension = verticalDimension;
        mMeasurer.measure(widget, mMeasure);
        widget.setWidth(mMeasure.measuredWidth);
        widget.setHeight(mMeasure.measuredHeight);
        widget.setHasBaseline(mMeasure.measuredHasBaseline);
        widget.setBaselineDistance(mMeasure.measuredBaseline);
    }

    private boolean basicMeasureWidgets(ConstraintWidgetContainer constraintWidgetContainer) {
        for (ConstraintWidget widget : constraintWidgetContainer.mChildren) {
            ConstraintWidget.DimensionBehaviour horizontal =
                    widget.mListDimensionBehaviors[HORIZONTAL];
            ConstraintWidget.DimensionBehaviour vertical = widget.mListDimensionBehaviors[VERTICAL];

            if (widget.getVisibility() == GONE) {
                widget.measured = true;
                continue;
            }

            // Basic validation
            // TODO: might move this earlier in the process
            if (widget.mMatchConstraintPercentWidth < 1 && horizontal == MATCH_CONSTRAINT) {
                widget.mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_PERCENT;
            }
            if (widget.mMatchConstraintPercentHeight < 1 && vertical == MATCH_CONSTRAINT) {
                widget.mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_PERCENT;
            }
            if (widget.getDimensionRatio() > 0) {
                if (horizontal == MATCH_CONSTRAINT
                        && (vertical == WRAP_CONTENT || vertical == FIXED)) {
                    widget.mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO;
                } else if (vertical == MATCH_CONSTRAINT
                        && (horizontal == WRAP_CONTENT || horizontal == FIXED)) {
                    widget.mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO;
                } else if (horizontal == MATCH_CONSTRAINT && vertical == MATCH_CONSTRAINT) {
                    if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD) {
                        widget.mMatchConstraintDefaultWidth = MATCH_CONSTRAINT_RATIO;
                    }
                    if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD) {
                        widget.mMatchConstraintDefaultHeight = MATCH_CONSTRAINT_RATIO;
                    }
                }
            }

            if (horizontal == MATCH_CONSTRAINT
                    && widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
                if (widget.mLeft.mTarget == null || widget.mRight.mTarget == null) {
                    horizontal = WRAP_CONTENT;
                }
            }
            if (vertical == MATCH_CONSTRAINT
                    && widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
                if (widget.mTop.mTarget == null || widget.mBottom.mTarget == null) {
                    vertical = WRAP_CONTENT;
                }
            }

            widget.mHorizontalRun.mDimensionBehavior = horizontal;
            widget.mHorizontalRun.matchConstraintsType = widget.mMatchConstraintDefaultWidth;
            widget.mVerticalRun.mDimensionBehavior = vertical;
            widget.mVerticalRun.matchConstraintsType = widget.mMatchConstraintDefaultHeight;

            if ((horizontal == MATCH_PARENT || horizontal == FIXED || horizontal == WRAP_CONTENT)
                    && (vertical == MATCH_PARENT
                    || vertical == FIXED || vertical == WRAP_CONTENT)) {
                int width = widget.getWidth();
                if (horizontal == MATCH_PARENT) {
                    width = constraintWidgetContainer.getWidth()
                            - widget.mLeft.mMargin - widget.mRight.mMargin;
                    horizontal = FIXED;
                }
                int height = widget.getHeight();
                if (vertical == MATCH_PARENT) {
                    height = constraintWidgetContainer.getHeight()
                            - widget.mTop.mMargin - widget.mBottom.mMargin;
                    vertical = FIXED;
                }
                measure(widget, horizontal, width, vertical, height);
                widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                widget.measured = true;
                continue;
            }

            if (horizontal == MATCH_CONSTRAINT && (vertical == WRAP_CONTENT || vertical == FIXED)) {
                if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_RATIO) {
                    if (vertical == WRAP_CONTENT) {
                        measure(widget, WRAP_CONTENT, 0, WRAP_CONTENT, 0);
                    }
                    int height = widget.getHeight();
                    int width = (int) (height * widget.mDimensionRatio + 0.5f);
                    measure(widget, FIXED, width, FIXED, height);
                    widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                    widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                    widget.measured = true;
                    continue;
                } else if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP) {
                    measure(widget, WRAP_CONTENT, 0, vertical, 0);
                    widget.mHorizontalRun.mDimension.wrapValue = widget.getWidth();
                    continue;
                } else if (widget.mMatchConstraintDefaultWidth
                        == ConstraintWidget.MATCH_CONSTRAINT_PERCENT) {
                    if (constraintWidgetContainer.mListDimensionBehaviors[HORIZONTAL] == FIXED
                            || constraintWidgetContainer.mListDimensionBehaviors[HORIZONTAL]
                            == MATCH_PARENT) {
                        float percent = widget.mMatchConstraintPercentWidth;
                        int width = (int) (0.5f + percent * constraintWidgetContainer.getWidth());
                        int height = widget.getHeight();
                        measure(widget, FIXED, width, vertical, height);
                        widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                        widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                        widget.measured = true;
                        continue;
                    }
                } else {
                    // let's verify we have both constraints
                    if (widget.mListAnchors[ConstraintWidget.ANCHOR_LEFT].mTarget == null
                            || widget.mListAnchors[ConstraintWidget.ANCHOR_RIGHT].mTarget == null) {
                        measure(widget, WRAP_CONTENT, 0, vertical, 0);
                        widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                        widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                        widget.measured = true;
                        continue;
                    }
                }
            }
            if (vertical == MATCH_CONSTRAINT
                    && (horizontal == WRAP_CONTENT || horizontal == FIXED)) {
                if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_RATIO) {
                    if (horizontal == WRAP_CONTENT) {
                        measure(widget, WRAP_CONTENT, 0, WRAP_CONTENT, 0);
                    }
                    int width = widget.getWidth();
                    float ratio = widget.mDimensionRatio;
                    if (widget.getDimensionRatioSide() == UNKNOWN) {
                        ratio = 1f / ratio;
                    }
                    int height = (int) (width * ratio + 0.5f);

                    measure(widget, FIXED, width, FIXED, height);
                    widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                    widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                    widget.measured = true;
                    continue;
                } else if (widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
                    measure(widget, horizontal, 0, WRAP_CONTENT, 0);
                    widget.mVerticalRun.mDimension.wrapValue = widget.getHeight();
                    continue;
                } else if (widget.mMatchConstraintDefaultHeight
                        == ConstraintWidget.MATCH_CONSTRAINT_PERCENT) {
                    if (constraintWidgetContainer.mListDimensionBehaviors[VERTICAL] == FIXED
                            || constraintWidgetContainer.mListDimensionBehaviors[VERTICAL]
                            == MATCH_PARENT) {
                        float percent = widget.mMatchConstraintPercentHeight;
                        int width = widget.getWidth();
                        int height = (int) (0.5f + percent * constraintWidgetContainer.getHeight());
                        measure(widget, horizontal, width, FIXED, height);
                        widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                        widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                        widget.measured = true;
                        continue;
                    }
                } else {
                    // let's verify we have both constraints
                    if (widget.mListAnchors[ConstraintWidget.ANCHOR_TOP].mTarget == null
                            || widget.mListAnchors[ConstraintWidget.ANCHOR_BOTTOM].mTarget
                            == null) {
                        measure(widget, WRAP_CONTENT, 0, vertical, 0);
                        widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                        widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                        widget.measured = true;
                        continue;
                    }
                }
            }
            if (horizontal == MATCH_CONSTRAINT && vertical == MATCH_CONSTRAINT) {
                if (widget.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_WRAP
                        || widget.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_WRAP) {
                    measure(widget, WRAP_CONTENT, 0, WRAP_CONTENT, 0);
                    widget.mHorizontalRun.mDimension.wrapValue = widget.getWidth();
                    widget.mVerticalRun.mDimension.wrapValue = widget.getHeight();
                } else if (widget.mMatchConstraintDefaultHeight
                        == ConstraintWidget.MATCH_CONSTRAINT_PERCENT
                        && widget.mMatchConstraintDefaultWidth
                        == ConstraintWidget.MATCH_CONSTRAINT_PERCENT
                        && constraintWidgetContainer.mListDimensionBehaviors[HORIZONTAL] == FIXED
                        && constraintWidgetContainer.mListDimensionBehaviors[VERTICAL] == FIXED) {
                    float horizPercent = widget.mMatchConstraintPercentWidth;
                    float vertPercent = widget.mMatchConstraintPercentHeight;
                    int width = (int) (0.5f + horizPercent * constraintWidgetContainer.getWidth());
                    int height = (int) (0.5f + vertPercent * constraintWidgetContainer.getHeight());
                    measure(widget, FIXED, width, FIXED, height);
                    widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                    widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                    widget.measured = true;
                }
            }
        }
        return false;
    }

    // @TODO: add description
    public void measureWidgets() {
        for (ConstraintWidget widget : mWidgetcontainer.mChildren) {
            if (widget.measured) {
                continue;
            }
            ConstraintWidget.DimensionBehaviour horiz = widget.mListDimensionBehaviors[HORIZONTAL];
            ConstraintWidget.DimensionBehaviour vert = widget.mListDimensionBehaviors[VERTICAL];
            int horizMatchConstraintsType = widget.mMatchConstraintDefaultWidth;
            int vertMatchConstraintsType = widget.mMatchConstraintDefaultHeight;

            boolean horizWrap = horiz == WRAP_CONTENT
                    || (horiz == MATCH_CONSTRAINT
                    && horizMatchConstraintsType == MATCH_CONSTRAINT_WRAP);

            boolean vertWrap = vert == WRAP_CONTENT
                    || (vert == MATCH_CONSTRAINT
                    && vertMatchConstraintsType == MATCH_CONSTRAINT_WRAP);

            boolean horizResolved = widget.mHorizontalRun.mDimension.resolved;
            boolean vertResolved = widget.mVerticalRun.mDimension.resolved;

            if (horizResolved && vertResolved) {
                measure(widget, FIXED, widget.mHorizontalRun.mDimension.value,
                        FIXED, widget.mVerticalRun.mDimension.value);
                widget.measured = true;
            } else if (horizResolved && vertWrap) {
                measure(widget, FIXED, widget.mHorizontalRun.mDimension.value,
                        WRAP_CONTENT, widget.mVerticalRun.mDimension.value);
                if (vert == MATCH_CONSTRAINT) {
                    widget.mVerticalRun.mDimension.wrapValue = widget.getHeight();
                } else {
                    widget.mVerticalRun.mDimension.resolve(widget.getHeight());
                    widget.measured = true;
                }
            } else if (vertResolved && horizWrap) {
                measure(widget, WRAP_CONTENT, widget.mHorizontalRun.mDimension.value,
                        FIXED, widget.mVerticalRun.mDimension.value);
                if (horiz == MATCH_CONSTRAINT) {
                    widget.mHorizontalRun.mDimension.wrapValue = widget.getWidth();
                } else {
                    widget.mHorizontalRun.mDimension.resolve(widget.getWidth());
                    widget.measured = true;
                }
            }
            if (widget.measured && widget.mVerticalRun.mBaselineDimension != null) {
                widget.mVerticalRun.mBaselineDimension.resolve(widget.getBaselineDistance());
            }
        }
    }

    /**
     * Invalidate the graph of constraints
     */
    public void invalidateGraph() {
        mNeedBuildGraph = true;
    }

    /**
     * Mark the widgets as needing to be remeasured
     */
    public void invalidateMeasures() {
        mNeedRedoMeasures = true;
    }

    ArrayList<RunGroup> mGroups = new ArrayList<>();

    // @TODO: add description
    public void buildGraph() {
        // First, let's identify the overall dependency graph
        buildGraph(mRuns);

        if (USE_GROUPS) {
            mGroups.clear();
            // Then get the horizontal and vertical groups
            RunGroup.index = 0;
            findGroup(mWidgetcontainer.mHorizontalRun, HORIZONTAL, mGroups);
            findGroup(mWidgetcontainer.mVerticalRun, VERTICAL, mGroups);
        }
        mNeedBuildGraph = false;
    }

    // @TODO: add description
    public void buildGraph(ArrayList<WidgetRun> runs) {
        runs.clear();
        mContainer.mHorizontalRun.clear();
        mContainer.mVerticalRun.clear();
        runs.add(mContainer.mHorizontalRun);
        runs.add(mContainer.mVerticalRun);
        HashSet<ChainRun> chainRuns = null;
        for (ConstraintWidget widget : mContainer.mChildren) {
            if (widget instanceof Guideline) {
                runs.add(new GuidelineReference(widget));
                continue;
            }
            if (widget.isInHorizontalChain()) {
                if (widget.horizontalChainRun == null) {
                    // build the horizontal chain
                    widget.horizontalChainRun = new ChainRun(widget, HORIZONTAL);
                }
                if (chainRuns == null) {
                    chainRuns = new HashSet<>();
                }
                chainRuns.add(widget.horizontalChainRun);
            } else {
                runs.add(widget.mHorizontalRun);
            }
            if (widget.isInVerticalChain()) {
                if (widget.verticalChainRun == null) {
                    // build the vertical chain
                    widget.verticalChainRun = new ChainRun(widget, VERTICAL);
                }
                if (chainRuns == null) {
                    chainRuns = new HashSet<>();
                }
                chainRuns.add(widget.verticalChainRun);
            } else {
                runs.add(widget.mVerticalRun);
            }
            if (widget instanceof HelperWidget) {
                runs.add(new HelperReferences(widget));
            }
        }
        if (chainRuns != null) {
            runs.addAll(chainRuns);
        }
        for (WidgetRun run : runs) {
            run.clear();
        }
        for (WidgetRun run : runs) {
            if (run.mWidget == mContainer) {
                continue;
            }
            run.apply();
        }
        if (DEBUG) {
            displayGraph();
        }
    }


    private void displayGraph() {
        String content = "digraph {\n";
        for (WidgetRun run : mRuns) {
            content = generateDisplayGraph(run, content);
        }
        content += "\n}\n";
        System.out.println("content:<<\n" + content + "\n>>");
    }

    private void applyGroup(DependencyNode node,
            int orientation,
            int direction,
            DependencyNode end,
            ArrayList<RunGroup> groups,
            RunGroup group) {
        WidgetRun run = node.mRun;
        if (run.mRunGroup != null
                || run == mWidgetcontainer.mHorizontalRun || run == mWidgetcontainer.mVerticalRun) {
            return;
        }

        if (group == null) {
            group = new RunGroup(run, direction);
            groups.add(group);
        }

        run.mRunGroup = group;
        group.add(run);
        for (Dependency dependent : run.start.mDependencies) {
            if (dependent instanceof DependencyNode) {
                applyGroup((DependencyNode) dependent,
                        orientation, RunGroup.START, end, groups, group);
            }
        }
        for (Dependency dependent : run.end.mDependencies) {
            if (dependent instanceof DependencyNode) {
                applyGroup((DependencyNode) dependent,
                        orientation, RunGroup.END, end, groups, group);
            }
        }
        if (orientation == VERTICAL && run instanceof VerticalWidgetRun) {
            for (Dependency dependent : ((VerticalWidgetRun) run).baseline.mDependencies) {
                if (dependent instanceof DependencyNode) {
                    applyGroup((DependencyNode) dependent,
                            orientation, RunGroup.BASELINE, end, groups, group);
                }
            }
        }
        for (DependencyNode target : run.start.mTargets) {
            if (target == end) {
                group.dual = true;
            }
            applyGroup(target, orientation, RunGroup.START, end, groups, group);
        }
        for (DependencyNode target : run.end.mTargets) {
            if (target == end) {
                group.dual = true;
            }
            applyGroup(target, orientation, RunGroup.END, end, groups, group);
        }
        if (orientation == VERTICAL && run instanceof VerticalWidgetRun) {
            for (DependencyNode target : ((VerticalWidgetRun) run).baseline.mTargets) {
                applyGroup(target, orientation, RunGroup.BASELINE, end, groups, group);
            }
        }
    }

    private void findGroup(WidgetRun run, int orientation, ArrayList<RunGroup> groups) {
        for (Dependency dependent : run.start.mDependencies) {
            if (dependent instanceof DependencyNode) {
                DependencyNode node = (DependencyNode) dependent;
                applyGroup(node, orientation, RunGroup.START, run.end, groups, null);
            } else if (dependent instanceof WidgetRun) {
                WidgetRun dependentRun = (WidgetRun) dependent;
                applyGroup(dependentRun.start, orientation, RunGroup.START, run.end, groups, null);
            }
        }
        for (Dependency dependent : run.end.mDependencies) {
            if (dependent instanceof DependencyNode) {
                DependencyNode node = (DependencyNode) dependent;
                applyGroup(node, orientation, RunGroup.END, run.start, groups, null);
            } else if (dependent instanceof WidgetRun) {
                WidgetRun dependentRun = (WidgetRun) dependent;
                applyGroup(dependentRun.end, orientation, RunGroup.END, run.start, groups, null);
            }
        }
        if (orientation == VERTICAL) {
            for (Dependency dependent : ((VerticalWidgetRun) run).baseline.mDependencies) {
                if (dependent instanceof DependencyNode) {
                    DependencyNode node = (DependencyNode) dependent;
                    applyGroup(node, orientation, RunGroup.BASELINE, null, groups, null);
                }
            }
        }
    }


    private String generateDisplayNode(DependencyNode node,
            boolean centeredConnection,
            String content) {
        StringBuilder contentBuilder = new StringBuilder(content);
        for (DependencyNode target : node.mTargets) {
            String constraint = "\n" + node.name();
            constraint += " -> " + target.name();
            if (node.mMargin > 0 || centeredConnection || node.mRun instanceof HelperReferences) {
                constraint += "[";
                if (node.mMargin > 0) {
                    constraint += "label=\"" + node.mMargin + "\"";
                    if (centeredConnection) {
                        constraint += ",";
                    }
                }
                if (centeredConnection) {
                    constraint += " style=dashed ";
                }
                if (node.mRun instanceof HelperReferences) {
                    constraint += " style=bold,color=gray ";
                }
                constraint += "]";
            }
            constraint += "\n";
            contentBuilder.append(constraint);
        }
        content = contentBuilder.toString();
//        for (DependencyNode dependency : node.dependencies) {
//            content = generateDisplayNode(dependency, content);
//        }
        return content;
    }

    private String nodeDefinition(WidgetRun run) {
        int orientation = run instanceof VerticalWidgetRun ? VERTICAL : HORIZONTAL;
        String name = run.mWidget.getDebugName();
        StringBuilder definition = new StringBuilder(name);
        ConstraintWidget.DimensionBehaviour behaviour =
                orientation == HORIZONTAL ? run.mWidget.getHorizontalDimensionBehaviour()
                        : run.mWidget.getVerticalDimensionBehaviour();
        RunGroup runGroup = run.mRunGroup;

        if (orientation == HORIZONTAL) {
            definition.append("_HORIZONTAL");
        } else {
            definition.append("_VERTICAL");
        }
        definition.append(" [shape=none, label=<");
        definition.append("<TABLE BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"2\">");
        definition.append("  <TR>");
        if (orientation == HORIZONTAL) {
            definition.append("    <TD ");
            if (run.start.resolved) {
                definition.append(" BGCOLOR=\"green\"");
            }
            definition.append(" PORT=\"LEFT\" BORDER=\"1\">L</TD>");
        } else {
            definition.append("    <TD ");
            if (run.start.resolved) {
                definition.append(" BGCOLOR=\"green\"");
            }
            definition.append(" PORT=\"TOP\" BORDER=\"1\">T</TD>");
        }
        definition.append("    <TD BORDER=\"1\" ");
        if (run.mDimension.resolved && !run.mWidget.measured) {
            definition.append(" BGCOLOR=\"green\" ");
        } else if (run.mDimension.resolved) {
            definition.append(" BGCOLOR=\"lightgray\" ");
        } else if (run.mWidget.measured) {
            definition.append(" BGCOLOR=\"yellow\" ");
        }
        if (behaviour == MATCH_CONSTRAINT) {
            definition.append("style=\"dashed\"");
        }
        definition.append(">");
        definition.append(name);
        if (runGroup != null) {
            definition.append(" [");
            definition.append(runGroup.mGroupIndex + 1);
            definition.append("/");
            definition.append(RunGroup.index);
            definition.append("]");
        }
        definition.append(" </TD>");
        if (orientation == HORIZONTAL) {
            definition.append("    <TD ");
            if (run.end.resolved) {
                definition.append(" BGCOLOR=\"green\"");
            }
            definition.append(" PORT=\"RIGHT\" BORDER=\"1\">R</TD>");
        } else {
            definition.append("    <TD ");
            if (((VerticalWidgetRun) run).baseline.resolved) {
                definition.append(" BGCOLOR=\"green\"");
            }
            definition.append(" PORT=\"BASELINE\" BORDER=\"1\">b</TD>");
            definition.append("    <TD ");
            if (run.end.resolved) {
                definition.append(" BGCOLOR=\"green\"");
            }
            definition.append(" PORT=\"BOTTOM\" BORDER=\"1\">B</TD>");
        }
        definition.append("  </TR></TABLE>");
        definition.append(">];\n");
        return definition.toString();
    }

    private String generateChainDisplayGraph(ChainRun chain, String content) {
        int orientation = chain.orientation;
        StringBuilder subgroup = new StringBuilder("subgraph ");
        subgroup.append("cluster_");
        subgroup.append(chain.mWidget.getDebugName());
        if (orientation == HORIZONTAL) {
            subgroup.append("_h");
        } else {
            subgroup.append("_v");
        }
        subgroup.append(" {\n");
        String definitions = "";
        for (WidgetRun run : chain.mWidgets) {
            subgroup.append(run.mWidget.getDebugName());
            if (orientation == HORIZONTAL) {
                subgroup.append("_HORIZONTAL");
            } else {
                subgroup.append("_VERTICAL");
            }
            subgroup.append(";\n");
            definitions = generateDisplayGraph(run, definitions);
        }
        subgroup.append("}\n");
        return content + definitions + subgroup;
    }

    private boolean isCenteredConnection(DependencyNode start, DependencyNode end) {
        int startTargets = 0;
        int endTargets = 0;
        for (DependencyNode s : start.mTargets) {
            if (s != end) {
                startTargets++;
            }
        }
        for (DependencyNode e : end.mTargets) {
            if (e != start) {
                endTargets++;
            }
        }
        return startTargets > 0 && endTargets > 0;
    }

    private String generateDisplayGraph(WidgetRun root, String content) {
        DependencyNode start = root.start;
        DependencyNode end = root.end;
        StringBuilder sb = new StringBuilder(content);

        if (!(root instanceof HelperReferences) && start.mDependencies.isEmpty()
                && end.mDependencies.isEmpty() && start.mTargets.isEmpty()
                && end.mTargets.isEmpty()) {
            return content;
        }
        sb.append(nodeDefinition(root));

        boolean centeredConnection = isCenteredConnection(start, end);
        content = generateDisplayNode(start, centeredConnection, content);
        content = generateDisplayNode(end, centeredConnection, content);
        if (root instanceof VerticalWidgetRun) {
            DependencyNode baseline = ((VerticalWidgetRun) root).baseline;
            content = generateDisplayNode(baseline, centeredConnection, content);
        }

        if (root instanceof HorizontalWidgetRun
                || (root instanceof ChainRun && ((ChainRun) root).orientation == HORIZONTAL)) {
            ConstraintWidget.DimensionBehaviour behaviour =
                    root.mWidget.getHorizontalDimensionBehaviour();
            if (behaviour == ConstraintWidget.DimensionBehaviour.FIXED
                    || behaviour == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) {
                if (!start.mTargets.isEmpty() && end.mTargets.isEmpty()) {
                    sb.append("\n");
                    sb.append(end.name());
                    sb.append(" -> ");
                    sb.append(start.name());
                    sb.append("\n");
                } else if (start.mTargets.isEmpty() && !end.mTargets.isEmpty()) {
                    sb.append("\n");
                    sb.append(start.name());
                    sb.append(" -> ");
                    sb.append(end.name());
                    sb.append("\n");
                }
            } else {
                if (behaviour == MATCH_CONSTRAINT && root.mWidget.getDimensionRatio() > 0) {
                    sb.append("\n");
                    sb.append(root.mWidget.getDebugName());
                    sb.append("_HORIZONTAL -> ");
                    sb.append(root.mWidget.getDebugName());
                    sb.append("_VERTICAL;\n");
                }
            }
        } else if (root instanceof VerticalWidgetRun
                || (root instanceof ChainRun && ((ChainRun) root).orientation == VERTICAL)) {
            ConstraintWidget.DimensionBehaviour behaviour =
                    root.mWidget.getVerticalDimensionBehaviour();
            if (behaviour == ConstraintWidget.DimensionBehaviour.FIXED
                    || behaviour == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT) {
                if (!start.mTargets.isEmpty() && end.mTargets.isEmpty()) {
                    sb.append("\n");
                    sb.append(end.name());
                    sb.append(" -> ");
                    sb.append(start.name());
                    sb.append("\n");
                } else if (start.mTargets.isEmpty() && !end.mTargets.isEmpty()) {
                    sb.append("\n");
                    sb.append(start.name());
                    sb.append(" -> ");
                    sb.append(end.name());
                    sb.append("\n");
                }
            } else {
                if (behaviour == MATCH_CONSTRAINT && root.mWidget.getDimensionRatio() > 0) {
                    sb.append("\n");
                    sb.append(root.mWidget.getDebugName());
                    sb.append("_VERTICAL -> ");
                    sb.append(root.mWidget.getDebugName());
                    sb.append("_HORIZONTAL;\n");
                }
            }
        }
        if (root instanceof ChainRun) {
            return generateChainDisplayGraph((ChainRun) root, content);
        }
        return sb.toString();
    }

}