summaryrefslogtreecommitdiff
path: root/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java
diff options
context:
space:
mode:
Diffstat (limited to 'solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java')
-rw-r--r--solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java550
1 files changed, 550 insertions, 0 deletions
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java b/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java
new file mode 100644
index 0000000..1f48990
--- /dev/null
+++ b/solver/src/main/java/android/support/constraint/solver/widgets/Analyzer.java
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2018 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.widgets.ConstraintWidget.DimensionBehaviour;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class to do widget constraints analysis.
+ * <p>
+ * Identify groups of widgets independent from each other.
+ * TODO: Identify Chains here instead.
+ */
+public class Analyzer {
+
+ private Analyzer() {
+ }
+
+ /**
+ * Find groups of constrained widgets.
+ * <p>
+ * Used to simplify the resolution process to layout the widgets when using optimizations.
+ * Wrap_content layouts require measuring the final size, groups are identified when
+ * the layout can be measured.
+ *
+ * @param layoutWidget Layout to analyze.
+ */
+ public static void determineGroups(ConstraintWidgetContainer layoutWidget) {
+ if ((layoutWidget.getOptimizationLevel() & Optimizer.OPTIMIZATION_GROUPS) != Optimizer.OPTIMIZATION_GROUPS) {
+ singleGroup(layoutWidget);
+ return;
+ }
+ layoutWidget.mSkipSolver = true;
+ layoutWidget.mGroupsWrapOptimized = false;
+ layoutWidget.mHorizontalWrapOptimized = false;
+ layoutWidget.mVerticalWrapOptimized = false;
+ final List<ConstraintWidget> widgets = layoutWidget.mChildren;
+ final List<ConstraintWidgetGroup> widgetGroups = layoutWidget.mWidgetGroups;
+ boolean horizontalWrapContent = layoutWidget.getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ boolean verticalWrapContent = layoutWidget.getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ boolean hasWrapContent = horizontalWrapContent || verticalWrapContent;
+ widgetGroups.clear();
+
+ for (ConstraintWidget widget : widgets) {
+ widget.mBelongingGroup = null;
+ widget.mGroupsToSolver = false;
+ widget.resetResolutionNodes();
+ }
+ for (ConstraintWidget widget : widgets) {
+ if (widget.mBelongingGroup == null) {
+ if (!determineGroups(widget, widgetGroups, hasWrapContent)) {
+ singleGroup(layoutWidget);
+ layoutWidget.mSkipSolver = false;
+ return;
+ }
+ }
+ }
+ int measuredWidth = 0;
+ int measuredHeight = 0;
+ // Resolve solvable widgets.
+ for (ConstraintWidgetGroup group : widgetGroups) {
+ measuredWidth = Math.max(measuredWidth,
+ getMaxDimension(group, ConstraintWidget.HORIZONTAL));
+ measuredHeight = Math.max(measuredHeight,
+ getMaxDimension(group, ConstraintWidget.VERTICAL));
+ }
+ // Change container to fixed and set resolved dimensions.
+ if (horizontalWrapContent) {
+ layoutWidget.setHorizontalDimensionBehaviour(DimensionBehaviour.FIXED);
+ layoutWidget.setWidth(measuredWidth);
+ layoutWidget.mGroupsWrapOptimized = true;
+ layoutWidget.mHorizontalWrapOptimized = true;
+ layoutWidget.mWrapFixedWidth = measuredWidth;
+ }
+ if (verticalWrapContent) {
+ layoutWidget.setVerticalDimensionBehaviour(DimensionBehaviour.FIXED);
+ layoutWidget.setHeight(measuredHeight);
+ layoutWidget.mGroupsWrapOptimized = true;
+ layoutWidget.mVerticalWrapOptimized = true;
+ layoutWidget.mWrapFixedHeight = measuredHeight;
+ }
+ setPosition(widgetGroups, ConstraintWidget.HORIZONTAL, layoutWidget.getWidth());
+ setPosition(widgetGroups, ConstraintWidget.VERTICAL, layoutWidget.getHeight());
+ }
+
+ /**
+ * @param widget Widget being traversed.
+ * @param widgetGroups Starting list to contain the widgets in this group.
+ * @param hasWrapContent Indicating if any dimension of the parent is in wrap_content.
+ * @return False if the group can't be optimized in any way.
+ */
+ private static boolean determineGroups(ConstraintWidget widget,
+ List<ConstraintWidgetGroup> widgetGroups, boolean hasWrapContent) {
+ ConstraintWidgetGroup traverseList = new ConstraintWidgetGroup(new ArrayList<ConstraintWidget>(), true);
+ widgetGroups.add(traverseList);
+ return traverse(widget, traverseList, widgetGroups, hasWrapContent);
+ }
+
+ /**
+ * Recursive function to traverse constrained widgets.
+ * The objective is to maintain in a single list all the widgets that can be reached through
+ * their constraints except for their parent.
+ *
+ * @param widget Widget being traversed.
+ * @param upperGroup List being passed down, originally by {@link #determineGroups(ConstraintWidget, List, boolean)}.
+ * @param widgetGroups List of widget groups identified.
+ * @param hasWrapContent Indicates if the layout has any dimension as wrap_content.
+ * @return If the group analysis failed or can't be done.
+ */
+ private static boolean traverse(ConstraintWidget widget, ConstraintWidgetGroup upperGroup,
+ List<ConstraintWidgetGroup> widgetGroups, boolean hasWrapContent) {
+ if (widget == null) {
+ return true;
+ }
+ widget.mOptimizerMeasured = false;
+ ConstraintWidgetContainer layoutWidget = (ConstraintWidgetContainer) widget.getParent();
+ if (widget.mBelongingGroup == null) {
+ // If it hasn't been assigned to a group.
+ widget.mOptimizerMeasurable = true;
+ upperGroup.mConstrainedGroup.add(widget);
+ widget.mBelongingGroup = upperGroup;
+ // Determine if group is measurable.
+ if (widget.mLeft.mTarget == null
+ && widget.mRight.mTarget == null
+ && widget.mTop.mTarget == null
+ && widget.mBottom.mTarget == null
+ && widget.mBaseline.mTarget == null
+ && widget.mCenter.mTarget == null) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ }
+ // Check if it has vertical bias.
+ if (widget.mTop.mTarget != null && widget.mBottom.mTarget != null) {
+ // Allow if it has no wrap content in that dimension an constrained to the parent.
+ boolean wrap = layoutWidget.getVerticalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ if (hasWrapContent) {
+ invalidate(layoutWidget, widget, upperGroup);
+ return false;
+ } else if (!(widget.mTop.mTarget.mOwner == widget.getParent()
+ && widget.mBottom.mTarget.mOwner == widget.getParent())) {
+ invalidate(layoutWidget, widget, upperGroup);
+ }
+ }
+ // Check if it has horizontal bias.
+ if (widget.mLeft.mTarget != null && widget.mRight.mTarget != null) {
+ // Allow if it has no wrap content in that dimension an constrained to the parent.
+ boolean wrap = layoutWidget.getHorizontalDimensionBehaviour() == DimensionBehaviour.WRAP_CONTENT;
+ if (hasWrapContent) {
+ invalidate(layoutWidget, widget, upperGroup);
+ return false;
+ } else if (!(widget.mLeft.mTarget.mOwner == widget.getParent()
+ && widget.mRight.mTarget.mOwner == widget.getParent())) {
+ invalidate(layoutWidget, widget, upperGroup);
+ }
+ }
+ if ((widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT
+ ^ widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT)
+ && widget.mDimensionRatio != 0.0f) {
+ // Calculate dimension.
+ resolveDimensionRatio(widget);
+ } else if (!(widget.getHorizontalDimensionBehaviour() != DimensionBehaviour.MATCH_CONSTRAINT
+ && widget.getVerticalDimensionBehaviour() != DimensionBehaviour.MATCH_CONSTRAINT)) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ }
+ // Is Horizontal start
+ if (((widget.mLeft.mTarget == null && widget.mRight.mTarget == null)
+ || (widget.mLeft.mTarget != null && widget.mLeft.mTarget.mOwner == widget.mParent && widget.mRight.mTarget == null)
+ || (widget.mRight.mTarget != null && widget.mRight.mTarget.mOwner == widget.mParent && widget.mLeft.mTarget == null)
+ || (widget.mLeft.mTarget != null && widget.mLeft.mTarget.mOwner == widget.mParent
+ && widget.mRight.mTarget != null && widget.mRight.mTarget.mOwner == widget.mParent))
+ && (widget.mCenter.mTarget == null)) {
+ if (!(widget instanceof Guideline) && !(widget instanceof Helper)) {
+ upperGroup.mStartHorizontalWidgets.add(widget);
+ }
+
+ }
+ // Is Vertical start
+ if (((widget.mTop.mTarget == null && widget.mBottom.mTarget == null)
+ || (widget.mTop.mTarget != null && widget.mTop.mTarget.mOwner == widget.mParent && widget.mBottom.mTarget == null)
+ || (widget.mBottom.mTarget != null && widget.mBottom.mTarget.mOwner == widget.mParent && widget.mTop.mTarget == null)
+ || (widget.mTop.mTarget != null && widget.mTop.mTarget.mOwner == widget.mParent
+ && widget.mBottom.mTarget != null && widget.mBottom.mTarget.mOwner == widget.mParent))
+ && (widget.mCenter.mTarget == null && widget.mBaseline.mTarget == null)) {
+ if (!(widget instanceof Guideline) && !(widget instanceof Helper)) {
+ upperGroup.mStartVerticalWidgets.add(widget);
+ }
+ }
+ } else {
+ // If it has, join the list and re-assign. Remove joint list from mWidgetGroups (if its a different list)
+ if (widget.mBelongingGroup != upperGroup) {
+ upperGroup.mConstrainedGroup.addAll(widget.mBelongingGroup.mConstrainedGroup);
+ upperGroup.mStartHorizontalWidgets.addAll(widget.mBelongingGroup.mStartHorizontalWidgets);
+ upperGroup.mStartVerticalWidgets.addAll(widget.mBelongingGroup.mStartVerticalWidgets);
+ if (widget.mBelongingGroup.mSkipSolver == false) {
+ upperGroup.mSkipSolver = false;
+ }
+ widgetGroups.remove(widget.mBelongingGroup);
+ for (ConstraintWidget auxWidget : widget.mBelongingGroup.mConstrainedGroup) {
+ auxWidget.mBelongingGroup = upperGroup;
+ }
+ }
+ return true;
+ }
+ // Proceed to traverse widgets, start with HelperWidgets since they contain multiple widgets.
+ if (widget instanceof Helper) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ final Helper hWidget = (Helper) widget;
+ for (int widgetsCount = 0; widgetsCount < hWidget.mWidgetsCount; widgetsCount++) {
+ if (!traverse(hWidget.mWidgets[widgetsCount], upperGroup, widgetGroups, hasWrapContent)) {
+ return false;
+ }
+ }
+ }
+ // We traverse every anchor, for wrap_content we ignore center (circular constraints).
+ final int anchorsSize = widget.mListAnchors.length;
+ for (int i = 0; i < anchorsSize; i++) {
+ final ConstraintAnchor anchor = widget.mListAnchors[i];
+ if (anchor.mTarget != null && anchor.mTarget.mOwner != widget.getParent()) {
+ if (anchor.mType == ConstraintAnchor.Type.CENTER) {
+ invalidate(layoutWidget, widget, upperGroup);
+ if (hasWrapContent) {
+ return false;
+ }
+ } else {
+ setConnection(anchor);
+ }
+ if (!traverse(anchor.mTarget.mOwner, upperGroup, widgetGroups, hasWrapContent)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static void invalidate(ConstraintWidgetContainer layoutWidget, ConstraintWidget widget, ConstraintWidgetGroup group) {
+ group.mSkipSolver = false;
+ layoutWidget.mSkipSolver = false;
+ widget.mOptimizerMeasurable = false;
+ }
+
+ /**
+ * Obtain the max length of a {@link ConstraintWidgetGroup} on a specific orientation.
+ * Length is saved on the group for future use as well.
+ *
+ * @param group Group of widgets being measured.
+ * @param orientation Orientation being measured.
+ * @return Max dimension in the group.
+ */
+ private static int getMaxDimension(ConstraintWidgetGroup group, int orientation) {
+ int dimension = 0;
+ int offset = orientation * 2;
+ List<ConstraintWidget> startWidgets = group.getStartWidgets(orientation);
+ final int size = startWidgets.size();
+ for (int i = 0; i < size; i++) {
+ ConstraintWidget widget = startWidgets.get(i);
+ boolean topLeftFlow = widget.mListAnchors[offset + 1].mTarget == null
+ || (widget.mListAnchors[offset].mTarget != null
+ && widget.mListAnchors[offset + 1].mTarget != null);
+ dimension = Math.max(dimension, getMaxDimensionTraversal(widget, orientation, topLeftFlow, 0));
+ }
+
+ group.mGroupDimensions[orientation] = dimension;
+ return dimension;
+ }
+
+ /**
+ * Traverse from a widget at the start of a tree (a widget constrained to any side of their parent),
+ * find the maximum length of the tree.
+ * Avoids cases when a widget's dimension shouldn't be considered.
+ *
+ * @param widget Widget being traversed.
+ * @param orientation Dimension being measured (HORIZONTAL/VERTICAL).
+ * @param topLeftFlow Indicates if the tree starts at the top or left of the container.
+ * @param depth How far the widget is from the start of the tree.
+ * @return Max dimension from the widget being traversed.
+ */
+ private static int getMaxDimensionTraversal(ConstraintWidget widget, int orientation, boolean topLeftFlow, int depth) {
+ // Start and end offset used to point to the correct anchors according to the flow
+ // of the widget at the start of the tree.
+ if (!widget.mOptimizerMeasurable) {
+ return 0;
+ }
+ int startOffset;
+ int endOffset;
+ int dimension = 0;
+ int dimensionPre = 0;
+ int dimensionPost = 0;
+ final int flow;
+ final int baselinePreDistance;
+ final int baselinePostDistance;
+ // If it has baseline, the dimensions change, despite maintaining the flow.
+ final boolean hasBaseline = widget.mBaseline.mTarget != null && orientation == ConstraintWidget.VERTICAL;
+
+ if (topLeftFlow) {
+ baselinePreDistance = widget.getBaselineDistance();
+ baselinePostDistance = widget.getHeight() - widget.getBaselineDistance();
+ startOffset = orientation * 2;
+ endOffset = startOffset + 1;
+ } else {
+ baselinePreDistance = widget.getHeight() - widget.getBaselineDistance();
+ baselinePostDistance = widget.getBaselineDistance();
+ endOffset = orientation * 2;
+ startOffset = endOffset + 1;
+ }
+
+ // Define the correct flow of direction. left -> right or left <- right.
+ // If the flow is going opposite from the startWidget, lengths and margin subtract.
+ if (widget.mListAnchors[endOffset].mTarget != null && widget.mListAnchors[startOffset].mTarget == null) {
+ flow = -1;
+ int aux = startOffset;
+ startOffset = endOffset;
+ endOffset = aux;
+ } else {
+ flow = 1;
+ }
+
+ if (hasBaseline) {
+ depth -= baselinePreDistance;
+ }
+ // Get position from horizontal/vertical bias.
+ dimension = widget.mListAnchors[startOffset].getMargin() * flow + getParentBiasOffset(widget, orientation);
+ int downDepth = dimension + depth;
+ int postTemp = ((orientation == ConstraintWidget.HORIZONTAL) ? widget.getWidth() : widget.getHeight()) * flow;
+ for (ResolutionNode targetNode : widget.mListAnchors[startOffset].getResolutionNode().dependents) {
+ final ResolutionAnchor anchor = (ResolutionAnchor) targetNode;
+ dimensionPre = Math.max(dimensionPre, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, downDepth));
+ }
+ for (ResolutionNode targetNode : widget.mListAnchors[endOffset].getResolutionNode().dependents) {
+ final ResolutionAnchor anchor = (ResolutionAnchor) targetNode;
+ dimensionPost = Math.max(dimensionPost, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, postTemp + downDepth));
+ }
+ if (hasBaseline) {
+ dimensionPre -= baselinePreDistance;
+ dimensionPost += baselinePostDistance;
+ } else {
+ dimensionPost += ((orientation == ConstraintWidget.HORIZONTAL) ? widget.getWidth() : widget.getHeight()) * flow;
+ }
+
+ // Baseline, only add distance from baseline to bottom instead of entire height.
+ int dimensionBaseline = 0;
+ if (orientation == ConstraintWidget.VERTICAL) {
+ for (ResolutionNode targetNode : widget.mBaseline.getResolutionNode().dependents) {
+ final ResolutionAnchor anchor = (ResolutionAnchor) targetNode;
+ if (flow == 1) {
+ dimensionBaseline = Math.max(dimensionBaseline, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, baselinePreDistance + downDepth));
+ } else {
+ dimensionBaseline = Math.max(dimensionBaseline, getMaxDimensionTraversal(anchor.myAnchor.mOwner, orientation, topLeftFlow, (baselinePostDistance * flow) + downDepth));
+ }
+ }
+ if (widget.mBaseline.getResolutionNode().dependents.size() > 0 && !hasBaseline) {
+ if (flow == 1) {
+ dimensionBaseline += baselinePreDistance;
+ } else {
+ dimensionBaseline -= baselinePostDistance;
+ }
+ }
+ }
+
+ int distanceBeforeWidget = dimension;
+ dimension += Math.max(dimensionPre, Math.max(dimensionPost, dimensionBaseline));
+ int leftTop = depth + distanceBeforeWidget;
+ int end = leftTop + postTemp;
+ if (flow == -1) {
+ int aux = end;
+ end = leftTop;
+ leftTop = aux;
+ }
+ if (topLeftFlow) {
+ Optimizer.setOptimizedWidget(widget, orientation, leftTop);
+ widget.setFrame(leftTop, end, orientation);
+ } else {
+ widget.mBelongingGroup.addWidgetsToSet(widget, orientation);
+ widget.setRelativePositioning(leftTop, orientation);
+ }
+ // Assuming widgets with only one dimension on Match_constraint would be measurable.
+ if (widget.getDimensionBehaviour(orientation) == DimensionBehaviour.MATCH_CONSTRAINT
+ && widget.mDimensionRatio != 0.0f) {
+ widget.mBelongingGroup.addWidgetsToSet(widget, orientation);
+ }
+ // Assuming is not measurable when the parent is on wrap_content.
+ if (widget.mListAnchors[startOffset].mTarget != null
+ && widget.mListAnchors[endOffset].mTarget != null) {
+ final ConstraintWidget parent = widget.getParent();
+ if (widget.mListAnchors[startOffset].mTarget.mOwner == parent
+ && widget.mListAnchors[endOffset].mTarget.mOwner == parent) {
+ widget.mBelongingGroup.addWidgetsToSet(widget, orientation);
+ }
+ }
+ return dimension;
+ }
+
+ private static void setConnection(ConstraintAnchor originAnchor) {
+ ResolutionNode originNode = originAnchor.getResolutionNode();
+ if (originAnchor.mTarget != null && originAnchor.mTarget.mTarget != originAnchor) {
+ // Go to Owner and add the dependent.
+ originAnchor.mTarget.getResolutionNode().addDependent(originNode);
+ }
+ }
+
+ /**
+ * Used when the Analyzer cannot simplify in independent groups.
+ * This will make it so all widgets are included in the same group.
+ *
+ * @param layoutWidget ConstrainedWidgetContainer being analyzed.
+ */
+ private static void singleGroup(ConstraintWidgetContainer layoutWidget) {
+ layoutWidget.mWidgetGroups.clear();
+ layoutWidget.mWidgetGroups.add(0, new ConstraintWidgetGroup(layoutWidget.mChildren));
+ }
+
+ /**
+ * Update widgets positions.
+ * Necessary for widgets dependent on the right/bottom side of the Container.
+ *
+ * @param groups Groups of widgets being updated.
+ * @param orientation Dimension to update on the widgets.
+ * @param containerLength Length of the widget container.
+ */
+ public static void setPosition(List<ConstraintWidgetGroup> groups, int orientation, int containerLength) {
+ final int groupsSize = groups.size();
+ for (int i = 0; i < groupsSize; i++) {
+ ConstraintWidgetGroup group = groups.get(i);
+ for (ConstraintWidget widget : group.getWidgetsToSet(orientation)) {
+ // We can only update those that we can measure.
+ if (widget.mOptimizerMeasurable) {
+ updateSizeDependentWidgets(widget, orientation, containerLength);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update the final layout position of widgets that depend on the size of the container.
+ * Exception for dimension-ratio as a work-around.
+ *
+ * @param widget Widget being updated.
+ * @param orientation Orientation being updated.
+ * @param containerLength The final container dimension in the orientation.
+ */
+ private static void updateSizeDependentWidgets(ConstraintWidget widget, int orientation, int containerLength) {
+ final int end;
+ final int start;
+ final int offset = orientation * 2;
+ ConstraintAnchor startAnchor = widget.mListAnchors[offset];
+ ConstraintAnchor endAnchor = widget.mListAnchors[offset + 1];
+ boolean hasBias = startAnchor.mTarget != null && endAnchor.mTarget != null;
+ if (hasBias) {
+ start = getParentBiasOffset(widget, orientation) + startAnchor.getMargin();
+ Optimizer.setOptimizedWidget(widget, orientation, start);
+ return;
+ }
+ /*
+ * ConstraintLayout::internalMeasureChildren() workaround (it would reset the widget's
+ * dimension even if it was set beforehand).
+ * It is assumed that the left/top anchor has been resolved. Since only the dimension is being reset.
+ */
+ if (widget.mDimensionRatio != 0.0f && widget.getDimensionBehaviour(orientation) == DimensionBehaviour.MATCH_CONSTRAINT) {
+ int length = resolveDimensionRatio(widget);
+ start = (int) widget.mListAnchors[offset].getResolutionNode().resolvedOffset;
+ end = start + length;
+ endAnchor.getResolutionNode().resolvedTarget = startAnchor.getResolutionNode();
+ endAnchor.getResolutionNode().resolvedOffset = length;
+ endAnchor.getResolutionNode().state = ResolutionNode.RESOLVED;
+ widget.setFrame(start, end, orientation);
+ return;
+ }
+ end = containerLength - widget.getRelativePositioning(orientation);
+ start = end - widget.getLength(orientation);
+ widget.setFrame(start, end, orientation);
+ Optimizer.setOptimizedWidget(widget, orientation, start);
+ }
+
+ /**
+ * Get the offset of a widget with bias exclusively with the parent.
+ * Offset is the distance from the left/top side of the parent to the start of the widget.
+ *
+ * @param orientation Orientation for the offset.
+ * @return The distance from the root based on the bias (does not include margin distance). 0 if it can't be calculated.
+ */
+ private static int getParentBiasOffset(ConstraintWidget widget, int orientation) {
+ int offset = orientation * 2;
+ ConstraintAnchor startAnchor = widget.mListAnchors[offset];
+ ConstraintAnchor endAnchor = widget.mListAnchors[offset + 1];
+ if (startAnchor.mTarget != null && startAnchor.mTarget.mOwner == widget.mParent
+ && endAnchor.mTarget != null && endAnchor.mTarget.mOwner == widget.mParent) {
+ int length = 0;
+ int widgetDimension = 0;
+ float bias = 0.0f;
+ length = widget.mParent.getLength(orientation);
+ bias = (orientation == ConstraintWidget.HORIZONTAL) ? widget.mHorizontalBiasPercent :
+ widget.mVerticalBiasPercent;
+ widgetDimension = widget.getLength(orientation);
+ length = length - startAnchor.getMargin() - endAnchor.getMargin();
+ length = length - widgetDimension;
+ length = ((int) ((float) length * bias));
+ return length;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Calculate the widget's dimension based on dimension ratio.
+ *
+ * @return The dimension calculated.
+ */
+ private static int resolveDimensionRatio(ConstraintWidget widget) {
+ int length = ConstraintWidget.UNKNOWN;
+ if (widget.getHorizontalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
+ if (widget.mDimensionRatioSide == ConstraintWidget.HORIZONTAL) {
+ length = (int) ((float) widget.getHeight() * widget.mDimensionRatio);
+ } else {
+ length = (int) ((float) widget.getHeight() / widget.mDimensionRatio);
+ }
+ widget.setWidth(length);
+ } else if (widget.getVerticalDimensionBehaviour() == DimensionBehaviour.MATCH_CONSTRAINT) {
+ if (widget.mDimensionRatioSide == ConstraintWidget.VERTICAL) {
+ length = (int) ((float) widget.getWidth() * widget.mDimensionRatio);
+ } else {
+ length = (int) ((float) widget.getWidth() / widget.mDimensionRatio);
+ }
+ widget.setHeight(length);
+ }
+ return length;
+ }
+}