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 aa51d695b..33589db94 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 @@ -26,7 +26,6 @@ import android.view.View; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; import androidx.constraintlayout.widget.R; import androidx.constraintlayout.widget.VirtualLayout; @@ -84,9 +83,11 @@ public class Grid extends VirtualLayout { private static final String TAG = "Grid"; public static final int VERTICAL = 1; public static final int HORIZONTAL = 0; + private static final boolean DEBUG_BOXES = false; private final int mMaxRows = 50; // maximum number of rows can be specified. private final int mMaxColumns = 50; // maximum number of columns can be specified. - private final ConstraintSet mConstraintSet = new ConstraintSet(); + // private final ConstraintSet mConstraintSet = new ConstraintSet(); + private View[] mBoxViews; ConstraintLayout mContainer; @@ -209,11 +210,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_rowWeights) { + } else if (attr == R.styleable.Grid_grid_rowWeights) { mStrRowWeights = a.getString(attr); - } else if (attr == R.styleable.Grid_grid_columnWeights) { + } else if (attr == R.styleable.Grid_grid_columnWeights) { mStrColumnWeights = a.getString(attr); - } else if (attr == R.styleable.Grid_grid_orientation) { + } else if (attr == R.styleable.Grid_grid_orientation) { mOrientation = a.getInt(attr, 0); } else if (attr == R.styleable.Grid_grid_horizontalGaps) { mHorizontalGaps = a.getDimension(attr, 0); @@ -222,7 +223,7 @@ protected void init(AttributeSet attrs) { } else if (attr == R.styleable.Grid_grid_validateInputs) { // @TODO handle validation mValidateInputs = a.getBoolean(attr, false); - } else if (attr == R.styleable.Grid_grid_useRtl) { + } else if (attr == R.styleable.Grid_grid_useRtl) { // @TODO handle RTL mUseRtl = a.getBoolean(attr, false); } @@ -243,13 +244,13 @@ private void updateActualRowsAndColumns() { if (mRowsSet == 0 || mColumnsSet == 0) { if (mColumnsSet > 0) { mColumns = mColumnsSet; - mRows = (mCount + mColumns -1) / mColumnsSet; // round up - } else if (mRowsSet > 0) { + mRows = (mCount + mColumns - 1) / mColumnsSet; // round up + } else if (mRowsSet > 0) { mRows = mRowsSet; - mColumns= (mCount + mRowsSet -1) / mRowsSet; // round up + mColumns = (mCount + mRowsSet - 1) / mRowsSet; // round up } else { // as close to square as possible favoring more rows - mRows = (int) (1.5 + Math.sqrt(mCount)); - mColumns = (mCount + mRows -1) / mRows; + mRows = (int) (1.5 + Math.sqrt(mCount)); + mColumns = (mCount + mRows - 1) / mRows; } } else { mRows = mRowsSet; @@ -262,18 +263,18 @@ public void onAttachedToWindow() { super.onAttachedToWindow(); mContainer = (ConstraintLayout) getParent(); - mConstraintSet.clone(mContainer); generateGrid(false); } /** * generate the Grid form based on the input attributes + * * @param isUpdate whether to update the existing grid (true) or create a new one (false) * @return true if all the inputs are valid else false */ private boolean generateGrid(boolean isUpdate) { - if (mContainer == null || mConstraintSet == null || mRows < 1 || mColumns < 1) { + if (mContainer == null || mRows < 1 || mColumns < 1) { return false; } @@ -306,8 +307,8 @@ private boolean generateGrid(boolean isUpdate) { } isSuccess &= arrangeWidgets(); - mConstraintSet.applyTo(mContainer); - + mContainer.requestLayout(); + applyLayoutFeatures(); return isSuccess || !mValidateInputs; } @@ -316,13 +317,14 @@ private boolean generateGrid(boolean isUpdate) { */ private void initVariables() { mPositionMatrix = new boolean[mRows][mColumns]; - for (boolean[] row: mPositionMatrix) { + for (boolean[] row : mPositionMatrix) { Arrays.fill(row, true); } } /** * 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 @@ -344,36 +346,36 @@ private float[] parseWeights(int size, String str) { return arr; } + private ConstraintLayout.LayoutParams params(View v) { + return (ConstraintLayout.LayoutParams) v.getLayoutParams(); + } + /** * Connect the view to the corresponding viewBoxes based on the input params - * @param viewId the Id of the view - * @param row row position to place the view + * + * @param view the Id of the view + * @param row row position to place the view * @param column column position to place the view */ - private void connectView(int viewId, int row, int column, int rowSpan, int columnSpan) { + private void connectView(View view, int row, int column, int rowSpan, int columnSpan) { + ConstraintLayout.LayoutParams params = params(view); // @TODO handle RTL - // connect Start of the view - mConstraintSet.connect(viewId, ConstraintSet.LEFT, mBoxViewIds[column], ConstraintSet.LEFT); - - // connect Top of the view - mConstraintSet.connect(viewId, ConstraintSet.TOP, mBoxViewIds[row], ConstraintSet.TOP); - - mConstraintSet.connect(viewId, ConstraintSet.RIGHT, - mBoxViewIds[column + columnSpan - 1], ConstraintSet.RIGHT); - - // connect Bottom of the view - mConstraintSet.connect(viewId, ConstraintSet.BOTTOM, - mBoxViewIds[row + rowSpan - 1], ConstraintSet.BOTTOM); + // Connect the 4 sides + params.leftToLeft = mBoxViewIds[column]; + params.topToTop = mBoxViewIds[row]; + params.rightToRight = mBoxViewIds[column + columnSpan - 1]; + params.bottomToBottom = mBoxViewIds[row + rowSpan - 1]; } /** * Arrange the views in the constraint_referenced_ids + * * @return true if all the widgets can be arranged properly else false */ private boolean arrangeWidgets() { int position; - + View[] views = getViews(mContainer); // @TODO handle RTL for (int i = 0; i < mCount; i++) { if (mSpanIds.contains(mIds[i])) { @@ -388,19 +390,21 @@ private boolean arrangeWidgets() { // no more available position. return false; } - connectView(mIds[i], row, col, 1, 1); + + connectView(views[i], row, col, 1, 1); } return true; } /** * Convert a 1D index to a 2D index that has index for row and index for column + * * @param index index in 1D * @return row as its values. */ private int getRowByIndex(int index) { if (mOrientation == 1) { - return index % mRows; + return index % mRows; } else { return index / mColumns; @@ -410,6 +414,7 @@ private int getRowByIndex(int index) { /** * Convert a 1D index to a 2D index that has index for row and index for column + * * @param index index in 1D * @return column as its values. */ @@ -423,10 +428,11 @@ private int getColByIndex(int index) { /** * Get the next available position for widget arrangement. + * * @return int[] -> [row, column] */ private int getNextPosition() { - // int[] position = new int[] {0, 0}; + // int[] position = new int[] {0, 0}; int position = 0; boolean positionFound = false; @@ -435,7 +441,7 @@ private int getNextPosition() { return -1; } - // position = getPositionByIndex(mNextAvailableIndex); + // position = getPositionByIndex(mNextAvailableIndex); position = mNextAvailableIndex; int row = getRowByIndex(mNextAvailableIndex); int col = getColByIndex(mNextAvailableIndex); @@ -451,16 +457,18 @@ private int getNextPosition() { /** * Check if the value of the spans/skips is valid + * * @param str spans/skips in string format * @return true if it is valid else false */ - private boolean isSpansValid(String str) { + private boolean isSpansValid(CharSequence str) { // TODO: check string has a valid format. return true; } /** * Check if the value of the rowWeights or columnsWeights is valid + * * @param str rowWeights/columnsWeights in string format * @return true if it is valid else false */ @@ -476,6 +484,7 @@ private boolean isWeightsValid(String str) { * 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 * @return a int matrix that contains skip information. */ @@ -501,10 +510,12 @@ private int[][] parseSpans(String str) { /** * Handle the span use cases + * * @param spansMatrix a int matrix that contains span information * @return true if the input spans is valid else false */ private boolean handleSpans(int[] mId, int[][] spansMatrix) { + View[] views = getViews(mContainer); for (int i = 0; i < spansMatrix.length; i++) { int row = getRowByIndex(spansMatrix[i][0]); int col = getColByIndex(spansMatrix[i][0]); @@ -512,8 +523,10 @@ private boolean handleSpans(int[] mId, int[][] spansMatrix) { spansMatrix[i][1], spansMatrix[i][2])) { return false; } - connectView(mId[i], row, col, - spansMatrix[i][1], spansMatrix[i][2]); + + connectView(views[i], row, col, + spansMatrix[i][1], spansMatrix[i][2]); + mSpanIds.add(mId[i]); } return true; @@ -521,11 +534,12 @@ private boolean handleSpans(int[] mId, int[][] spansMatrix) { /** * Make positions in the grid unavailable based on the skips attr + * * @param skipsMatrix a int matrix that contains skip information * @return true if all the skips are valid else false */ private boolean handleSkips(int[][] skipsMatrix) { - for(int i = 0; i < skipsMatrix.length; i++) { + for (int i = 0; i < skipsMatrix.length; i++) { int row = getRowByIndex(skipsMatrix[i][0]); int col = getColByIndex(skipsMatrix[i][0]); if (!invalidatePositions(row, col, @@ -538,10 +552,11 @@ private boolean handleSkips(int[][] skipsMatrix) { /** * Make the specified positions in the grid unavailable. - * @param startRow the row of the staring position + * + * @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 + * @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, @@ -561,6 +576,7 @@ private boolean invalidatePositions(int startRow, int startColumn, /** * Visualize the boxViews that are used to constraint widgets. + * * @param canvas canvas to visualize the boxViews */ @Override @@ -590,46 +606,50 @@ public void onDraw(Canvas canvas) { } /** - * Set chain between boxView horzontally + * Set chain between boxView horizontally */ private void setBoxViewHorizontalChains() { int gridId = getId(); int maxVal = Math.max(mRows, mColumns); - int minVal = Math.min(mRows, mColumns); - float[] columnWeights = parseWeights(mColumns, mStrColumnWeights); + float[] columnWeights = parseWeights(mColumns, mStrColumnWeights); + ConstraintLayout.LayoutParams params = params(mBoxViews[0]); // chain all the views on the longer side (either horizontal or vertical) if (mColumns == 1) { - mConstraintSet.center(mBoxViewIds[0], gridId, ConstraintSet.LEFT, 0, gridId, - ConstraintSet.RIGHT, 0, 0.5f); - return; - } - if (maxVal == mColumns) { - mConstraintSet.createHorizontalChain(gridId, ConstraintSet.LEFT, gridId, - ConstraintSet.RIGHT, mBoxViewIds, columnWeights, - ConstraintSet.CHAIN_SPREAD_INSIDE); - for (int i = 1; i < mBoxViews.length; i++) { - mConstraintSet.setMargin(mBoxViewIds[i], ConstraintSet.LEFT, (int) mHorizontalGaps); - } + params.leftToLeft = gridId; + params.rightToRight = gridId; return; } - // chain partial veriws on the shorter side (either horizontal or vertical) - // add constraints to the parent for the non-chained views - mConstraintSet.createHorizontalChain(gridId, ConstraintSet.LEFT, gridId, - ConstraintSet.RIGHT, Arrays.copyOf(mBoxViewIds, minVal), columnWeights, - ConstraintSet.CHAIN_SPREAD_INSIDE); - for (int i = 1; i < mBoxViews.length; i++) { - if (i < minVal) { - mConstraintSet.setMargin(mBoxViewIds[i], ConstraintSet.LEFT, (int) mHorizontalGaps); + // chains are grid <- box <-> box <-> box -> grid + + for (int i = 0; i < mColumns; i++) { + params = params(mBoxViews[i]); + if (columnWeights != null) { + params.horizontalWeight = columnWeights[i]; + } + if (i > 0) { + params.leftToRight = mBoxViewIds[i - 1]; } else { - mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.LEFT, - gridId, ConstraintSet.LEFT); - mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.RIGHT, - gridId, ConstraintSet.RIGHT); + params.leftToLeft = gridId; + } + if (i < mColumns - 1) { + params.rightToLeft = mBoxViewIds[i + 1]; + } else { + params.rightToRight = gridId; + } + if (i > 0) { + params.leftMargin = (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++) { + params = params(mBoxViews[i]); + params.leftToLeft = gridId; + params.rightToRight = gridId; + } } /** @@ -638,58 +658,94 @@ private void setBoxViewHorizontalChains() { private void setBoxViewVerticalChains() { int gridId = getId(); int maxVal = Math.max(mRows, mColumns); - int minVal = Math.min(mRows, mColumns); - float[] rowWeights = parseWeights(mRows, mStrRowWeights); + float[] rowWeights = parseWeights(mRows, mStrRowWeights); + ConstraintLayout.LayoutParams params; // chain all the views on the longer side (either horizontal or vertical) if (mRows == 1) { - mConstraintSet.center(mBoxViewIds[0], gridId, ConstraintSet.TOP, 0, gridId, - ConstraintSet.BOTTOM, 0, 0.5f); + params = params(mBoxViews[0]); + params.topToTop = gridId; + params.bottomToBottom = gridId; return; } - if (maxVal == mRows) { - mConstraintSet.createVerticalChain(gridId, ConstraintSet.TOP, gridId, - ConstraintSet.BOTTOM, mBoxViewIds, rowWeights, - ConstraintSet.CHAIN_SPREAD_INSIDE); - for (int i = 1; i < mBoxViews.length; i++) { - mConstraintSet.setMargin(mBoxViewIds[i], ConstraintSet.TOP, (int) mVerticalGaps); + // chains are constrained like this: grid <- box <-> box <-> box -> grid + for (int i = 0; i < mRows; i++) { + params = params(mBoxViews[i]); + if (rowWeights != null) { + params.verticalWeight = rowWeights[i]; } - return; - } - - // chain partial veriws on the shorter side (either horizontal or vertical) - // add constraints to the parent for the non-chained views - mConstraintSet.createVerticalChain(gridId, ConstraintSet.TOP, gridId, - ConstraintSet.BOTTOM, Arrays.copyOf(mBoxViewIds, minVal), rowWeights, - ConstraintSet.CHAIN_SPREAD_INSIDE); - for (int i = 1; i < mBoxViews.length; i++) { - if (i < minVal) { - mConstraintSet.setMargin(mBoxViewIds[i], ConstraintSet.TOP, (int) mVerticalGaps); + if (i > 0) { + params.topToBottom = mBoxViewIds[i - 1]; + } else { + params.topToTop = gridId; + } + if (i < mRows - 1) { + params.bottomToTop = mBoxViewIds[i + 1]; } else { - mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.TOP, - gridId, ConstraintSet.TOP); - mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.BOTTOM, - gridId, ConstraintSet.BOTTOM); + params.bottomToBottom = gridId; } + if (i > 0) { + params.topMargin = (int) mHorizontalGaps; + } + } + + // 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++) { + params = params(mBoxViews[i]); + params.topToTop = gridId; + params.bottomToBottom = gridId; } } + private View makeNewView() { + View v = new View(getContext()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + v.setId(View.generateViewId()); + } + v.setVisibility(INVISIBLE); + if (DEBUG_BOXES) { + v.setVisibility(VISIBLE); + v.setBackgroundColor(0xFF880088); + } + ConstraintLayout.LayoutParams params = + new ConstraintLayout.LayoutParams(0, 0); + + mContainer.addView(v, params); + return v; + } + /** * create boxViews for constraining widgets */ private void buildBoxes() { int boxCount = Math.max(mRows, mColumns); - mBoxViews = new View[boxCount]; - mBoxViewIds = new int[boxCount]; - for (int i = 0; i < mBoxViews.length; i++) { - mBoxViews[i] = new View(getContext()); // need to remove old Views - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - mBoxViews[i].setId(View.generateViewId()); + if (mBoxViews == null) { // no box views build all + mBoxViews = new View[boxCount]; + for (int i = 0; i < mBoxViews.length; i++) { + mBoxViews[i] = makeNewView(); // need to remove old Views } - ConstraintLayout.LayoutParams params = - new ConstraintLayout.LayoutParams(0, 0); + } else { + if (boxCount != mBoxViews.length) { + View[] temp = new View[boxCount]; + for (int i = 0; i < boxCount; i++) { + if (i < mBoxViews.length) { // use old one + temp[i] = mBoxViews[i]; + } else { // make new one + temp[i] = makeNewView(); + } + } + // remove excess + for (int j = boxCount; j < mBoxViews.length; j++) { + View view = mBoxViews[j]; + mContainer.removeView(view); + } + mBoxViews = temp; + } + } - mContainer.addView(mBoxViews[i], params); + mBoxViewIds = new int[boxCount]; + for (int i = 0; i < mBoxViews.length; i++) { mBoxViewIds[i] = mBoxViews[i].getId(); } @@ -699,6 +755,7 @@ private void buildBoxes() { /** * get the value of rows + * * @return the value of rows */ public int getRows() { @@ -707,6 +764,7 @@ public int getRows() { /** * set new rows value and also invoke initVariables and invalidate + * * @param rows new rows value */ public void setRows(int rows) { @@ -728,6 +786,7 @@ public void setRows(int rows) { /** * get the value of columns + * * @return the value of columns */ public int getColumns() { @@ -736,6 +795,7 @@ public int getColumns() { /** * set new columns value and also invoke initVariables and invalidate + * * @param columns new rows value */ public void setColumns(int columns) { @@ -757,6 +817,7 @@ public void setColumns(int columns) { /** * get the value of orientation + * * @return the value of orientation */ public int getOrientation() { @@ -765,6 +826,7 @@ public int getOrientation() { /** * set new orientation value and also invoke invalidate + * * @param orientation new orientation value */ public void setOrientation(int orientation) { @@ -783,6 +845,7 @@ public void setOrientation(int orientation) { /** * get the string value of spans + * * @return the string value of spans */ public String getSpans() { @@ -791,24 +854,26 @@ public String getSpans() { /** * set new spans value and also invoke invalidate + * * @param spans new spans value */ - public void setSpans(String spans) { + public void setSpans(CharSequence spans) { if (!isSpansValid(spans)) { return; } - if (mStrSpans != null && mStrSpans.equals(spans)) { + if (mStrSpans != null && mStrSpans.contentEquals(spans)) { return; } - mStrSpans = spans; + mStrSpans = spans.toString(); generateGrid(true); invalidate(); } /** * get the string value of skips + * * @return the string value of skips */ public String getSkips() { @@ -817,6 +882,7 @@ public String getSkips() { /** * set new skips value and also invoke invalidate + * * @param skips new spans value */ public void setSkips(String skips) { @@ -835,6 +901,7 @@ public void setSkips(String skips) { /** * get the string value of rowWeights + * * @return the string value of rowWeights */ public String getRowWeights() { @@ -843,6 +910,7 @@ public String getRowWeights() { /** * set new rowWeights value and also invoke invalidate + * * @param rowWeights new rowWeights value */ public void setRowWeights(String rowWeights) { @@ -861,6 +929,7 @@ public void setRowWeights(String rowWeights) { /** * get the string value of columnWeights + * * @return the string value of columnWeights */ public String getColumnWeights() { @@ -869,6 +938,7 @@ public String getColumnWeights() { /** * set new columnWeights value and also invoke invalidate + * * @param columnWeights new columnWeights value */ public void setColumnWeights(String columnWeights) { @@ -887,6 +957,7 @@ public void setColumnWeights(String columnWeights) { /** * get the value of horizontalGaps + * * @return the value of horizontalGaps */ public float getHorizontalGaps() { @@ -894,7 +965,8 @@ public float getHorizontalGaps() { } /** - * set new horizontalGaps value and also invoke invalidate + * set new horizontalGaps value and also invoke invalidate + * * @param horizontalGaps new horizontalGaps value */ public void setHorizontalGaps(float horizontalGaps) { @@ -913,6 +985,7 @@ public void setHorizontalGaps(float horizontalGaps) { /** * get the value of verticalGaps + * * @return the value of verticalGaps */ public float getVerticalGaps() { @@ -921,6 +994,7 @@ public float getVerticalGaps() { /** * set new verticalGaps value and also invoke invalidate + * * @param verticalGaps new verticalGaps value */ public void setVerticalGaps(float verticalGaps) {