public class

BasicMeasure

extends java.lang.Object

 java.lang.Object

↳androidx.constraintlayout.core.widgets.analyzer.BasicMeasure

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/)

Overview

Implements basic measure for linear resolution

Summary

Fields
public static final intAT_MOST

public static final intEXACTLY

public static final intFIXED

public static final intMATCH_PARENT

public static final intUNSPECIFIED

public static final intWRAP_CONTENT

Constructors
publicBasicMeasure(ConstraintWidgetContainer constraintWidgetContainer)

Methods
public longsolverMeasure(ConstraintWidgetContainer layout, int optimizationLevel, int paddingX, int paddingY, int widthMode, int widthSize, int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight)

Called by ConstraintLayout onMeasure()

public voidupdateHierarchy(ConstraintWidgetContainer layout)

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

Fields

public static final int UNSPECIFIED

public static final int EXACTLY

public static final int AT_MOST

public static final int MATCH_PARENT

public static final int WRAP_CONTENT

public static final int FIXED

Constructors

public BasicMeasure(ConstraintWidgetContainer constraintWidgetContainer)

Methods

public void updateHierarchy(ConstraintWidgetContainer layout)

public long solverMeasure(ConstraintWidgetContainer layout, int optimizationLevel, int paddingX, int paddingY, int widthMode, int widthSize, int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight)

Called by ConstraintLayout onMeasure()

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.GONE;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
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.VERTICAL;

import androidx.constraintlayout.core.LinearSystem;
import androidx.constraintlayout.core.widgets.Barrier;
import androidx.constraintlayout.core.widgets.ConstraintAnchor;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import androidx.constraintlayout.core.widgets.Guideline;
import androidx.constraintlayout.core.widgets.Helper;
import androidx.constraintlayout.core.widgets.Optimizer;
import androidx.constraintlayout.core.widgets.VirtualLayout;

import java.util.ArrayList;

/**
 * Implements basic measure for linear resolution
 */
public class BasicMeasure {

    private static final boolean DEBUG = false;
    private static final boolean DO_NOT_USE = false;
    private static final int MODE_SHIFT = 30;
    public static final int UNSPECIFIED = 0;
    public static final int EXACTLY = 1 << MODE_SHIFT;
    public static final int AT_MOST = 2 << MODE_SHIFT;

    public static final int MATCH_PARENT = -1;
    public static final int WRAP_CONTENT = -2;
    public static final int FIXED = -3;

    private final ArrayList<ConstraintWidget> mVariableDimensionsWidgets = new ArrayList<>();
    private Measure mMeasure = new Measure();

    // @TODO: add description
    public void updateHierarchy(ConstraintWidgetContainer layout) {
        mVariableDimensionsWidgets.clear();
        final int childCount = layout.mChildren.size();
        for (int i = 0; i < childCount; i++) {
            ConstraintWidget widget = layout.mChildren.get(i);
            if (widget.getHorizontalDimensionBehaviour()
                    == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                    || widget.getVerticalDimensionBehaviour()
                    == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
                mVariableDimensionsWidgets.add(widget);
            }
        }
        layout.invalidateGraph();
    }

    private ConstraintWidgetContainer mConstraintWidgetContainer;

    public BasicMeasure(ConstraintWidgetContainer constraintWidgetContainer) {
        this.mConstraintWidgetContainer = constraintWidgetContainer;
    }

    private void measureChildren(ConstraintWidgetContainer layout) {
        final int childCount = layout.mChildren.size();
        boolean optimize = layout.optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
        Measurer measurer = layout.getMeasurer();
        for (int i = 0; i < childCount; i++) {
            ConstraintWidget child = layout.mChildren.get(i);
            if (child instanceof Guideline) {
                continue;
            }
            if (child instanceof Barrier) {
                continue;
            }
            if (child.isInVirtualLayout()) {
                continue;
            }

            if (optimize && child.mHorizontalRun != null && child.mVerticalRun != null
                    && child.mHorizontalRun.mDimension.resolved
                    && child.mVerticalRun.mDimension.resolved) {
                continue;
            }

            ConstraintWidget.DimensionBehaviour widthBehavior =
                    child.getDimensionBehaviour(HORIZONTAL);
            ConstraintWidget.DimensionBehaviour heightBehavior =
                    child.getDimensionBehaviour(VERTICAL);

            boolean skip = widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                    && child.mMatchConstraintDefaultWidth != MATCH_CONSTRAINT_WRAP
                    && heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                    && child.mMatchConstraintDefaultHeight != MATCH_CONSTRAINT_WRAP;

            if (!skip && layout.optimizeFor(Optimizer.OPTIMIZATION_DIRECT)
                    && !(child instanceof VirtualLayout)) {
                if (widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                        && child.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
                        && heightBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                        && !child.isInHorizontalChain()) {
                    skip = true;
                }

                if (heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                        && child.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
                        && widthBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                        && !child.isInHorizontalChain()) {
                    skip = true;
                }

                // Don't measure yet -- let the direct solver have a shot at it.
                if ((widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
                        || heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT)
                        && child.mDimensionRatio > 0) {
                    skip = true;
                }
            }

            if (skip) {
                // we don't need to measure here as the dimension of the widget
                // will be completely computed by the solver.
                continue;
            }

            measure(measurer, child, Measure.SELF_DIMENSIONS);
            if (layout.mMetrics != null) {
                layout.mMetrics.measuredWidgets++;
            }
        }
        measurer.didMeasures();
    }

    private void solveLinearSystem(ConstraintWidgetContainer layout,
            String reason,
            int pass,
            int w,
            int h) {
        long startLayout = 0;
        if (layout.mMetrics != null) {
            startLayout = System.nanoTime();
        }

        int minWidth = layout.getMinWidth();
        int minHeight = layout.getMinHeight();
        layout.setMinWidth(0);
        layout.setMinHeight(0);
        layout.setWidth(w);
        layout.setHeight(h);
        layout.setMinWidth(minWidth);
        layout.setMinHeight(minHeight);
        if (DEBUG) {
            System.out.println("### Solve <" + reason + "> ###");
        }
        mConstraintWidgetContainer.setPass(pass);
        mConstraintWidgetContainer.layout();
        if (layout.mMetrics != null) {
            long endLayout = System.nanoTime();
            layout.mMetrics.mSolverPasses++;
            layout.mMetrics.measuresLayoutDuration += (endLayout - startLayout);
        }
    }

    /**
     * Called by ConstraintLayout onMeasure()
     */
    public long solverMeasure(ConstraintWidgetContainer layout,
            int optimizationLevel,
            int paddingX, int paddingY,
            int widthMode, int widthSize,
            int heightMode, int heightSize,
            int lastMeasureWidth,
            int lastMeasureHeight) {
        Measurer measurer = layout.getMeasurer();
        long layoutTime = 0;

        final int childCount = layout.mChildren.size();
        int startingWidth = layout.getWidth();
        int startingHeight = layout.getHeight();

        boolean optimizeWrap =
                Optimizer.enabled(optimizationLevel, Optimizer.OPTIMIZATION_GRAPH_WRAP);
        boolean optimize = optimizeWrap
                || Optimizer.enabled(optimizationLevel, Optimizer.OPTIMIZATION_GRAPH);

        if (optimize) {
            for (int i = 0; i < childCount; i++) {
                ConstraintWidget child = layout.mChildren.get(i);
                boolean matchWidth = child.getHorizontalDimensionBehaviour()
                        == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
                boolean matchHeight = child.getVerticalDimensionBehaviour()
                        == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
                boolean ratio = matchWidth && matchHeight && child.getDimensionRatio() > 0;
                if (child.isInHorizontalChain() && ratio) {
                    optimize = false;
                    break;
                }
                if (child.isInVerticalChain() && ratio) {
                    optimize = false;
                    break;
                }
                if (child instanceof VirtualLayout) {
                    optimize = false;
                    break;
                }
                if (child.isInHorizontalChain()
                        || child.isInVerticalChain()) {
                    optimize = false;
                    break;
                }
            }
        }

        if (optimize && LinearSystem.sMetrics != null) {
            LinearSystem.sMetrics.measures++;
        }

        boolean allSolved = false;

        optimize &= (widthMode == EXACTLY && heightMode == EXACTLY) || optimizeWrap;

        int computations = 0;

        if (optimize) {
            // For non-optimizer this doesn't seem to be a problem.
            // For both cases, having the width address max size early seems to work
            //  (which makes sense).
            // Putting it specific to optimizer to reduce unnecessary risk.
            widthSize = Math.min(layout.getMaxWidth(), widthSize);
            heightSize = Math.min(layout.getMaxHeight(), heightSize);

            if (widthMode == EXACTLY && layout.getWidth() != widthSize) {
                layout.setWidth(widthSize);
                layout.invalidateGraph();
            }
            if (heightMode == EXACTLY && layout.getHeight() != heightSize) {
                layout.setHeight(heightSize);
                layout.invalidateGraph();
            }
            if (widthMode == EXACTLY && heightMode == EXACTLY) {
                allSolved = layout.directMeasure(optimizeWrap);
                computations = 2;
            } else {
                allSolved = layout.directMeasureSetup(optimizeWrap);
                if (widthMode == EXACTLY) {
                    allSolved &= layout.directMeasureWithOrientation(optimizeWrap, HORIZONTAL);
                    computations++;
                }
                if (heightMode == EXACTLY) {
                    allSolved &= layout.directMeasureWithOrientation(optimizeWrap, VERTICAL);
                    computations++;
                }
            }
            if (allSolved) {
                layout.updateFromRuns(widthMode == EXACTLY, heightMode == EXACTLY);
            }
        } else {
            if (false) {
                layout.mHorizontalRun.clear();
                layout.mVerticalRun.clear();
                for (ConstraintWidget child : layout.getChildren()) {
                    child.mHorizontalRun.clear();
                    child.mVerticalRun.clear();
                }
            }
        }

        if (!allSolved || computations != 2) {
            int optimizations = layout.getOptimizationLevel();
            if (childCount > 0) {
                measureChildren(layout);
            }
            if (layout.mMetrics != null) {
                layoutTime = System.nanoTime();
            }

            updateHierarchy(layout);

            // let's update the size dependent widgets if any...
            final int sizeDependentWidgetsCount = mVariableDimensionsWidgets.size();

            // let's solve the linear system.
            if (childCount > 0) {
                solveLinearSystem(layout, "First pass", 0, startingWidth, startingHeight);
            }

            if (DEBUG) {
                System.out.println("size dependent widgets: " + sizeDependentWidgetsCount);
            }

            if (sizeDependentWidgetsCount > 0) {
                boolean needSolverPass = false;
                boolean containerWrapWidth = layout.getHorizontalDimensionBehaviour()
                        == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
                boolean containerWrapHeight = layout.getVerticalDimensionBehaviour()
                        == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
                int minWidth = Math.max(layout.getWidth(),
                        mConstraintWidgetContainer.getMinWidth());
                int minHeight = Math.max(layout.getHeight(),
                        mConstraintWidgetContainer.getMinHeight());

                ////////////////////////////////////////////////////////////////////////////////////
                // Let's first apply sizes for VirtualLayouts if any
                ////////////////////////////////////////////////////////////////////////////////////
                for (int i = 0; i < sizeDependentWidgetsCount; i++) {
                    ConstraintWidget widget = mVariableDimensionsWidgets.get(i);
                    if (!(widget instanceof VirtualLayout)) {
                        continue;
                    }
                    int preWidth = widget.getWidth();
                    int preHeight = widget.getHeight();
                    needSolverPass |= measure(measurer, widget, Measure.TRY_GIVEN_DIMENSIONS);
                    if (layout.mMetrics != null) {
                        layout.mMetrics.measuredMatchWidgets++;
                    }
                    int measuredWidth = widget.getWidth();
                    int measuredHeight = widget.getHeight();
                    if (measuredWidth != preWidth) {
                        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 != preHeight) {
                        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;
                    }
                    VirtualLayout virtualLayout = (VirtualLayout) widget;
                    needSolverPass |= virtualLayout.needSolverPass();
                }
                ////////////////////////////////////////////////////////////////////////////////////

                int maxIterations = 2;
                for (int j = 0; j < maxIterations; j++) {
                    for (int i = 0; i < sizeDependentWidgetsCount; i++) {
                        ConstraintWidget widget = mVariableDimensionsWidgets.get(i);
                        if ((widget instanceof Helper && !(widget instanceof VirtualLayout))
                                || widget instanceof Guideline) {
                            continue;
                        }
                        if (widget.getVisibility() == GONE) {
                            continue;
                        }
                        if (optimize && widget.mHorizontalRun.mDimension.resolved
                                && widget.mVerticalRun.mDimension.resolved) {
                            continue;
                        }
                        if (widget instanceof VirtualLayout) {
                            continue;
                        }

                        int preWidth = widget.getWidth();
                        int preHeight = widget.getHeight();
                        int preBaselineDistance = widget.getBaselineDistance();

                        int measureStrategy = Measure.TRY_GIVEN_DIMENSIONS;
                        if (j == maxIterations - 1) {
                            measureStrategy = Measure.USE_GIVEN_DIMENSIONS;
                        }
                        boolean hasMeasure = measure(measurer, widget, measureStrategy);
                        if (DO_NOT_USE && !widget.hasDependencies()) {
                            hasMeasure = false;
                        }
                        needSolverPass |= hasMeasure;
                        if (DEBUG && hasMeasure) {
                            System.out.println("{#} Needs Solver pass as measure true for "
                                    + widget.getDebugName());
                        }
                        if (layout.mMetrics != null) {
                            layout.mMetrics.measuredMatchWidgets++;
                        }

                        int measuredWidth = widget.getWidth();
                        int measuredHeight = widget.getHeight();

                        if (measuredWidth != preWidth) {
                            widget.setWidth(measuredWidth);
                            if (containerWrapWidth && widget.getRight() > minWidth) {
                                int w = widget.getRight()
                                        + widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin();
                                minWidth = Math.max(minWidth, w);
                            }
                            if (DEBUG) {
                                System.out.println("{#} Needs Solver pass as Width for "
                                        + widget.getDebugName() + " changed: "
                                        + measuredWidth + " != " + preWidth);
                            }
                            needSolverPass = true;
                        }
                        if (measuredHeight != preHeight) {
                            widget.setHeight(measuredHeight);
                            if (containerWrapHeight && widget.getBottom() > minHeight) {
                                int h = widget.getBottom()
                                        + widget.getAnchor(ConstraintAnchor.Type.BOTTOM)
                                        .getMargin();
                                minHeight = Math.max(minHeight, h);
                            }
                            if (DEBUG) {
                                System.out.println("{#} Needs Solver pass as Height for "
                                        + widget.getDebugName() + " changed: "
                                        + measuredHeight + " != " + preHeight);
                            }
                            needSolverPass = true;
                        }
                        if (widget.hasBaseline()
                                && preBaselineDistance != widget.getBaselineDistance()) {
                            if (DEBUG) {
                                System.out.println("{#} Needs Solver pass as Baseline for "
                                        + widget.getDebugName() + " changed: "
                                        + widget.getBaselineDistance() + " != "
                                        + preBaselineDistance);
                            }
                            needSolverPass = true;
                        }
                    }
                    if (needSolverPass) {
                        solveLinearSystem(layout, "intermediate pass",
                                1 + j, startingWidth, startingHeight);
                        needSolverPass = false;
                    } else {
                        break;
                    }
                }
            }
            layout.setOptimizationLevel(optimizations);
        }
        if (layout.mMetrics != null) {
            layoutTime = (System.nanoTime() - layoutTime);
        }
        return layoutTime;
    }

    /**
     * Convenience function to fill in the measure spec
     *
     * @param measurer        the measurer callback
     * @param widget          the widget to measure
     * @param measureStrategy how to use the current ConstraintWidget dimensions during the measure
     * @return true if needs another solver pass
     */
    private boolean measure(Measurer measurer, ConstraintWidget widget, int measureStrategy) {
        mMeasure.horizontalBehavior = widget.getHorizontalDimensionBehaviour();
        mMeasure.verticalBehavior = widget.getVerticalDimensionBehaviour();
        mMeasure.horizontalDimension = widget.getWidth();
        mMeasure.verticalDimension = widget.getHeight();
        mMeasure.measuredNeedsSolverPass = false;
        mMeasure.measureStrategy = measureStrategy;

        boolean horizontalMatchConstraints = (mMeasure.horizontalBehavior
                == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
        boolean verticalMatchConstraints = (mMeasure.verticalBehavior
                == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
        boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0;
        boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0;

        if (horizontalUseRatio) {
            if (widget.mResolvedMatchConstraintDefault[HORIZONTAL]
                    == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
                mMeasure.horizontalBehavior = ConstraintWidget.DimensionBehaviour.FIXED;
            }
        }
        if (verticalUseRatio) {
            if (widget.mResolvedMatchConstraintDefault[VERTICAL]
                    == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
                mMeasure.verticalBehavior = ConstraintWidget.DimensionBehaviour.FIXED;
            }
        }

        measurer.measure(widget, mMeasure);
        widget.setWidth(mMeasure.measuredWidth);
        widget.setHeight(mMeasure.measuredHeight);
        widget.setHasBaseline(mMeasure.measuredHasBaseline);
        widget.setBaselineDistance(mMeasure.measuredBaseline);
        mMeasure.measureStrategy = Measure.SELF_DIMENSIONS;
        return mMeasure.measuredNeedsSolverPass;
    }

    public interface Measurer {
        // @TODO: add description
        void measure(ConstraintWidget widget, Measure measure);

        // @TODO: add description
        void didMeasures();
    }

    public static class Measure {
        public static int SELF_DIMENSIONS = 0;
        public static int TRY_GIVEN_DIMENSIONS = 1;
        public static int USE_GIVEN_DIMENSIONS = 2;
        public ConstraintWidget.DimensionBehaviour horizontalBehavior;
        public ConstraintWidget.DimensionBehaviour verticalBehavior;
        public int horizontalDimension;
        public int verticalDimension;
        public int measuredWidth;
        public int measuredHeight;
        public int measuredBaseline;
        public boolean measuredHasBaseline;
        public boolean measuredNeedsSolverPass;
        public int measureStrategy;
    }
}