diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/helper/widget/Grid.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/helper/widget/Grid.java
index b93ca34f4..6495df7c2 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/helper/widget/Grid.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/helper/widget/Grid.java
@@ -16,12 +16,9 @@
package androidx.constraintlayout.helper.widget;
import android.content.Context;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Pair;
-import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -53,6 +50,14 @@
*
Indicates the number of columns will be created for the grid form. |
*
*
+ * grid_rowWeights |
+ * Specifies the weight of each row in the grid form (default value is 1). |
+ *
+ *
+ * grid_columnWeights |
+ * Specifies the weight of each column in the grid form (default value is 1). |
+ *
+ *
* grid_spans |
* Offers the capability to span a widget across multiple rows and columns |
*
@@ -110,6 +115,16 @@ public class Grid extends VirtualLayout {
*/
private String mStrSkips;
+ /**
+ * string format of the row weight
+ */
+ private String mStrRowWeights;
+
+ /**
+ * string format of the column weight
+ */
+ private String mStrColumnWeights;
+
/**
* Horizontal gaps in Dp
*/
@@ -152,52 +167,6 @@ public class Grid extends VirtualLayout {
*/
Set mSpanIds = new HashSet<>();
- /**
- * class that stores the relevant span information
- */
- static class Span {
- int mId;
- int mStartRow;
- int mStartColumn;
- int mRowSpan;
- int mColumnSpan;
- String mGravity;
-
- Span(int id, int startRow, int startColumn,
- int rowSpan, int columnSpan, String gravity) {
- this.mId = id;
- this.mStartRow = startRow;
- this.mStartColumn = startColumn;
- this.mRowSpan = rowSpan;
- this.mColumnSpan = columnSpan;
- this.mGravity = gravity;
- }
-
- public int getId() {
- return mId;
- }
-
- public int getStartRow() {
- return mStartRow;
- }
-
- public int getStartColumn() {
- return mStartColumn;
- }
-
- public int getRowSpan() {
- return mRowSpan;
- }
-
- public int getColumnSpan() {
- return mColumnSpan;
- }
-
- public String getGravity() {
- return mGravity;
- }
- }
-
public Grid(Context context) {
super(context);
}
@@ -230,7 +199,11 @@ protected void init(AttributeSet attrs) {
mStrSpans = a.getString(attr);
} else if (attr == R.styleable.Grid_grid_skips) {
mStrSkips = a.getString(attr);
- } else if (attr == R.styleable.Grid_grid_orientation) {
+ } else if (attr == R.styleable.Grid_grid_rowWeights) {
+ mStrRowWeights = a.getString(attr);
+ } else if (attr == R.styleable.Grid_grid_columnWeights) {
+ mStrColumnWeights = a.getString(attr);
+ } else if (attr == R.styleable.Grid_grid_orientation) {
mOrientation = a.getString(attr);
} else if (attr == R.styleable.Grid_grid_horizontalGaps) {
mHorizontalGaps = a.getInteger(attr, 0);
@@ -256,23 +229,37 @@ public void onAttachedToWindow() {
mContainer = (ConstraintLayout) getParent();
mConstraintSet.clone(mContainer);
+
+ generateGrid();
+ }
+
+ /**
+ * generate the Grid form based on the input attributes
+ * @return true if all the inputs are valid else false
+ */
+ private boolean generateGrid() {
+ boolean isSuccess = true;
+
createGuidelines(mRows, mColumns);
if (mStrSkips != null && !mStrSkips.trim().isEmpty()) {
- HashMap> mSkipMap = parseSkips(mStrSkips);
+ HashMap> mSkipMap = parseSpans(mStrSkips);
if (mSkipMap != null) {
- handleSkips(mSkipMap);
+ isSuccess &= handleSkips(mSkipMap);
}
}
if (mStrSpans != null && !mStrSpans.trim().isEmpty()) {
- Span[] mSpans = parseSpans(mStrSpans);
+ HashMap> mSpans = parseSpans(mStrSpans);
if (mSpans != null) {
- handleSpans(mSpans);
+ isSuccess &= handleSpans(mIds, mSpans);
}
}
+ isSuccess &= arrangeWidgets();
+
+ mConstraintSet.applyTo(mContainer);
- arrangeWidgets();
+ return isSuccess || !mValidateInputs;
}
/**
@@ -288,15 +275,42 @@ private void initVariables() {
mVerticalGuideLines = new Guideline[mColumns + 1];
}
+ /**
+ * parse the weights/pads in the string format into a float array
+ * @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(",");
+ if (values.length != size) {
+ return null;
+ }
+
+ float[] arr = new float[size];
+ for (int i = 0; i < arr.length; i++) {
+ arr[i] = Float.parseFloat(values[i].trim());
+ }
+ return arr;
+ }
+
/**
* create vertical and horizontal guidelines based on mRows and mColumns
* @param rows number of rows is required for grid
* @param columns number of columns is required for grid
*/
private void createGuidelines(int rows, int columns) {
+ float[] rowWeights = parseWeights(rows, mStrRowWeights);
+ float[] columnWeights = parseWeights(columns, mStrColumnWeights);
- float[] horizontalPositions = getLinspace(0, 1, rows + 1);
- float[] verticalPositions = getLinspace(0, 1, columns + 1);
+ float[] horizontalPositions = getLinePositions(0, 1,
+ rows + 1, rowWeights);
+ float[] verticalPositions = getLinePositions(0, 1,
+ columns + 1, columnWeights);
for (int i = 0; i < mHorizontalGuideLines.length; i++) {
mHorizontalGuideLines[i] = getNewGuideline(myContext,
@@ -335,10 +349,9 @@ private Guideline getNewGuideline(Context context, int orientation, float positi
* @param viewId the Id of the view
* @param row row position to place the view
* @param column column position to place the view
- * @param gravity gravity info, including top, left, bottom, right, guideline,start,end
*/
private void connectView(int viewId, int row, int column, int rowSpan, int columnSpan,
- int horizontalGaps, int verticalGaps, String gravity) {
+ int horizontalGaps, int verticalGaps) {
// @TODO handle RTL
// connect Start of the view
@@ -358,13 +371,6 @@ private void connectView(int viewId, int row, int column, int rowSpan, int colum
mConstraintSet.connect(viewId, ConstraintSet.BOTTOM,
mHorizontalGuideLines[row + rowSpan].getId(),
ConstraintSet.TOP, verticalGaps);
-
- // handle gravity
- if (!gravity.trim().equals("")) {
- handleGravity(viewId, gravity);
- }
-
- mConstraintSet.applyTo(mContainer);
}
/**
@@ -387,7 +393,7 @@ private boolean arrangeWidgets() {
return false;
}
connectView(mIds[i], position.first, position.second,
- 1, 1, mHorizontalGaps, mVerticalGaps, "");
+ 1, 1, mHorizontalGaps, mVerticalGaps);
}
return true;
}
@@ -437,136 +443,33 @@ private Pair getNextPosition() {
return new Pair<>(position.first, position.second);
}
- /**
- * Handle the gravity. The value could be t, r, b, l, s, e, tl, br, etc.
- * t = top, r = right, b = bottom l = left, s = start, e = end
- * @param viewId the id of a view
- * @param gravity the gravity
- */
- private void handleGravity(int viewId, String gravity) {
- for (int i = 0; i < gravity.length(); i++) {
- // @TODO handle RTL
- switch (gravity.charAt(i)) {
- case 't':
- mConstraintSet.setVerticalBias(viewId, 0);
- break;
- case 'r':
- mConstraintSet.setHorizontalBias(viewId, 1);
- break;
- case 'b':
- mConstraintSet.setVerticalBias(viewId, 1);
- break;
- case 'l':
- mConstraintSet.setHorizontalBias(viewId, 0);
- break;
- case 's':
- mConstraintSet.setHorizontalBias(viewId, 0);
- break;
- case 'e':
- mConstraintSet.setHorizontalBias(viewId, 1);
- break;
- default:
- Log.w(TAG, "unknown gravity value: " + gravity.charAt(i));
- }
- }
- }
-
- /**
- * Check if the value of the Spans is valid
- * @param mStrSpans spans in string format
- * @return true if it is valid else false
- */
- private boolean isSpansValid(String mStrSpans) {
- // TODO: check string has a valid format.
- return true;
- }
-
- /**
- * Parse the spans in the string format into a span object
- * the format of a span is viewId|index:rowSpanxcolumnSpan-gravity
- * viewID - The id of a view in the constraint_referenced_ids list
- * index - the index of the starting position
- * row_span - The number of rows to span
- * col_span- The number of columns to span
- * gravity (optional) - letters t, l, b, r, s ,e = top, left, bottom, right, start, end.
- * Two letters could be used together (e.g., tl, br, etc.)
- * @param strSpans Grid spans in the string format
- * @return a HashMap contains span information of individual views.
- */
- private Span[] parseSpans(String strSpans) {
- if (!isSpansValid(strSpans)) {
- return null;
- }
-
- String[] spans = strSpans.split(",");
- Span[] spanArray = new Span[spans.length];
-
- for (int i = 0; i < spans.length; i++) {
- String[] idAndRest = spans[i].trim().split(":");
- String[] startPositionAndRest = idAndRest[1].split("#");
- String[] rowSpanAndRest = startPositionAndRest[1].split("x");
- String[] colSpanAndGravity = rowSpanAndRest[1].split("-");
-
- int id = findId(mContainer, idAndRest[0]);
- Pair startPosition =
- getPositionByIndex(Integer.parseInt(startPositionAndRest[0]));
- int rowSpan = Integer.parseInt(rowSpanAndRest[0]);
- int columnSpan = Integer.parseInt(colSpanAndGravity[0]);
- String gravity = colSpanAndGravity.length > 1 ? colSpanAndGravity[1] : "";
-
- spanArray[i] = new Span(id, startPosition.first, startPosition.second,
- rowSpan, columnSpan, gravity);
- }
- return spanArray;
- }
-
- /**
- * Handle the span use cases
- * @param spans a array of span object
- * @return true if the input spans is valid else false
- */
- private boolean handleSpans(Span[] spans) {
- for (Span span : spans) {
- if (!invalidatePositions(span.mStartRow, span.mStartColumn,
- span.mRowSpan, span.mColumnSpan)) {
- // Try to place the widget to the skipped space
- return false;
- }
- connectView(span.mId, span.mStartRow, span.mStartColumn, span.mRowSpan,
- span.mColumnSpan, mHorizontalGaps, mVerticalGaps, span.mGravity);
- mSpanIds.add(span.mId);
- }
- return true;
- }
-
/**
* Check if the value of the skips is valid
- * @param mStrSkips skips in string format
+ * @param str skips in string format
* @return true if it is valid else false
*/
- private boolean isSkipsValid(String mStrSkips) {
+ private boolean isSpansValid(String str) {
// TODO: check string has a valid format.
return true;
}
/**
- * parse the skips in the string format into a HashMap>
+ * parse the skips/spans in the string format into a HashMap>
* 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 strSkips string format of skips
+ * @param str string format of skips or spans
* @return a hashmap that contains skip information.
*/
- private HashMap> parseSkips(String strSkips) {
- // TODO: check string has a valid format.
- if (!isSkipsValid(strSkips)) {
+ private HashMap> parseSpans(String str) {
+ if (!isSpansValid(str)) {
return null;
}
HashMap> skipMap = new HashMap<>();
- String[] skips = strSkips.split(",");
+ String[] skips = str.split(",");
String[] indexAndSpan;
String[] rowAndCol;
for (String skip: skips) {
@@ -578,6 +481,29 @@ private HashMap> parseSkips(String strSkips) {
return skipMap;
}
+ /**
+ * Handle the span use cases
+ * @param spansMap a hashmap that contains span information
+ * @return true if the input spans is valid else false
+ */
+ private boolean handleSpans(int[] mId, HashMap> spansMap) {
+ int mIdIndex = 0;
+ Pair startPosition;
+ for (Map.Entry> entry : spansMap.entrySet()) {
+ startPosition = getPositionByIndex(entry.getKey());
+ if (!invalidatePositions(startPosition.first, startPosition.second,
+ entry.getValue().first, entry.getValue().second)) {
+ return false;
+ }
+ connectView(mId[mIdIndex], startPosition.first, startPosition.second,
+ entry.getValue().first, entry.getValue().second,
+ mHorizontalGaps, mVerticalGaps);
+ mSpanIds.add(mId[mIdIndex]);
+ mIdIndex++;
+ }
+ return true;
+ }
+
/**
* Make positions in the grid unavailable based on the skips attr
* @param skipsMap a hashmap that contains skip information
@@ -618,69 +544,76 @@ private boolean invalidatePositions(int startRow, int startColumn,
return true;
}
- // From ConstraintHelper -> move to a util function
/**
- * Iterate through the container's children to find a matching id.
- * Slow path, seems necessary to handle dynamic modules resolution...
- *
- * @param container the parent container - a ConstraintLayout in this case
- * @param idString the string format of a view Id
- * @return the actual viewId in Integer
+ * Generate line positions (for the Guideline positioning)
+ * @param min min value of the linear spaced positions
+ * * @param max max value of the linear spaced positions
+ * @param numPositions number of positions is required
+ * @param weights a float array for space weights
+ * @return a float array of the corresponding positions
*/
- private int findId(ConstraintLayout container, String idString) {
- if (idString == null || container == null) {
- return 0;
- }
- Resources resources = myContext.getResources();
- if (resources == null) {
- return 0;
+ private float[] getLinePositions(float min, float max, int numPositions, float[] weights) {
+ if (weights != null && numPositions - 1 != weights.length) {
+ return null;
}
- final int count = container.getChildCount();
- for (int j = 0; j < count; j++) {
- View child = container.getChildAt(j);
- if (child.getId() != -1) {
- String res = null;
- try {
- res = resources.getResourceEntryName(child.getId());
- } catch (android.content.res.Resources.NotFoundException e) {
- // nothing
- }
- if (idString.equals(res)) {
- return child.getId();
- }
- }
+
+ float[] positions = new float[numPositions];
+ int weightSum = 0;
+ for (int i = 0; i < numPositions - 1; i++) {
+ weightSum += weights != null ? weights[i] : 1;
}
- return 0;
- }
- /**
- * Generate linearly spaced positions (for the Guideline positioning)
- * @param min min value of the linear spaced positions
- * @param max max value of the linear spaced positions
- * @param positions number of positions in the space
- * @return an float array of the corresponding positions
- */
- private float[] getLinspace(float min, float max, int positions) {
- float[] d = new float[positions];
- for (int i = 0; i < positions; i++) {
- d[i] = min + i * (max - min) / (positions - 1);
+ float availableSpace = max - min;
+ float baseWeight = availableSpace / weightSum;
+ positions[0] = min;
+ for (int i = 0; i < numPositions - 1; i++) {
+ float w = weights != null ? weights[i] : 1;
+ positions[i + 1] = positions[i] + w * baseWeight;
}
- return d;
+ return positions;
}
/**
* get the string value of spans
* @return the string value of spans
*/
- public String getStrSpans() {
+ public String getSpans() {
return mStrSpans;
}
+ /**
+ * set new spans value and also invoke requestLayout
+ * @param spans new spans value
+ * @return true if it succeeds otherwise false
+ */
+ public Boolean setSpans(String spans) {
+ if (!isSpansValid(spans)) {
+ return false;
+ }
+ mStrSpans = spans;
+ requestLayout();
+ return true;
+ }
+
/**
* get the string value of skips
* @return the string value of skips
*/
- public String getStrSkips() {
+ public String getSkips() {
return mStrSkips;
}
+
+ /**
+ * set new skips value and also invoke requestLayout
+ * @param skips new spans value
+ * @return true if it succeeds otherwise false
+ */
+ public Boolean setSkips(String skips) {
+ if (!isSpansValid(skips)) {
+ return false;
+ }
+ mStrSkips = skips;
+ requestLayout();
+ return true;
+ }
}
diff --git a/constraintlayout/constraintlayout/src/main/res/values/attrs.xml b/constraintlayout/constraintlayout/src/main/res/values/attrs.xml
index 4bd9d4004..9f2c814d3 100644
--- a/constraintlayout/constraintlayout/src/main/res/values/attrs.xml
+++ b/constraintlayout/constraintlayout/src/main/res/values/attrs.xml
@@ -308,6 +308,8 @@
+
+
diff --git a/projects/GridExperiments/app/src/main/res/layout/activity_main.xml b/projects/GridExperiments/app/src/main/res/layout/activity_main.xml
index 6a102bb5e..0db1b3079 100644
--- a/projects/GridExperiments/app/src/main/res/layout/activity_main.xml
+++ b/projects/GridExperiments/app/src/main/res/layout/activity_main.xml
@@ -12,6 +12,7 @@
app:constraint_referenced_ids="btn0,btn1,btn2,btn3"
app:grid_columns="3"
app:grid_rows="3"
+ app:grid_columnWeights="3,1,2"
app:grid_horizontalGaps="10"
app:grid_verticalGaps="10"
app:grid_orientation="horizontal"
diff --git a/projects/GridExperiments/app/src/main/res/layout/calculator.xml b/projects/GridExperiments/app/src/main/res/layout/calculator.xml
index de38729d2..57942ec9f 100644
--- a/projects/GridExperiments/app/src/main/res/layout/calculator.xml
+++ b/projects/GridExperiments/app/src/main/res/layout/calculator.xml
@@ -9,13 +9,13 @@
android:id="@+id/grid"
android:layout_width="match_parent"
android:layout_height="match_parent"
- app:constraint_referenced_ids="clear,neg,percent,div,btn7,btn8,btn9,mult,btn4,btn5,btn6,sub,btn1,btn2,btn3,plus,btn0,dot,equal"
+ app:constraint_referenced_ids="calculatorText,btn0,clear,neg,percent,div,btn7,btn8,btn9,mult,btn4,btn5,btn6,sub,btn1,btn2,btn3,plus,btn0,dot,equal"
app:grid_columns="4"
app:grid_rows="7"
app:grid_horizontalGaps="5"
app:grid_verticalGaps="5"
app:grid_orientation="horizontal"
- app:grid_spans="calculatorText:0#2x4,btn0:24#1x2" />
+ app:grid_spans="0:2x4,24:1x2" />