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
The Grid Helper in the Core library that helps to enable Grid in Compose
Summary
Fields |
---|
public static final int | HORIZONTAL |
public static final int | SPANS_RESPECT_WIDGET_ORDER |
public static final int | SUB_GRID_BY_COL_ROW |
public static final int | VERTICAL |
from VirtualLayout | mMeasure |
from HelperWidget | mWidgets[], mWidgetsCount |
from ConstraintWidget | ANCHOR_BASELINE, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_RIGHT, ANCHOR_TOP, BOTH, CHAIN_PACKED, CHAIN_SPREAD, CHAIN_SPREAD_INSIDE, DEFAULT_BIAS, DIRECT, frame, GONE, horizontalChainRun, horizontalGroup, INVISIBLE, isTerminalWidget[], mAnchors, MATCH_CONSTRAINT_PERCENT, MATCH_CONSTRAINT_RATIO, MATCH_CONSTRAINT_RATIO_RESOLVED, MATCH_CONSTRAINT_SPREAD, MATCH_CONSTRAINT_WRAP, mBaseline, mBottom, mCenter, mCircleConstraintAngle, mDimensionRatio, mDimensionRatioSide, measured, mHorizontalResolution, mHorizontalRun, mIsHeightWrapContent, mIsWidthWrapContent, mLeft, mListAnchors[], mListDimensionBehaviors[], mListNextMatchConstraintsWidget[], mMatchConstraintDefaultHeight, mMatchConstraintDefaultWidth, mMatchConstraintMaxHeight, mMatchConstraintMaxWidth, mMatchConstraintMinHeight, mMatchConstraintMinWidth, mMatchConstraintPercentHeight, mMatchConstraintPercentWidth, mMinHeight, mMinWidth, mNextChainWidget[], mOffsetX, mOffsetY, mParent, mResolvedMatchConstraintDefault[], mRight, mTop, mVerticalResolution, mVerticalRun, mWeight[], mX, mY, run[], SOLVER, stringId, UNKNOWN, verticalChainRun, verticalGroup, VISIBLE, WRAP_BEHAVIOR_HORIZONTAL_ONLY, WRAP_BEHAVIOR_INCLUDED, WRAP_BEHAVIOR_SKIPPED, WRAP_BEHAVIOR_VERTICAL_ONLY |
Methods |
---|
public void | addToSolver(LinearSystem system, boolean optimize)
Add this widget to the solver |
public java.lang.String | getColumnWeights()
get the string value of columnWeights |
public ConstraintWidgetContainer | getContainer()
get the parent ConstraintWidgetContainer |
public int | getFlags()
Get all the flags of a Grid |
public float | getHorizontalGaps()
get the value of horizontalGaps |
public int | getOrientation()
get the value of orientation |
public java.lang.String | getRowWeights()
get the string value of rowWeights |
public float | getVerticalGaps()
get the value of verticalGaps |
public void | measure(int widthMode, int widthSize, int heightMode, int heightSize)
|
public void | setColumns(int columns)
set new columns value |
public void | setColumnWeights(java.lang.String columnWeights)
set new columnWeights value and also invoke invalidate |
public void | setContainer(ConstraintWidgetContainer container)
Set the parent ConstraintWidgetContainer |
public void | setFlags(int flags)
Set flags of a Grid |
public void | setHorizontalGaps(float horizontalGaps)
set new horizontalGaps value and also invoke invalidate |
public void | setOrientation(int orientation)
set new orientation value |
public void | setRows(int rows)
set new rows value |
public void | setRowWeights(java.lang.String rowWeights)
set new rowWeights value and also invoke invalidate |
public void | setSkips(java.lang.String skips)
set new skips value |
public void | setSpans(java.lang.CharSequence spans)
set new spans value |
public void | setVerticalGaps(float verticalGaps)
set new verticalGaps value and also invoke invalidate |
from VirtualLayout | applyRtl, captureWidgets, contains, getMeasuredHeight, getMeasuredWidth, getPaddingBottom, getPaddingLeft, getPaddingRight, getPaddingTop, measure, measureChildren, needsCallbackFromSolver, needSolverPass, setMeasure, setPadding, setPaddingBottom, setPaddingEnd, setPaddingLeft, setPaddingRight, setPaddingStart, setPaddingTop, updateConstraints |
from HelperWidget | add, addDependents, copy, findGroupInDependents, removeAllIds |
from ConstraintWidget | addChildrenToSolverByDependency, allowedInBarrier, connect, connect, connect, connectCircularConstraint, createObjectVariables, ensureMeasureRequested, ensureWidgetRuns, getAnchor, getAnchors, getBaselineDistance, getBiasPercent, getBottom, getCompanionWidget, getContainerItemSkip, getDebugName, getDimensionBehaviour, getDimensionRatio, getDimensionRatioSide, getHasBaseline, getHeight, getHorizontalBiasPercent, getHorizontalChainControlWidget, getHorizontalChainStyle, getHorizontalDimensionBehaviour, getHorizontalMargin, getLastHorizontalMeasureSpec, getLastVerticalMeasureSpec, getLeft, getLength, getMaxHeight, getMaxWidth, getMinHeight, getMinWidth, getNextChainMember, getOptimizerWrapHeight, getOptimizerWrapWidth, getParent, getPreviousChainMember, getRight, getRootX, getRootY, getRun, getSceneString, getTop, getType, getVerticalBiasPercent, getVerticalChainControlWidget, getVerticalChainStyle, getVerticalDimensionBehaviour, getVerticalMargin, getVisibility, getWidth, getWrapBehaviorInParent, getX, getY, hasBaseline, hasDanglingDimension, hasDependencies, hasDimensionOverride, hasResolvedTargets, immediateConnect, isAnimated, isHeightWrapContent, isHorizontalSolvingPassDone, isInBarrier, isInHorizontalChain, isInPlaceholder, isInVerticalChain, isInVirtualLayout, isMeasureRequested, isResolvedHorizontally, isResolvedVertically, isRoot, isSpreadHeight, isSpreadWidth, isVerticalSolvingPassDone, isWidthWrapContent, markHorizontalSolvingPassDone, markVerticalSolvingPassDone, oppositeDimensionDependsOn, oppositeDimensionsTied, reset, resetAllConstraints, resetAnchor, resetAnchors, resetFinalResolution, resetSolverVariables, resetSolvingPassFlag, serialize, setAnimated, setBaselineDistance, setCompanionWidget, setContainerItemSkip, setDebugName, setDebugSolverName, setDimension, setDimensionRatio, setDimensionRatio, setFinalBaseline, setFinalFrame, setFinalHorizontal, setFinalLeft, setFinalTop, setFinalVertical, setFrame, setFrame, setGoneMargin, setHasBaseline, setHeight, setHeightWrapContent, setHorizontalBiasPercent, setHorizontalChainStyle, setHorizontalDimension, setHorizontalDimensionBehaviour, setHorizontalMatchStyle, setHorizontalWeight, setInBarrier, setInPlaceholder, setInVirtualLayout, setLastMeasureSpec, setLength, setMaxHeight, setMaxWidth, setMeasureRequested, setMinHeight, setMinWidth, setOffset, setOrigin, setParent, setType, setupDimensionRatio, setVerticalBiasPercent, setVerticalChainStyle, setVerticalDimension, setVerticalDimensionBehaviour, setVerticalMatchStyle, setVerticalWeight, setVisibility, setWidth, setWidthWrapContent, setWrapBehaviorInParent, setX, setY, toString, updateFromRuns, updateFromSolver |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait |
Fields
public static final int
HORIZONTALpublic static final int
VERTICALpublic static final int
SUB_GRID_BY_COL_ROWpublic static final int
SPANS_RESPECT_WIDGET_ORDERConstructors
public
GridCore(int rows, int columns)
Methods
get the parent ConstraintWidgetContainer
Returns:
the parent ConstraintWidgetContainer
Set the parent ConstraintWidgetContainer
Parameters:
container: the parent ConstraintWidgetContainer
public void
setSpans(java.lang.CharSequence spans)
set new spans value
Parameters:
spans: new spans value
public void
setSkips(java.lang.String skips)
set new skips value
Parameters:
skips: new spans value
public float
getHorizontalGaps()
get the value of horizontalGaps
Returns:
the value of horizontalGaps
public void
setHorizontalGaps(float horizontalGaps)
set new horizontalGaps value and also invoke invalidate
Parameters:
horizontalGaps: new horizontalGaps value
public float
getVerticalGaps()
get the value of verticalGaps
Returns:
the value of verticalGaps
public void
setVerticalGaps(float verticalGaps)
set new verticalGaps value and also invoke invalidate
Parameters:
verticalGaps: new verticalGaps value
public java.lang.String
getRowWeights()
get the string value of rowWeights
Returns:
the string value of rowWeights
public void
setRowWeights(java.lang.String rowWeights)
set new rowWeights value and also invoke invalidate
Parameters:
rowWeights: new rowWeights value
public java.lang.String
getColumnWeights()
get the string value of columnWeights
Returns:
the string value of columnWeights
public void
setColumnWeights(java.lang.String columnWeights)
set new columnWeights value and also invoke invalidate
Parameters:
columnWeights: new columnWeights value
public int
getOrientation()
get the value of orientation
Returns:
the value of orientation
public void
setOrientation(int orientation)
set new orientation value
Parameters:
orientation: new orientation value
public void
setRows(int rows)
set new rows value
Parameters:
rows: new rows value
public void
setColumns(int columns)
set new columns value
Parameters:
columns: new rows value
Get all the flags of a Grid
Returns:
an int value containing flag information
public void
setFlags(int flags)
Set flags of a Grid
Parameters:
flags: an int value containing flag information
public void
measure(int widthMode, int widthSize, int heightMode, int heightSize)
public void
addToSolver(
LinearSystem system, boolean optimize)
Add this widget to the solver
Parameters:
system: the solver we want to add the widget to
optimize: true if Optimizer.OPTIMIZATION_GRAPH is on
Source
/*
* Copyright (C) 2022 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.utils;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.core.LinearSystem;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import androidx.constraintlayout.core.widgets.VirtualLayout;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* The Grid Helper in the Core library that helps to enable Grid in Compose
*/
public class GridCore extends VirtualLayout {
// TODO: Handle padding from VirtualLayout. It should represent the padding applied around the
// Grid itself. Usually decreasing its size.
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
// Flags using incremental bit positions
public static final int SUB_GRID_BY_COL_ROW = 1;
public static final int SPANS_RESPECT_WIDGET_ORDER = 2;
private static final int DEFAULT_SIZE = 3; // default rows and columns.
private static final int MAX_ROWS = 50; // maximum number of rows can be specified.
private static final int MAX_COLUMNS = 50; // maximum number of columns can be specified.
/**
* Container for all the ConstraintWidgets
*/
ConstraintWidgetContainer mContainer;
/**
* boxWidgets were created as anchor points for arranging the associated widgets
*/
private ConstraintWidget[] mBoxWidgets;
/**
* Check if skips/spans of a Row or a Columns is handled
*/
private boolean mExtraSpaceHandled = false;
/**
* number of rows of the grid
*/
private int mRows;
/**
* number of rows set by the JSON or API
*/
private int mRowsSet;
/**
* number of columns of the grid
*/
private int mColumns;
/**
* number of columns set by the XML or API
*/
private int mColumnsSet;
/**
* Horizontal gaps in Dp
*/
private float mHorizontalGaps;
/**
* Vertical gaps in Dp
*/
private float mVerticalGaps;
/**
* string format of the row weight
*/
private String mRowWeights;
/**
* string format of the column weight
*/
private String mColumnWeights;
/**
* string format of the input Spans
*/
private String mSpans;
/**
* string format of the input Skips
*/
private String mSkips;
/**
* orientation of the widget arrangement - vertical or horizontal
*/
private int mOrientation;
/**
* Indicates what is the next available position to place a widget
*/
private int mNextAvailableIndex = 0;
/**
* A boolean matrix that tracks the positions that are occupied by skips and spans
* true: available position
* false: non-available position
*/
private boolean[][] mPositionMatrix;
/**
* Store the widget ids of handled spans
*/
Set<String> mSpanIds = new HashSet<>();
/**
* A int matrix that contains the positions where a widget would constraint to at each direction
* Each row contains 4 values that indicate the position to constraint of a widget.
* Example row: [left, top, right, bottom]
*/
private int[][] mConstraintMatrix;
/**
* An int value containing flag information.
*/
private int mFlags;
/**
* A int matrix to store the span related information
*/
private int[][] mSpanMatrix;
/**
* Index specify the next span to be handled.
*/
private int mSpanIndex = 0;
public GridCore() {
updateActualRowsAndColumns();
initMatrices();
}
public GridCore(int rows, int columns) {
mRowsSet = rows;
mColumnsSet = columns;
if (rows > MAX_ROWS) {
mRowsSet = DEFAULT_SIZE;
}
if (columns > MAX_COLUMNS) {
mColumnsSet = DEFAULT_SIZE;
}
updateActualRowsAndColumns();
initMatrices();
}
/**
* get the parent ConstraintWidgetContainer
*
* @return the parent ConstraintWidgetContainer
*/
@Nullable
public ConstraintWidgetContainer getContainer() {
return mContainer;
}
/**
* Set the parent ConstraintWidgetContainer
* @param container the parent ConstraintWidgetContainer
*/
public void setContainer(@NonNull ConstraintWidgetContainer container) {
mContainer = container;
}
/**
* set new spans value
*
* @param spans new spans value
*/
public void setSpans(@NonNull CharSequence spans) {
if (mSpans != null && mSpans.equals(spans.toString())) {
return;
}
mExtraSpaceHandled = false;
mSpans = spans.toString();
}
/**
* set new skips value
*
* @param skips new spans value
*/
public void setSkips(@NonNull String skips) {
if (mSkips != null && mSkips.equals(skips)) {
return;
}
mExtraSpaceHandled = false;
mSkips = skips;
}
/**
* get the value of horizontalGaps
*
* @return the value of horizontalGaps
*/
public float getHorizontalGaps() {
return mHorizontalGaps;
}
/**
* set new horizontalGaps value and also invoke invalidate
*
* @param horizontalGaps new horizontalGaps value
*/
public void setHorizontalGaps(float horizontalGaps) {
if (horizontalGaps < 0) {
return;
}
if (mHorizontalGaps == horizontalGaps) {
return;
}
mHorizontalGaps = horizontalGaps;
}
/**
* get the value of verticalGaps
*
* @return the value of verticalGaps
*/
public float getVerticalGaps() {
return mVerticalGaps;
}
/**
* set new verticalGaps value and also invoke invalidate
*
* @param verticalGaps new verticalGaps value
*/
public void setVerticalGaps(float verticalGaps) {
if (verticalGaps < 0) {
return;
}
if (mVerticalGaps == verticalGaps) {
return;
}
mVerticalGaps = verticalGaps;
}
/**
* get the string value of rowWeights
*
* @return the string value of rowWeights
*/
@Nullable
public String getRowWeights() {
return mRowWeights;
}
/**
* set new rowWeights value and also invoke invalidate
*
* @param rowWeights new rowWeights value
*/
public void setRowWeights(@NonNull String rowWeights) {
if (mRowWeights != null && mRowWeights.equals(rowWeights)) {
return;
}
mRowWeights = rowWeights;
}
/**
* get the string value of columnWeights
*
* @return the string value of columnWeights
*/
@Nullable
public String getColumnWeights() {
return mColumnWeights;
}
/**
* set new columnWeights value and also invoke invalidate
*
* @param columnWeights new columnWeights value
*/
public void setColumnWeights(@NonNull String columnWeights) {
if (mColumnWeights != null && mColumnWeights.equals(columnWeights)) {
return;
}
mColumnWeights = columnWeights;
}
/**
* get the value of orientation
*
* @return the value of orientation
*/
public int getOrientation() {
return mOrientation;
}
/**
* set new orientation value
*
* @param orientation new orientation value
*/
public void setOrientation(int orientation) {
if (!(orientation == HORIZONTAL || orientation == VERTICAL)) {
return;
}
if (mOrientation == orientation) {
return;
}
mOrientation = orientation;
}
/**
* set new rows value
*
* @param rows new rows value
*/
public void setRows(int rows) {
if (rows > MAX_ROWS) {
return;
}
if (mRowsSet == rows) {
return;
}
mRowsSet = rows;
updateActualRowsAndColumns();
initVariables();
}
/**
* set new columns value
*
* @param columns new rows value
*/
public void setColumns(int columns) {
if (columns > MAX_COLUMNS) {
return;
}
if (mColumnsSet == columns) {
return;
}
mColumnsSet = columns;
updateActualRowsAndColumns();
initVariables();
}
/**
* Get all the flags of a Grid
* @return an int value containing flag information
*/
public int getFlags() {
return mFlags;
}
/**
* Set flags of a Grid
* @param flags an int value containing flag information
*/
public void setFlags(int flags) {
mFlags = flags;
}
/**
* Handle the span use cases
*
* @param spansMatrix a int matrix that contains span information
*/
private void handleSpans(int[][] spansMatrix) {
if (isSpansRespectWidgetOrder()) {
return;
}
for (int i = 0; i < spansMatrix.length; i++) {
int row = getRowByIndex(spansMatrix[i][0]);
int col = getColByIndex(spansMatrix[i][0]);
if (!invalidatePositions(row, col,
spansMatrix[i][1], spansMatrix[i][2])) {
return;
}
connectWidget(mWidgets[i], row, col,
spansMatrix[i][1], spansMatrix[i][2]);
mSpanIds.add(mWidgets[i].stringId);
}
}
/**
* Arrange the widgets in the constraint_referenced_ids
*/
private void arrangeWidgets() {
int position;
// @TODO handle RTL
for (int i = 0; i < mWidgetsCount; i++) {
if (mSpanIds.contains(mWidgets[i].stringId)) {
// skip the widget Id that's already handled by handleSpans
continue;
}
position = getNextPosition();
int row = getRowByIndex(position);
int col = getColByIndex(position);
if (position == -1) {
// no more available position.
return;
}
if (isSpansRespectWidgetOrder() && mSpanMatrix != null) {
if (mSpanIndex < mSpanMatrix.length && mSpanMatrix[mSpanIndex][0] == position) {
// when invoke getNextPosition this position would be set to false
mPositionMatrix[row][col] = true;
// if there is not enough space to constrain the span, don't do it.
if (!invalidatePositions(row, col,
mSpanMatrix[mSpanIndex][1], mSpanMatrix[mSpanIndex][2])) {
continue;
}
connectWidget(mWidgets[i], row, col,
mSpanMatrix[mSpanIndex][1], mSpanMatrix[mSpanIndex][2]);
mSpanIndex++;
continue;
}
}
connectWidget(mWidgets[i], row, col, 1, 1);
}
}
/**
* generate the Grid form based on the input attributes
*
* @param isUpdate whether to update the existing grid (true) or create a new one (false)
*/
private void setupGrid(boolean isUpdate) {
if (mRows < 1 || mColumns < 1) {
return;
}
if (isUpdate) {
for (int i = 0; i < mPositionMatrix.length; i++) {
for (int j = 0; j < mPositionMatrix[0].length; j++) {
mPositionMatrix[i][j] = true;
}
}
mSpanIds.clear();
}
mNextAvailableIndex = 0;
if (mSkips != null && !mSkips.trim().isEmpty()) {
int[][] mSkips = parseSpans(this.mSkips, false);
if (mSkips != null) {
handleSkips(mSkips);
}
}
if (mSpans != null && !mSpans.trim().isEmpty()) {
mSpanMatrix = parseSpans(this.mSpans, true);
}
// Need to create boxes before handleSpans since the spanned widgets would be
// constrained in this step.
createBoxes();
if (mSpanMatrix != null) {
handleSpans(mSpanMatrix);
}
}
/**
* Convert a 1D index to a 2D index that has index for row
*
* @param index index in 1D
* @return row as its values.
*/
private int getRowByIndex(int index) {
if (mOrientation == 1) {
return index % mRows;
} else {
return index / mColumns;
}
}
/**
* Convert a 1D index to a 2D index that has index for column
*
* @param index index in 1D
* @return column as its values.
*/
private int getColByIndex(int index) {
if (mOrientation == 1) {
return index / mRows;
} else {
return index % mColumns;
}
}
/**
* Make positions in the grid unavailable based on the skips attr
*
* @param skipsMatrix a int matrix that contains skip information
*/
private void handleSkips(int[][] skipsMatrix) {
for (int[] matrix : skipsMatrix) {
int row = getRowByIndex(matrix[0]);
int col = getColByIndex(matrix[0]);
if (!invalidatePositions(row, col,
matrix[1], matrix[2])) {
return;
}
}
}
/**
* Make the specified positions in the grid unavailable.
*
* @param startRow the row of the staring position
* @param startColumn the column of the staring position
* @param rowSpan how many rows to span
* @param columnSpan how many columns to span
* @return true if we could properly invalidate the positions else false
*/
private boolean invalidatePositions(int startRow, int startColumn,
int rowSpan, int columnSpan) {
for (int i = startRow; i < startRow + rowSpan; i++) {
for (int j = startColumn; j < startColumn + columnSpan; j++) {
if (i >= mPositionMatrix.length || j >= mPositionMatrix[0].length
|| !mPositionMatrix[i][j]) {
// the position is already occupied.
return false;
}
mPositionMatrix[i][j] = false;
}
}
return true;
}
/**
* Parse the weights/pads in the string format into a float array. Note that weight are
* normally expected to match the size. But in case they don't, we trim or pad the weight with
* trailing 1 to match the expected size.
*
* @param size size of the return array
* @param str weights/pads in a string format
* @return a float array with weights/pads values
*/
private float[] parseWeights(int size, String str) {
if (str == null || str.trim().isEmpty()) {
return null;
}
String[] values = str.split(",");
// Return array must be of the expected size, effectively trimming excess weights
float[] arr = new float[size];
for (int i = 0; i < arr.length; i++) {
if (i < values.length) {
try {
arr[i] = Float.parseFloat(values[i]);
} catch (Exception e) {
System.err.println("Error parsing `" + values[i] + "`: " + e.getMessage());
// Fallback to 1f.
arr[i] = 1f;
}
} else {
// Fill in missing weights with 1f
arr[i] = 1f;
}
}
return arr;
}
/**
* Get the next available position for widget arrangement.
* @return int[] -> [row, column]
*/
private int getNextPosition() {
int position = 0;
boolean positionFound = false;
while (!positionFound) {
if (mNextAvailableIndex >= mRows * mColumns) {
return -1;
}
position = mNextAvailableIndex;
int row = getRowByIndex(mNextAvailableIndex);
int col = getColByIndex(mNextAvailableIndex);
if (mPositionMatrix[row][col]) {
mPositionMatrix[row][col] = false;
positionFound = true;
}
mNextAvailableIndex++;
}
return position;
}
/**
* Compute the actual rows and columns given what was set
* if 0,0 find the most square rows and columns that fits
* if 0,n or n,0 scale to fit
*/
private void updateActualRowsAndColumns() {
if (mRowsSet == 0 || mColumnsSet == 0) {
if (mColumnsSet > 0) {
mColumns = mColumnsSet;
mRows = (mWidgetsCount + mColumns - 1) / mColumnsSet; // round up
} else if (mRowsSet > 0) {
mRows = mRowsSet;
mColumns = (mWidgetsCount + mRowsSet - 1) / mRowsSet; // round up
} else { // as close to square as possible favoring more rows
mRows = (int) (1.5 + Math.sqrt(mWidgetsCount));
mColumns = (mWidgetsCount + mRows - 1) / mRows;
}
} else {
mRows = mRowsSet;
mColumns = mColumnsSet;
}
}
/**
* Create a new boxWidget for constraining widgets
* @return the created boxWidget
*/
private ConstraintWidget makeNewWidget() {
ConstraintWidget widget = new ConstraintWidget();
widget.mListDimensionBehaviors[HORIZONTAL] = MATCH_CONSTRAINT;
widget.mListDimensionBehaviors[VERTICAL] = MATCH_CONSTRAINT;
widget.stringId = String.valueOf(widget.hashCode());
return widget;
}
/**
* Connect the widget to the corresponding widgetBoxes based on the input params
*
* @param widget the widget that we want to add constraints to
* @param row row position to place the widget
* @param column column position to place the widget
*/
private void connectWidget(ConstraintWidget widget, int row, int column,
int rowSpan, int columnSpan) {
// Connect the 4 sides
widget.mLeft.connect(mBoxWidgets[column].mLeft, 0);
widget.mTop.connect(mBoxWidgets[row].mTop, 0);
widget.mRight.connect(mBoxWidgets[column + columnSpan - 1].mRight, 0);
widget.mBottom.connect(mBoxWidgets[row + rowSpan - 1].mBottom, 0);
}
/**
* Set chain between boxWidget horizontally
*/
private void setBoxWidgetHorizontalChains() {
int maxVal = Math.max(mRows, mColumns);
ConstraintWidget widget = mBoxWidgets[0];
float[] columnWeights = parseWeights(mColumns, mColumnWeights);
// chain all the widgets on the longer side (either horizontal or vertical)
if (mColumns == 1) {
clearHorizontalAttributes(widget);
widget.mLeft.connect(mLeft, 0);
widget.mRight.connect(mRight, 0);
return;
}
// chains are grid <- box <-> box <-> box -> grid
for (int i = 0; i < mColumns; i++) {
widget = mBoxWidgets[i];
clearHorizontalAttributes(widget);
if (columnWeights != null) {
widget.setHorizontalWeight(columnWeights[i]);
}
if (i > 0) {
widget.mLeft.connect(mBoxWidgets[i - 1].mRight, 0);
} else {
widget.mLeft.connect(mLeft, 0);
}
if (i < mColumns - 1) {
widget.mRight.connect(mBoxWidgets[i + 1].mLeft, 0);
} else {
widget.mRight.connect(mRight, 0);
}
if (i > 0) {
widget.mLeft.mMargin = (int) mHorizontalGaps;
}
}
// excess boxes are connected to grid those sides are not use
// for efficiency they should be connected to parent
for (int i = mColumns; i < maxVal; i++) {
widget = mBoxWidgets[i];
clearHorizontalAttributes(widget);
widget.mLeft.connect(mLeft, 0);
widget.mRight.connect(mRight, 0);
}
}
/**
* Set chain between boxWidget vertically
*/
private void setBoxWidgetVerticalChains() {
int maxVal = Math.max(mRows, mColumns);
ConstraintWidget widget = mBoxWidgets[0];
float[] rowWeights = parseWeights(mRows, mRowWeights);
// chain all the widgets on the longer side (either horizontal or vertical)
if (mRows == 1) {
clearVerticalAttributes(widget);
widget.mTop.connect(mTop, 0);
widget.mBottom.connect(mBottom, 0);
return;
}
// chains are constrained like this: grid <- box <-> box <-> box -> grid
for (int i = 0; i < mRows; i++) {
widget = mBoxWidgets[i];
clearVerticalAttributes(widget);
if (rowWeights != null) {
widget.setVerticalWeight(rowWeights[i]);
}
if (i > 0) {
widget.mTop.connect(mBoxWidgets[i - 1].mBottom, 0);
} else {
widget.mTop.connect(mTop, 0);
}
if (i < mRows - 1) {
widget.mBottom.connect(mBoxWidgets[i + 1].mTop, 0);
} else {
widget.mBottom.connect(mBottom, 0);
}
if (i > 0) {
widget.mTop.mMargin = (int) mVerticalGaps;
}
}
// excess boxes are connected to grid those sides are not use
// for efficiency they should be connected to parent
for (int i = mRows; i < maxVal; i++) {
widget = mBoxWidgets[i];
clearVerticalAttributes(widget);
widget.mTop.connect(mTop, 0);
widget.mBottom.connect(mBottom, 0);
}
}
/**
* Chains the boxWidgets and add constraints to the widgets
*/
private void addConstraints() {
setBoxWidgetVerticalChains();
setBoxWidgetHorizontalChains();
arrangeWidgets();
}
/**
* Create all the boxWidgets that will be used to constrain widgets
*/
private void createBoxes() {
int boxCount = Math.max(mRows, mColumns);
if (mBoxWidgets == null) { // no box widgets build all
mBoxWidgets = new ConstraintWidget[boxCount];
for (int i = 0; i < mBoxWidgets.length; i++) {
mBoxWidgets[i] = makeNewWidget(); // need to remove old Widgets
}
} else {
if (boxCount != mBoxWidgets.length) {
ConstraintWidget[] temp = new ConstraintWidget[boxCount];
for (int i = 0; i < boxCount; i++) {
if (i < mBoxWidgets.length) { // use old one
temp[i] = mBoxWidgets[i];
} else { // make new one
temp[i] = makeNewWidget();
}
}
// remove excess
for (int j = boxCount; j < mBoxWidgets.length; j++) {
ConstraintWidget widget = mBoxWidgets[j];
mContainer.remove(widget);
}
mBoxWidgets = temp;
}
}
}
/**
* Clear the vertical related attributes
* @param widget widget that has the attributes to be cleared
*/
private void clearVerticalAttributes(ConstraintWidget widget) {
widget.setVerticalWeight(UNKNOWN);
widget.mTop.reset();
widget.mBottom.reset();
widget.mBaseline.reset();
}
/**
* Clear the horizontal related attributes
* @param widget widget that has the attributes to be cleared
*/
private void clearHorizontalAttributes(ConstraintWidget widget) {
widget.setHorizontalWeight(UNKNOWN);
widget.mLeft.reset();
widget.mRight.reset();
}
/**
* Initialize the relevant variables
*/
private void initVariables() {
mPositionMatrix = new boolean[mRows][mColumns];
for (boolean[] row : mPositionMatrix) {
Arrays.fill(row, true);
}
if (mWidgetsCount > 0) {
mConstraintMatrix = new int[mWidgetsCount][4];
for (int[] row : mConstraintMatrix) {
Arrays.fill(row, -1);
}
}
}
/**
* parse the skips/spans in the string format into a int matrix
* that each row has the information - [index, row_span, col_span]
* the format of the input string is index:row_spanxcol_span.
* index - the index of the starting position
* row_span - the number of rows to span
* col_span- the number of columns to span
*
* @param str string format of skips or spans
* @param isSpans whether is spans to be parsed (it is skips if not)
* @return a int matrix that contains skip information.
*/
private int[][] parseSpans(String str, boolean isSpans) {
try {
int extraRows = 0;
int extraColumns = 0;
String[] spans = str.split(",");
// Sort the spans by the position
Arrays.sort(spans, (span1, span2) -> Integer.parseInt(span1.split(":")[0])
- Integer.parseInt(span2.split(":")[0]));
int[][] spanMatrix = new int[spans.length][3];
String[] indexAndSpan;
if (mRows == 1 || mColumns == 1) {
for (int i = 0; i < spans.length; i++) {
indexAndSpan = spans[i].trim().split(":");
spanMatrix[i][0] = Integer.parseInt(indexAndSpan[0]);
spanMatrix[i][1] = 1;
spanMatrix[i][2] = 1;
if (mColumns == 1) {
spanMatrix[i][1] = Integer.parseInt(indexAndSpan[1]);
extraRows += spanMatrix[i][1];
if (isSpans) {
extraRows--;
}
}
if (mRows == 1) {
spanMatrix[i][2] = Integer.parseInt(indexAndSpan[1]);
extraColumns += spanMatrix[i][2];
if (isSpans) {
extraColumns--;
}
}
}
if (extraRows != 0 && !mExtraSpaceHandled) {
this.setRows(mRows + extraRows);
}
if (extraColumns != 0 && !mExtraSpaceHandled) {
this.setColumns(mColumns + extraColumns);
}
mExtraSpaceHandled = true;
} else {
String[] rowAndCol;
for (int i = 0; i < spans.length; i++) {
indexAndSpan = spans[i].trim().split(":");
rowAndCol = indexAndSpan[1].split("x");
spanMatrix[i][0] = Integer.parseInt(indexAndSpan[0]);
if (isSubGridByColRow()) {
spanMatrix[i][1] = Integer.parseInt(rowAndCol[1]);
spanMatrix[i][2] = Integer.parseInt(rowAndCol[0]);
} else {
spanMatrix[i][1] = Integer.parseInt(rowAndCol[0]);
spanMatrix[i][2] = Integer.parseInt(rowAndCol[1]);
}
}
}
return spanMatrix;
} catch (Exception e) {
return null;
}
}
/**
* fill the constraintMatrix based on the input attributes
*
* @param isUpdate whether to update the existing grid (true) or create a new one (false)
*/
private void fillConstraintMatrix(boolean isUpdate) {
if (isUpdate) {
for (int i = 0; i < mPositionMatrix.length; i++) {
for (int j = 0; j < mPositionMatrix[0].length; j++) {
mPositionMatrix[i][j] = true;
}
}
for (int i = 0; i < mConstraintMatrix.length; i++) {
for (int j = 0; j < mConstraintMatrix[0].length; j++) {
mConstraintMatrix[i][j] = -1;
}
}
}
mNextAvailableIndex = 0;
if (mSkips != null && !mSkips.trim().isEmpty()) {
int[][] mSkips = parseSpans(this.mSkips, false);
if (mSkips != null) {
handleSkips(mSkips);
}
}
if (mSpans != null && !mSpans.trim().isEmpty()) {
int[][] mSpans = parseSpans(this.mSpans, true);
if (mSpans != null) {
handleSpans(mSpans);
}
}
}
/**
* Set up the Grid engine.
*/
private void initMatrices() {
boolean isUpdate = mConstraintMatrix != null
&& mConstraintMatrix.length == mWidgetsCount
&& mPositionMatrix != null
&& mPositionMatrix.length == mRows
&& mPositionMatrix[0].length == mColumns;
if (!isUpdate) {
initVariables();
}
fillConstraintMatrix(isUpdate);
}
/**
* Flag to implicitly reverse the order of width/height specified in spans & skips.
* E.g.: 1:3x2 is read as 1:2x3
*/
private boolean isSubGridByColRow() {
return (mFlags & SUB_GRID_BY_COL_ROW) > 0;
}
/**
* Flag to respect the order of the Widgets when arranging for spans.
*/
private boolean isSpansRespectWidgetOrder() {
return (mFlags & SPANS_RESPECT_WIDGET_ORDER) > 0;
}
@Override
public void measure(int widthMode, int widthSize, int heightMode, int heightSize) {
super.measure(widthMode, widthSize, heightMode, heightSize);
mContainer = (ConstraintWidgetContainer) getParent();
setupGrid(false);
mContainer.add(mBoxWidgets);
}
@Override
public void addToSolver(@Nullable LinearSystem system, boolean optimize) {
super.addToSolver(system, optimize);
addConstraints();
}
}