From 8ce9906dc4ac95195720c3434b2431101c828bc1 Mon Sep 17 00:00:00 2001 From: "Jian-Syuan (Shane) Wong" Date: Wed, 1 Jun 2022 08:46:18 -0700 Subject: [PATCH 1/5] [Grid] replace Guidelines with viewBoxes --- .../constraintlayout/helper/widget/Grid.java | 223 ++++++++++-------- .../support/constraint/app/MainActivity.java | 22 +- 2 files changed, 142 insertions(+), 103 deletions(-) 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 5887c7ba1..456214c70 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 @@ -17,17 +17,20 @@ import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; +import android.view.View; import androidx.constraintlayout.motion.widget.Debug; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; -import androidx.constraintlayout.widget.Guideline; import androidx.constraintlayout.widget.R; import androidx.constraintlayout.widget.VirtualLayout; -import androidx.core.view.ViewCompat; import java.util.Arrays; import java.util.HashMap; @@ -88,6 +91,8 @@ public class Grid extends VirtualLayout { 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 Paint mPaint = new Paint(); + private View[] mBoxViews; ConstraintLayout mContainer; /** @@ -100,16 +105,6 @@ public class Grid extends VirtualLayout { */ private int mColumns; - /** - * an Guideline array to store all the vertical guidelines - */ - private Guideline[] mVerticalGuideLines; - - /** - * an Guideline array to store all the horizontal guidelines - */ - private Guideline[] mHorizontalGuideLines; - /** * string format of the input Spans */ @@ -143,7 +138,7 @@ public class Grid extends VirtualLayout { /** * orientation of the view arrangement - vertical or horizontal */ - private int mOrientation = 0; // default value is horizontal + private int mOrientation; /** * Indicates what is the next available position to place an widget @@ -172,6 +167,11 @@ public class Grid extends VirtualLayout { */ Set mSpanIds = new HashSet<>(); + /** + * Ids boxViews where to specify constraints + */ + private int[] mAnchorIds; + public Grid(Context context) { super(context); } @@ -209,7 +209,7 @@ protected void init(AttributeSet attrs) { } else if (attr == R.styleable.Grid_grid_columnWeights) { mStrColumnWeights = a.getString(attr); } else if (attr == R.styleable.Grid_grid_orientation) { - mOrientation = a.getInt(attr,0); + mOrientation = a.getInt(attr, 0); } else if (attr == R.styleable.Grid_grid_horizontalGaps) { mHorizontalGaps = a.getDimension(attr, 0); } else if (attr == R.styleable.Grid_grid_verticalGaps) { @@ -222,7 +222,7 @@ protected void init(AttributeSet attrs) { mUseRtl = a.getBoolean(attr, false); } } -Log.v(TAG, " >>>>>>>>>>> col = "+mColumns); + Log.v(TAG, " >>>>>>>>>>> col = " + mColumns); initVariables(); a.recycle(); } @@ -260,7 +260,7 @@ private boolean generateGrid(boolean isUpdate) { mNextAvailableIndex = 0; boolean isSuccess = true; - createGuidelines(mRows, mColumns, isUpdate); + buildBoxes(isUpdate); if (mStrSkips != null && !mStrSkips.trim().isEmpty()) { HashMap> mSkipMap = parseSpans(mStrSkips); @@ -290,9 +290,6 @@ private void initVariables() { for (boolean[] row: mPositionMatrix) { Arrays.fill(row, true); } - - mHorizontalGuideLines = new Guideline[mRows + 1]; - mVerticalGuideLines = new Guideline[mColumns + 1]; } /** @@ -319,71 +316,7 @@ private float[] parseWeights(int size, String str) { } /** - * 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 - * @param isUpdate whether to update existing guidelines (true) or create new ones (false) - */ - private void createGuidelines(int rows, int columns, boolean isUpdate) { - float[] rowWeights = parseWeights(rows, mStrRowWeights); - float[] columnWeights = parseWeights(columns, mStrColumnWeights); - - float[] horizontalPositions = getLinePositions(0, 1, - rows + 1, rowWeights); - float[] verticalPositions = getLinePositions(0, 1, - columns + 1, columnWeights); - - for (int i = 0; i < mHorizontalGuideLines.length; i++) { - if (isUpdate) { - updateGuideLinePosition(mHorizontalGuideLines[i], horizontalPositions[i]); - continue; - } - - mHorizontalGuideLines[i] = getNewGuideline(myContext, - ConstraintLayout.LayoutParams.HORIZONTAL, horizontalPositions[i]); - mContainer.addView(mHorizontalGuideLines[i]); - } - for (int i = 0; i < mVerticalGuideLines.length; i++) { - if (isUpdate) { - updateGuideLinePosition(mVerticalGuideLines[i], verticalPositions[i]); - continue; - } - - mVerticalGuideLines[i] = getNewGuideline(myContext, - ConstraintLayout.LayoutParams.VERTICAL, verticalPositions[i]); - mContainer.addView(mVerticalGuideLines[i]); - } - } - - /** - * get a new Guideline based on the specified orientation and position - * @param context the context - * @param orientation orientation of a Guideline - * @param position position of a Guideline - * @return a Guideline - */ - private Guideline getNewGuideline(Context context, int orientation, float position) { - Guideline guideline = new Guideline(context); - guideline.setId(ViewCompat.generateViewId()); - ConstraintLayout.LayoutParams lp = - new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.WRAP_CONTENT, - ConstraintLayout.LayoutParams.WRAP_CONTENT); - lp.orientation = orientation; - lp.guidePercent = position; - guideline.setLayoutParams(lp); - - return guideline; - } - - private void updateGuideLinePosition(Guideline guideline, float position) { - ConstraintLayout.LayoutParams params = - (ConstraintLayout.LayoutParams) guideline.getLayoutParams(); - params.guidePercent = position; - guideline.setLayoutParams(params); - } - - /** - * Connect the view to the corresponding guidelines based on the input params + * 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 column column position to place the view @@ -392,23 +325,30 @@ private void connectView(int viewId, int row, int column, int rowSpan, int colum float horizontalGaps, float verticalGaps) { // @TODO handle RTL + int leftSide = column == 0 ? ConstraintSet.START : ConstraintSet.END; + int topSide = row == 0 ? ConstraintSet.TOP : ConstraintSet.BOTTOM; + int rightSide = column + columnSpan == mColumns ? ConstraintSet.END : ConstraintSet.START; + int bottomSide = row + rowSpan == mRows ? ConstraintSet.BOTTOM : ConstraintSet.TOP; + // connect Start of the view mConstraintSet.connect(viewId, ConstraintSet.START, - mVerticalGuideLines[column].getId(), ConstraintSet.END,(int) horizontalGaps); + mAnchorIds[column], leftSide, (int) horizontalGaps / 2); // connect Top of the view mConstraintSet.connect(viewId, ConstraintSet.TOP, - mHorizontalGuideLines[row].getId(), ConstraintSet.BOTTOM, (int) verticalGaps); + mAnchorIds[row], topSide, (int) verticalGaps / 2); // connect End of the view + int rightIndex = rightSide + == ConstraintSet.END ? mAnchorIds.length - 1 : column + columnSpan; mConstraintSet.connect(viewId, ConstraintSet.END, - mVerticalGuideLines[column + columnSpan].getId(), - ConstraintSet.START, (int) horizontalGaps); + mAnchorIds[rightIndex], rightSide, (int) horizontalGaps / 2); // connect Bottom of the view + int bottomIndex = bottomSide + == ConstraintSet.BOTTOM ? mAnchorIds.length - 1 : row + rowSpan; mConstraintSet.connect(viewId, ConstraintSet.BOTTOM, - mHorizontalGuideLines[row + rowSpan].getId(), - ConstraintSet.TOP,(int) verticalGaps); + mAnchorIds[bottomIndex], bottomSide, (int) verticalGaps / 2); } /** @@ -575,7 +515,7 @@ private boolean handleSkips(HashMap> skipsMap) { * @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 esle false + * @return true if we could properly invalidate the positions else false */ private boolean invalidatePositions(int startRow, int startColumn, int rowSpan, int columnSpan) { @@ -593,7 +533,7 @@ private boolean invalidatePositions(int startRow, int startColumn, } /** - * Generate line positions (for the Guideline positioning) + * Generate line positions (for the viewBoxes 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 @@ -621,6 +561,104 @@ private float[] getLinePositions(float min, float max, int numPositions, float[] return positions; } + /** + * Visualize the boxViews that are used to constraint widgets. + * @param canvas canvas to visualize the boxViews + */ + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + // Visualize the viewBoxes if isInEditMode() is true +// if (!isInEditMode()) { +// return; +// } + mPaint.setColor(Color.RED); + mPaint.setStyle(Paint.Style.FILL); + int myTop = getTop(); + int myLeft = getLeft(); + int myBottom = getBottom(); + int myRight = getRight(); + for (int i = 0; i < mBoxViews.length; i++) { + View box = mBoxViews[i]; + int l = box.getLeft() - myLeft; + int t = box.getTop() - myTop; + int r = box.getRight() - myLeft; + int b = box.getBottom() - myTop; + canvas.drawRect(l, 0, r, myBottom - myTop, mPaint); + canvas.drawRect(0, t, myRight - myLeft, b, mPaint); + } + } + + /** + * create boxViews for constraining widgets + * @param isUpdate whether to update existing boxViews (true) or create new ones (false) + */ + private void buildBoxes(boolean isUpdate) { + float[] rowWeights = parseWeights(mRows, mStrRowWeights); + float[] columnWeights = parseWeights(mColumns, mStrColumnWeights); + + float[] verticalPositions = getLinePositions(0, 1, + mRows + 1, rowWeights); + float[] horizontalPositions = getLinePositions(0, 1, + mColumns + 1, columnWeights); + + // if the attr update dosen't invovle rows or columns, we only need to + // update the positions of the boxVies. + if (isUpdate) { + for (int i = 0; i < mBoxViews.length; i++) { + int row = Math.min(mRows - 2, i); + int col = Math.min(mColumns - 2, i); + updateBoxPosition(mBoxViews[i], horizontalPositions[col + 1], + verticalPositions[row + 1]); + } + return; + } + + int boxCount = Math.max(mRows - 1, mColumns - 1); + mAnchorIds = new int[boxCount + 2]; + int gridId = getId(); + mAnchorIds[0] = gridId; + mAnchorIds[mAnchorIds.length - 1] = gridId; + + if (mBoxViews == null || !isUpdate) { + mBoxViews = new View[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()); + } + ConstraintLayout.LayoutParams params = + new ConstraintLayout.LayoutParams(1, 1); + int row = Math.min(mRows - 2, i); + int col = Math.min(mColumns - 2, i); + + params.leftToLeft = gridId; + params.topToTop = gridId; + params.bottomToBottom = gridId; + params.rightToRight = gridId; + params.horizontalBias = horizontalPositions[col + 1]; + params.verticalBias = verticalPositions[row + 1]; + mContainer.addView(mBoxViews[i], params); + mAnchorIds[i + 1] = mBoxViews[i].getId(); + } + } + } + + /** + * Update the horizontal and vertical postion of a box view + * @param box box View to be updated + * @param horizontalPosition horizontal position + * @param verticalPosition vertical position + */ + private void updateBoxPosition(View box, + float horizontalPosition, float verticalPosition) { + ConstraintLayout.LayoutParams params = + (ConstraintLayout.LayoutParams) box.getLayoutParams(); + params.horizontalBias = horizontalPosition; + params.verticalBias = verticalPosition; + box.setLayoutParams(params); + } + /** * get the value of rows * @return the value of rows @@ -707,7 +745,6 @@ public boolean setOrientation(int orientation) { generateGrid(true); invalidate(); return true; - } /** diff --git a/projects/GridExperiments/app/src/main/java/android/support/constraint/app/MainActivity.java b/projects/GridExperiments/app/src/main/java/android/support/constraint/app/MainActivity.java index 79545f016..8cb907019 100644 --- a/projects/GridExperiments/app/src/main/java/android/support/constraint/app/MainActivity.java +++ b/projects/GridExperiments/app/src/main/java/android/support/constraint/app/MainActivity.java @@ -87,16 +87,18 @@ protected void onCreate(Bundle savedInstanceState) { if (layout == R.layout.calendar) { fillCal(0); } -// mGrid = findViewById(R.id.grid); -// mGrid.setRows(mSizes[0]); -// mGrid.setColumns(mSizes[0]); -// mGrid.setRowWeights(mThreeWeights[0]); -// mGrid.setColumnWeights(mThreeWeights[0]); -// mGrid.setSkips(mSkips[0]); -// mGrid.setSpans(mSpans[0]); -// mGrid.setVerticalGaps(mGaps[0]); -// mGrid.setHorizontalGaps(mGaps[0]); -// mGrid.setOrientation(mOrientation); + if (layout == R.layout.responsive) { + mGrid = findViewById(R.id.grid); + mGrid.setRows(mSizes[0]); + mGrid.setColumns(mSizes[0]); + mGrid.setRowWeights(mThreeWeights[0]); + mGrid.setColumnWeights(mThreeWeights[0]); + mGrid.setSkips(mSkips[0]); + mGrid.setSpans(mSpans[0]); + mGrid.setVerticalGaps(mGaps[0]); + mGrid.setHorizontalGaps(mGaps[0]); + mGrid.setOrientation(mOrientation); + } } int mMonth = 0; public void nextPrev(View v) { From dc389969d16c5f5ea1d7370b41d9e1c193c4b3ee Mon Sep 17 00:00:00 2001 From: "Jian-Syuan (Shane) Wong" Date: Wed, 1 Jun 2022 10:35:58 -0700 Subject: [PATCH 2/5] do not use view margin for vGaps and hGaps --- .../constraintlayout/helper/widget/Grid.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) 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 456214c70..91adeeb8b 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 @@ -321,8 +321,7 @@ private float[] parseWeights(int size, String str) { * @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, - float horizontalGaps, float verticalGaps) { + private void connectView(int viewId, int row, int column, int rowSpan, int columnSpan) { // @TODO handle RTL int leftSide = column == 0 ? ConstraintSet.START : ConstraintSet.END; @@ -331,24 +330,22 @@ private void connectView(int viewId, int row, int column, int rowSpan, int colum int bottomSide = row + rowSpan == mRows ? ConstraintSet.BOTTOM : ConstraintSet.TOP; // connect Start of the view - mConstraintSet.connect(viewId, ConstraintSet.START, - mAnchorIds[column], leftSide, (int) horizontalGaps / 2); + mConstraintSet.connect(viewId, ConstraintSet.START, mAnchorIds[column], leftSide); // connect Top of the view - mConstraintSet.connect(viewId, ConstraintSet.TOP, - mAnchorIds[row], topSide, (int) verticalGaps / 2); + mConstraintSet.connect(viewId, ConstraintSet.TOP, mAnchorIds[row], topSide); // connect End of the view int rightIndex = rightSide == ConstraintSet.END ? mAnchorIds.length - 1 : column + columnSpan; mConstraintSet.connect(viewId, ConstraintSet.END, - mAnchorIds[rightIndex], rightSide, (int) horizontalGaps / 2); + mAnchorIds[rightIndex], rightSide); // connect Bottom of the view int bottomIndex = bottomSide == ConstraintSet.BOTTOM ? mAnchorIds.length - 1 : row + rowSpan; mConstraintSet.connect(viewId, ConstraintSet.BOTTOM, - mAnchorIds[bottomIndex], bottomSide, (int) verticalGaps / 2); + mAnchorIds[bottomIndex], bottomSide); } /** @@ -370,8 +367,7 @@ private boolean arrangeWidgets() { // no more available position. return false; } - connectView(mIds[i], position.first, position.second, - 1, 1, mHorizontalGaps, mVerticalGaps); + connectView(mIds[i], position.first, position.second, 1, 1); } return true; } @@ -484,8 +480,7 @@ private boolean handleSpans(int[] mId, HashMap> return false; } connectView(mId[mIdIndex], startPosition.first, startPosition.second, - entry.getValue().first, entry.getValue().second, - mHorizontalGaps, mVerticalGaps); + entry.getValue().first, entry.getValue().second); mSpanIds.add(mId[mIdIndex]); mIdIndex++; } @@ -569,9 +564,9 @@ private float[] getLinePositions(float min, float max, int numPositions, float[] public void onDraw(Canvas canvas) { super.onDraw(canvas); // Visualize the viewBoxes if isInEditMode() is true -// if (!isInEditMode()) { -// return; -// } + if (!isInEditMode()) { + return; + } mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); int myTop = getTop(); @@ -619,6 +614,9 @@ private void buildBoxes(boolean isUpdate) { int gridId = getId(); mAnchorIds[0] = gridId; mAnchorIds[mAnchorIds.length - 1] = gridId; + int boxWidth = mHorizontalGaps == 0 ? 1 : (int) mHorizontalGaps; + int boxHeight = mVerticalGaps == 0 ? 1: (int) mVerticalGaps; + if (mBoxViews == null || !isUpdate) { mBoxViews = new View[boxCount]; @@ -628,7 +626,7 @@ private void buildBoxes(boolean isUpdate) { mBoxViews[i].setId(View.generateViewId()); } ConstraintLayout.LayoutParams params = - new ConstraintLayout.LayoutParams(1, 1); + new ConstraintLayout.LayoutParams(boxWidth, boxHeight); int row = Math.min(mRows - 2, i); int col = Math.min(mColumns - 2, i); @@ -638,6 +636,8 @@ private void buildBoxes(boolean isUpdate) { params.rightToRight = gridId; params.horizontalBias = horizontalPositions[col + 1]; params.verticalBias = verticalPositions[row + 1]; + params.leftMargin = mHorizontalGaps == 0 ? 0 : -(int) mHorizontalGaps; + params.topMargin = mHorizontalGaps == 0 ? 0 : -(int) mVerticalGaps; mContainer.addView(mBoxViews[i], params); mAnchorIds[i + 1] = mBoxViews[i].getId(); } From c77db90d5a2f472e9f23e9b815ed1f2fefd3f1bd Mon Sep 17 00:00:00 2001 From: "Jian-Syuan (Shane) Wong" Date: Tue, 7 Jun 2022 10:22:20 -0700 Subject: [PATCH 3/5] chain the boxViews --- .../constraintlayout/helper/widget/Grid.java | 195 ++++++++---------- .../app/src/main/res/layout/activity_main.xml | 58 +++++- 2 files changed, 145 insertions(+), 108 deletions(-) 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 91adeeb8b..560756180 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 @@ -168,9 +168,9 @@ public class Grid extends VirtualLayout { Set mSpanIds = new HashSet<>(); /** - * Ids boxViews where to specify constraints + * Ids of the boxViews */ - private int[] mAnchorIds; + private int[] mBoxViewIds; public Grid(Context context) { super(context); @@ -260,7 +260,7 @@ private boolean generateGrid(boolean isUpdate) { mNextAvailableIndex = 0; boolean isSuccess = true; - buildBoxes(isUpdate); + buildBoxes(); if (mStrSkips != null && !mStrSkips.trim().isEmpty()) { HashMap> mSkipMap = parseSpans(mStrSkips); @@ -324,28 +324,18 @@ private float[] parseWeights(int size, String str) { private void connectView(int viewId, int row, int column, int rowSpan, int columnSpan) { // @TODO handle RTL - int leftSide = column == 0 ? ConstraintSet.START : ConstraintSet.END; - int topSide = row == 0 ? ConstraintSet.TOP : ConstraintSet.BOTTOM; - int rightSide = column + columnSpan == mColumns ? ConstraintSet.END : ConstraintSet.START; - int bottomSide = row + rowSpan == mRows ? ConstraintSet.BOTTOM : ConstraintSet.TOP; - // connect Start of the view - mConstraintSet.connect(viewId, ConstraintSet.START, mAnchorIds[column], leftSide); + mConstraintSet.connect(viewId, ConstraintSet.LEFT, mBoxViewIds[column], ConstraintSet.LEFT); // connect Top of the view - mConstraintSet.connect(viewId, ConstraintSet.TOP, mAnchorIds[row], topSide); + mConstraintSet.connect(viewId, ConstraintSet.TOP, mBoxViewIds[row], ConstraintSet.TOP); - // connect End of the view - int rightIndex = rightSide - == ConstraintSet.END ? mAnchorIds.length - 1 : column + columnSpan; - mConstraintSet.connect(viewId, ConstraintSet.END, - mAnchorIds[rightIndex], rightSide); + mConstraintSet.connect(viewId, ConstraintSet.RIGHT, + mBoxViewIds[column + columnSpan - 1], ConstraintSet.RIGHT); // connect Bottom of the view - int bottomIndex = bottomSide - == ConstraintSet.BOTTOM ? mAnchorIds.length - 1 : row + rowSpan; mConstraintSet.connect(viewId, ConstraintSet.BOTTOM, - mAnchorIds[bottomIndex], bottomSide); + mBoxViewIds[row + rowSpan - 1], ConstraintSet.BOTTOM); } /** @@ -527,35 +517,6 @@ private boolean invalidatePositions(int startRow, int startColumn, return true; } - /** - * Generate line positions (for the viewBoxes 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 float[] getLinePositions(float min, float max, int numPositions, float[] weights) { - if (weights != null && numPositions - 1 != weights.length) { - return null; - } - - float[] positions = new float[numPositions]; - int weightSum = 0; - for (int i = 0; i < numPositions - 1; i++) { - weightSum += weights != null ? weights[i] : 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 positions; - } - /** * Visualize the boxViews that are used to constraint widgets. * @param canvas canvas to visualize the boxViews @@ -568,7 +529,7 @@ public void onDraw(Canvas canvas) { return; } mPaint.setColor(Color.RED); - mPaint.setStyle(Paint.Style.FILL); + mPaint.setStyle(Paint.Style.STROKE); int myTop = getTop(); int myLeft = getLeft(); int myBottom = getBottom(); @@ -585,78 +546,100 @@ public void onDraw(Canvas canvas) { } /** - * create boxViews for constraining widgets - * @param isUpdate whether to update existing boxViews (true) or create new ones (false) + * Set chain between boxView horzontally */ - private void buildBoxes(boolean isUpdate) { - float[] rowWeights = parseWeights(mRows, mStrRowWeights); + private void setBoxViewHorizontalChains() { + int gridId = getId(); + int maxVal = Math.max(mRows, mColumns); + int minVal = Math.min(mRows, mColumns); float[] columnWeights = parseWeights(mColumns, mStrColumnWeights); - float[] verticalPositions = getLinePositions(0, 1, - mRows + 1, rowWeights); - float[] horizontalPositions = getLinePositions(0, 1, - mColumns + 1, columnWeights); - - // if the attr update dosen't invovle rows or columns, we only need to - // update the positions of the boxVies. - if (isUpdate) { - for (int i = 0; i < mBoxViews.length; i++) { - int row = Math.min(mRows - 2, i); - int col = Math.min(mColumns - 2, i); - updateBoxPosition(mBoxViews[i], horizontalPositions[col + 1], - verticalPositions[row + 1]); + // chain all the views on the longer side (either horizontal or vertical) + 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); } return; } - int boxCount = Math.max(mRows - 1, mColumns - 1); - mAnchorIds = new int[boxCount + 2]; + // 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); + } else { + mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.LEFT, + gridId, ConstraintSet.LEFT); + mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.RIGHT, + gridId, ConstraintSet.RIGHT); + } + } + } + + /** + * Set chain between boxView vertically + */ + private void setBoxViewVerticalChains() { int gridId = getId(); - mAnchorIds[0] = gridId; - mAnchorIds[mAnchorIds.length - 1] = gridId; - int boxWidth = mHorizontalGaps == 0 ? 1 : (int) mHorizontalGaps; - int boxHeight = mVerticalGaps == 0 ? 1: (int) mVerticalGaps; - - - if (mBoxViews == null || !isUpdate) { - mBoxViews = new View[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()); - } - ConstraintLayout.LayoutParams params = - new ConstraintLayout.LayoutParams(boxWidth, boxHeight); - int row = Math.min(mRows - 2, i); - int col = Math.min(mColumns - 2, i); - - params.leftToLeft = gridId; - params.topToTop = gridId; - params.bottomToBottom = gridId; - params.rightToRight = gridId; - params.horizontalBias = horizontalPositions[col + 1]; - params.verticalBias = verticalPositions[row + 1]; - params.leftMargin = mHorizontalGaps == 0 ? 0 : -(int) mHorizontalGaps; - params.topMargin = mHorizontalGaps == 0 ? 0 : -(int) mVerticalGaps; - mContainer.addView(mBoxViews[i], params); - mAnchorIds[i + 1] = mBoxViews[i].getId(); + int maxVal = Math.max(mRows, mColumns); + int minVal = Math.min(mRows, mColumns); + float[] rowWeights = parseWeights(mRows, mStrRowWeights); + + // chain all the views on the longer side (either horizontal or vertical) + 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); + } + 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); + } else { + mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.TOP, + gridId, ConstraintSet.TOP); + mConstraintSet.connect(mBoxViewIds[i], ConstraintSet.BOTTOM, + gridId, ConstraintSet.BOTTOM); } } } /** - * Update the horizontal and vertical postion of a box view - * @param box box View to be updated - * @param horizontalPosition horizontal position - * @param verticalPosition vertical position + * create boxViews for constraining widgets */ - private void updateBoxPosition(View box, - float horizontalPosition, float verticalPosition) { - ConstraintLayout.LayoutParams params = - (ConstraintLayout.LayoutParams) box.getLayoutParams(); - params.horizontalBias = horizontalPosition; - params.verticalBias = verticalPosition; - box.setLayoutParams(params); + 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()); + } + ConstraintLayout.LayoutParams params = + new ConstraintLayout.LayoutParams(0, 0); + + mContainer.addView(mBoxViews[i], params); + mBoxViewIds[i] = mBoxViews[i].getId(); + } + + setBoxViewVerticalChains(); + setBoxViewHorizontalChains(); } /** 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 5b636c47b..884a4a3bf 100644 --- a/projects/GridExperiments/app/src/main/res/layout/activity_main.xml +++ b/projects/GridExperiments/app/src/main/res/layout/activity_main.xml @@ -9,15 +9,29 @@ android:id="@+id/grid" android:layout_width="match_parent" android:layout_height="match_parent" - app:constraint_referenced_ids="btn0,btn1,btn2,btn3" + android:layout_margin="30dp" + app:constraint_referenced_ids="innerGrid,btn0,btn1,btn2,btn3" + app:grid_columns="3" + app:grid_rows="3" + app:grid_horizontalGaps="10dp" + app:grid_verticalGaps="10dp" + app:grid_orientation="horizontal" + app:grid_skips="1:1x1,4:1x1,6:1x1" /> + + +