public class

Grid

extends VirtualLayout

 java.lang.Object

↳View

androidx.constraintlayout.widget.ConstraintHelper

androidx.constraintlayout.widget.VirtualLayout

↳androidx.constraintlayout.helper.widget.Grid

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout', version: '2.2.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout
  • version: 2.2.0-beta01

Artifact androidx.constraintlayout:constraintlayout:2.2.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.constraintlayout:constraintlayout com.android.support.constraint:constraint-layout

Overview

A helper class that helps arrange widgets in a grid form

Grid

AttributesDescription
grid_rows Indicates the number of rows will be created for the grid form.
grid_columns 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
grid_skips Enables skip certain positions in the grid and leave them empty
grid_orientation Defines how the associated widgets will be arranged - vertically or horizontally
grid_horizontalGaps Adds margin horizontally between widgets
grid_verticalGaps Adds margin vertically between widgets

Summary

Fields
public static final intHORIZONTAL

public static final intVERTICAL

from ConstraintHelperCHILD_TAG, mCount, mHelperWidget, mIds[], mMap, mReferenceIds, mReferenceTags, mUseViewMeasure, myContext
Constructors
publicGrid(Context context)

publicGrid(Context context, AttributeSet attrs)

publicGrid(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public intgetColumns()

get the value of columns

public java.lang.StringgetColumnWeights()

get the string value of columnWeights

public floatgetHorizontalGaps()

get the value of horizontalGaps

public intgetOrientation()

get the value of orientation

public intgetRows()

get the value of rows

public java.lang.StringgetRowWeights()

get the string value of rowWeights

public java.lang.StringgetSkips()

get the string value of skips

public java.lang.StringgetSpans()

get the string value of spans

public floatgetVerticalGaps()

get the value of verticalGaps

protected voidinit(AttributeSet attrs)

public voidonAttachedToWindow()

public voidonDraw(Canvas canvas)

Visualize the boxViews that are used to constraint widgets.

public voidsetColumns(int columns)

set new columns value and also invoke initVariables and invalidate

public voidsetColumnWeights(java.lang.String columnWeights)

set new columnWeights value and also invoke invalidate

public voidsetHorizontalGaps(float horizontalGaps)

set new horizontalGaps value and also invoke invalidate

public voidsetOrientation(int orientation)

set new orientation value and also invoke invalidate

public voidsetRows(int rows)

set new rows value and also invoke initVariables and invalidate

public voidsetRowWeights(java.lang.String rowWeights)

set new rowWeights value and also invoke invalidate

public voidsetSkips(java.lang.String skips)

set new skips value and also invoke invalidate

public voidsetSpans(java.lang.CharSequence spans)

set new spans value and also invoke invalidate

public voidsetVerticalGaps(float verticalGaps)

set new verticalGaps value and also invoke invalidate

from VirtualLayoutapplyLayoutFeaturesInConstraintSet, onMeasure, setElevation, setVisibility
from ConstraintHelperaddView, applyHelperParams, applyLayoutFeatures, applyLayoutFeatures, containsId, getReferencedIds, getViews, indexFromId, isChildOfHelper, loadParameters, onMeasure, removeView, resolveRtl, setIds, setReferencedIds, setReferenceTags, setTag, updatePostConstraints, updatePostLayout, updatePostMeasure, updatePreDraw, updatePreLayout, updatePreLayout, validateParams
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int VERTICAL

public static final int HORIZONTAL

Constructors

public Grid(Context context)

public Grid(Context context, AttributeSet attrs)

public Grid(Context context, AttributeSet attrs, int defStyleAttr)

Methods

protected void init(AttributeSet attrs)

public void onAttachedToWindow()

public void onDraw(Canvas canvas)

Visualize the boxViews that are used to constraint widgets.

Parameters:

canvas: canvas to visualize the boxViews

public int getRows()

get the value of rows

Returns:

the value of rows

public void setRows(int rows)

set new rows value and also invoke initVariables and invalidate

Parameters:

rows: new rows value

public int getColumns()

get the value of columns

Returns:

the value of columns

public void setColumns(int columns)

set new columns value and also invoke initVariables and invalidate

Parameters:

columns: new rows value

public int getOrientation()

get the value of orientation

Returns:

the value of orientation

public void setOrientation(int orientation)

set new orientation value and also invoke invalidate

Parameters:

orientation: new orientation value

public java.lang.String getSpans()

get the string value of spans

Returns:

the string value of spans

public void setSpans(java.lang.CharSequence spans)

set new spans value and also invoke invalidate

Parameters:

spans: new spans value

public java.lang.String getSkips()

get the string value of skips

Returns:

the string value of skips

public void setSkips(java.lang.String skips)

set new skips value and also invoke invalidate

Parameters:

skips: new spans 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 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

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.helper.widget;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.constraintlayout.widget.R;
import androidx.constraintlayout.widget.VirtualLayout;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * A helper class that helps arrange widgets in a grid form
 *
 * <h2>Grid</h2>
 * <table summary="Grid attributes">
 *   <tr>
 *     <th>Attributes</th><th>Description</th>
 *   </tr>
 *   <tr>
 *     <td>grid_rows</td>
 *     <td>Indicates the number of rows will be created for the grid form.</td>
 *   </tr>
 *   <tr>
 *     <td>grid_columns</td>
 *     <td>Indicates the number of columns will be created for the grid form.</td>
 *   </tr>
 *   <tr>
 *     <td>grid_rowWeights</td>
 *     <td>Specifies the weight of each row in the grid form (default value is 1).</td>
 *   </tr>
 *   <tr>
 *     <td>grid_columnWeights</td>
 *     <td>Specifies the weight of each column in the grid form (default value is 1).</td>
 *   </tr>
 *   <tr>
 *     <td>grid_spans</td>
 *     <td>Offers the capability to span a widget across multiple rows and columns</td>
 *   </tr>
 *   <tr>
 *     <td>grid_skips</td>
 *     <td>Enables skip certain positions in the grid and leave them empty</td>
 *   </tr>
 *   <tr>
 *     <td>grid_orientation</td>
 *     <td>Defines how the associated widgets will be arranged - vertically or horizontally</td>
 *   </tr>
 *   <tr>
 *     <td>grid_horizontalGaps</td>
 *     <td>Adds margin horizontally between widgets</td>
 *   </tr>
 *   <tr>
 *      <td>grid_verticalGaps</td>
 *     <td>Adds margin vertically between widgets</td>
 *   </tr>
 * </table>
 */
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 View[] mBoxViews;
    ConstraintLayout mContainer;

    /**
     * number of rows of the grid
     */
    private int mRows;

    /**
     * number of rows set by the XML 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;

    /**
     * string format of the input Spans
     */
    private String mStrSpans;

    /**
     * string format of the input Skips
     */
    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
     */
    private float mHorizontalGaps;

    /**
     * Vertical gaps in Dp
     */
    private float mVerticalGaps;

    /**
     * orientation of the view arrangement - vertical or horizontal
     */
    private int mOrientation;

    /**
     * Indicates what is the next available position to place an widget
     */
    private int mNextAvailableIndex = 0;

    /**
     * Indicates whether the input attributes need to be validated
     */
    private boolean mValidateInputs;

    /**
     * Indicates whether to use RTL layout direction
     */
    @SuppressWarnings("unused")
    private boolean mUseRtl;

    /**
     * A integer matrix that tracks the positions that are occupied by skips and spans
     * true: available position
     * false: non-available position
     */
    private boolean[][] mPositionMatrix;

    /**
     * Store the view ids of handled spans
     */
    Set<Integer> mSpanIds = new HashSet<>();

    /**
     * Ids of the boxViews
     */
    private int[] mBoxViewIds;

    public Grid(Context context) {
        super(context);
    }

    public Grid(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public Grid(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void init(AttributeSet attrs) {
        super.init(attrs);
        mUseViewMeasure = true;

        // Parse the relevant attributes from layout xml
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs,
                    R.styleable.Grid);
            final int n = a.getIndexCount();

            for (int i = 0; i < n; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.Grid_grid_rows) {
                    mRowsSet = a.getInteger(attr, 0);
                } else if (attr == R.styleable.Grid_grid_columns) {
                    mColumnsSet = a.getInteger(attr, 0);
                } else if (attr == R.styleable.Grid_grid_spans) {
                    mStrSpans = a.getString(attr);
                } else if (attr == R.styleable.Grid_grid_skips) {
                    mStrSkips = a.getString(attr);
                } 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.getInt(attr, 0);
                } else if (attr == R.styleable.Grid_grid_horizontalGaps) {
                    mHorizontalGaps = a.getDimension(attr, 0);
                } else if (attr == R.styleable.Grid_grid_verticalGaps) {
                    mVerticalGaps = a.getDimension(attr, 0);
                } else if (attr == R.styleable.Grid_grid_validateInputs) {
                    // @TODO handle validation
                    mValidateInputs = a.getBoolean(attr, false);
                } else if (attr == R.styleable.Grid_grid_useRtl) {
                    // @TODO handle RTL
                    mUseRtl = a.getBoolean(attr, false);
                }
            }

            updateActualRowsAndColumns();
            initVariables();
            a.recycle();
        }
    }

    /**
     * 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 = (mCount + mColumns - 1) / mColumnsSet; // round up
            } else if (mRowsSet > 0) {
                mRows = mRowsSet;
                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;
            }
        } else {
            mRows = mRowsSet;
            mColumns = mColumnsSet;
        }
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        mContainer = (ConstraintLayout) getParent();

        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 || mRows < 1 || mColumns < 1) {
            return false;
        }

        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;
        boolean isSuccess = true;

        buildBoxes();

        if (mStrSkips != null && !mStrSkips.trim().isEmpty()) {
            int[][] mSkips = parseSpans(mStrSkips);
            if (mSkips != null) {
                isSuccess &= handleSkips(mSkips);
            }
        }

        if (mStrSpans != null && !mStrSpans.trim().isEmpty()) {
            int[][] mSpans = parseSpans(mStrSpans);
            if (mSpans != null) {
                isSuccess &= handleSpans(mIds, mSpans);
            }
        }
        isSuccess &= arrangeWidgets();
        return isSuccess || !mValidateInputs;
    }

    /**
     * Initialize the relevant variables
     */
    private void initVariables() {
        mPositionMatrix = new boolean[mRows][mColumns];
        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
     */
    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;
    }

    private ConstraintLayout.LayoutParams params(View v) {
        return (ConstraintLayout.LayoutParams) v.getLayoutParams();
    }

    /**
     * Connect the view to the corresponding viewBoxes based on the input params
     *
     * @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(View view, int row, int column, int rowSpan, int columnSpan) {
        ConstraintLayout.LayoutParams params = params(view);
        // @TODO handle RTL
        // Connect the 4 sides
        params.leftToLeft = mBoxViewIds[column];
        params.topToTop = mBoxViewIds[row];
        params.rightToRight = mBoxViewIds[column + columnSpan - 1];
        params.bottomToBottom = mBoxViewIds[row + rowSpan - 1];
        view.setLayoutParams(params);
    }

    /**
     * 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])) {
                // skip the viewId 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 false;
            }

            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;
        } else {
            return index / mColumns;
        }
    }

    /**
     * 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.
     */
    private int getColByIndex(int index) {
        if (mOrientation == 1) {
            return index / mRows;
        } else {
            return index % mColumns;
        }
    }

    /**
     * Get the next available position for widget arrangement.
     *
     * @return int[] -> [row, column]
     */
    private int getNextPosition() {
        //  int[] position = new int[] {0, 0};
        int position = 0;
        boolean positionFound = false;

        while (!positionFound) {
            if (mNextAvailableIndex >= mRows * mColumns) {
                return -1;
            }

            // position = getPositionByIndex(mNextAvailableIndex);
            position = mNextAvailableIndex;
            int row = getRowByIndex(mNextAvailableIndex);
            int col = getColByIndex(mNextAvailableIndex);
            if (mPositionMatrix[row][col]) {
                mPositionMatrix[row][col] = false;
                positionFound = true;
            }

            mNextAvailableIndex++;
        }
        return position;
    }

    /**
     * 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(@SuppressWarnings("unused") 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
     */
    private boolean isWeightsValid(@SuppressWarnings("unused") String str) {
        // TODO: check string has a valid format.
        return true;
    }

    /**
     * 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
     * @return a int matrix that contains skip information.
     */
    private int[][] parseSpans(String str) {
        if (!isSpansValid(str)) {
            return null;
        }

        String[] spans = str.split(",");
        int[][] spanMatrix = new int[spans.length][3];

        String[] indexAndSpan;
        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]);
            spanMatrix[i][1] = Integer.parseInt(rowAndCol[0]);
            spanMatrix[i][2] = Integer.parseInt(rowAndCol[1]);
        }
        return spanMatrix;
    }

    /**
     * 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]);
            if (!invalidatePositions(row, col,
                    spansMatrix[i][1], spansMatrix[i][2])) {
                return false;
            }

            connectView(views[i], row, col,
                    spansMatrix[i][1], spansMatrix[i][2]);

            mSpanIds.add(mId[i]);
        }
        return true;
    }

    /**
     * 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++) {
            int row = getRowByIndex(skipsMatrix[i][0]);
            int col = getColByIndex(skipsMatrix[i][0]);
            if (!invalidatePositions(row, col,
                    skipsMatrix[i][1], skipsMatrix[i][2])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 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;
    }

    /**
     * Visualize the boxViews that are used to constraint widgets.
     *
     * @param canvas canvas to visualize the boxViews
     */
    @Override
    public void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        // Visualize the viewBoxes if isInEditMode() is true
        if (!isInEditMode()) {
            return;
        }
        @SuppressLint("DrawAllocation")
        Paint paint = new Paint(); // used only during design time
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        int myTop = getTop();
        int myLeft = getLeft();
        int myBottom = getBottom();
        int myRight = getRight();
        for (View box : mBoxViews) {
            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, paint);
            canvas.drawRect(0, t, myRight - myLeft, b, paint);
        }
    }

    /**
     * Set chain between boxView horizontally
     */
    private void setBoxViewHorizontalChains() {
        int gridId = getId();
        int maxVal = Math.max(mRows, mColumns);

        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) {
            clearHParams(mBoxViews[0]);
            params.leftToLeft = gridId;
            params.rightToRight = gridId;
            mBoxViews[0].setLayoutParams(params);
            return;
        }


        //  chains are grid <- box <-> box <-> box -> grid

        for (int i = 0; i < mColumns; i++) {
            params = params(mBoxViews[i]);
            clearHParams(mBoxViews[i]);
            if (columnWeights != null) {
                params.horizontalWeight = columnWeights[i];
            }
            if (i > 0) {
                params.leftToRight = mBoxViewIds[i - 1];
            } else {
                params.leftToLeft = gridId;
            }
            if (i < mColumns - 1) {
                params.rightToLeft = mBoxViewIds[i + 1];
            } else {
                params.rightToRight = gridId;
            }
            if (i > 0) {
                params.leftMargin = (int) mHorizontalGaps;
            }
            mBoxViews[i].setLayoutParams(params);
        }
        // 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]);
            clearHParams(mBoxViews[i]);
            params.leftToLeft = gridId;
            params.rightToRight = gridId;
            mBoxViews[i].setLayoutParams(params);
        }
    }

    /**
     * Set chain between boxView vertically
     */
    private void setBoxViewVerticalChains() {
        int gridId = getId();
        int maxVal = Math.max(mRows, mColumns);

        float[] rowWeights = parseWeights(mRows, mStrRowWeights);
        ConstraintLayout.LayoutParams params;
        // chain all the views on the longer side (either horizontal or vertical)
        if (mRows == 1) {
            params = params(mBoxViews[0]);
            clearVParams(mBoxViews[0]);
            params.topToTop = gridId;
            params.bottomToBottom = gridId;
            mBoxViews[0].setLayoutParams(params);
            return;
        }
        // chains are constrained like this: grid <- box <-> box <-> box -> grid
        for (int i = 0; i < mRows; i++) {
            params = params(mBoxViews[i]);
            clearVParams(mBoxViews[i]);
            if (rowWeights != null) {
                params.verticalWeight = rowWeights[i];
            }
            if (i > 0) {
                params.topToBottom = mBoxViewIds[i - 1];
            } else {
                params.topToTop = gridId;
            }
            if (i < mRows - 1) {
                params.bottomToTop = mBoxViewIds[i + 1];
            } else {
                params.bottomToBottom = gridId;
            }
            if (i > 0) {
                params.topMargin = (int) mHorizontalGaps;
            }
            mBoxViews[i].setLayoutParams(params);
        }

        // 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]);
            clearVParams(mBoxViews[i]);
            params.topToTop = gridId;
            params.bottomToBottom = gridId;
            mBoxViews[i].setLayoutParams(params);
        }
    }

    /**
     * Create a new boxView
     * @return boxView
     */
    private View makeNewView() {
        View v = new View(getContext());
        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;
    }

    /**
     * Clear vertical related layout params
     * @param view view that has the layout params to be cleared
     */
    private void clearVParams(View view) {
        ConstraintLayout.LayoutParams params = params(view);

        params.verticalWeight  = ConstraintSet.UNSET;
        params.topToBottom = ConstraintSet.UNSET;
        params.topToTop = ConstraintSet.UNSET;
        params.bottomToTop = ConstraintSet.UNSET;
        params.bottomToBottom  = ConstraintSet.UNSET;
        params.topMargin = ConstraintSet.UNSET;

        view.setLayoutParams(params);
    }

    /**
     * Clear horizontal related layout params
     * @param view view that has the layout params to be cleared
     */
    private void clearHParams(View view) {
        ConstraintLayout.LayoutParams params = params(view);

        params.horizontalWeight = ConstraintSet.UNSET;
        params.leftToRight = ConstraintSet.UNSET;
        params.leftToLeft = ConstraintSet.UNSET;
        params.rightToLeft  = ConstraintSet.UNSET;
        params.rightToRight = ConstraintSet.UNSET;
        params.leftMargin  = ConstraintSet.UNSET;

        view.setLayoutParams(params);
    }

    /**
     * create boxViews for constraining widgets
     */
    private void buildBoxes() {
        int boxCount = Math.max(mRows, mColumns);
        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
            }
        } 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;
            }
        }

        mBoxViewIds = new int[boxCount];
        for (int i = 0; i < mBoxViews.length; i++) {
            mBoxViewIds[i] = mBoxViews[i].getId();
        }

        setBoxViewVerticalChains();
        setBoxViewHorizontalChains();
    }

    /**
     * get the value of rows
     *
     * @return the value of rows
     */
    public int getRows() {
        return mRowsSet;
    }

    /**
     * set new rows value and also invoke initVariables and invalidate
     *
     * @param rows new rows value
     */
    public void setRows(int rows) {
        if (rows > mMaxRows) {
            return;
        }

        if (mRowsSet == rows) {
            return;
        }

        mRowsSet = rows;
        updateActualRowsAndColumns();

        initVariables();
        generateGrid(false);
        invalidate();
    }

    /**
     * get the value of columns
     *
     * @return the value of columns
     */
    public int getColumns() {
        return mColumnsSet;
    }

    /**
     * set new columns value and also invoke initVariables and invalidate
     *
     * @param columns new rows value
     */
    public void setColumns(int columns) {
        if (columns > mMaxColumns) {
            return;
        }

        if (mColumnsSet == columns) {
            return;
        }

        mColumnsSet = columns;
        updateActualRowsAndColumns();

        initVariables();
        generateGrid(false);
        invalidate();
    }

    /**
     * get the value of orientation
     *
     * @return the value of orientation
     */
    public int getOrientation() {
        return mOrientation;
    }

    /**
     * set new orientation value and also invoke invalidate
     *
     * @param orientation new orientation value
     */
    public void setOrientation(int orientation) {
        if (!(orientation == HORIZONTAL || orientation == VERTICAL)) {
            return;
        }

        if (mOrientation == orientation) {
            return;
        }

        mOrientation = orientation;
        generateGrid(true);
        invalidate();
    }

    /**
     * get the string value of spans
     *
     * @return the string value of spans
     */
    public String getSpans() {
        return mStrSpans;
    }

    /**
     * set new spans value and also invoke invalidate
     *
     * @param spans new spans value
     */
    public void setSpans(CharSequence spans) {
        if (!isSpansValid(spans)) {
            return;
        }

        if (mStrSpans != null && mStrSpans.contentEquals(spans)) {
            return;
        }

        mStrSpans = spans.toString();
        generateGrid(true);
        invalidate();
    }

    /**
     * get the string value of skips
     *
     * @return the string value of skips
     */
    public String getSkips() {
        return mStrSkips;
    }

    /**
     * set new skips value and also invoke invalidate
     *
     * @param skips new spans value
     */
    public void setSkips(String skips) {
        if (!isSpansValid(skips)) {
            return;
        }

        if (mStrSkips != null && mStrSkips.equals(skips)) {
            return;
        }

        mStrSkips = skips;
        generateGrid(true);
        invalidate();
    }

    /**
     * get the string value of rowWeights
     *
     * @return the string value of rowWeights
     */
    public String getRowWeights() {
        return mStrRowWeights;
    }

    /**
     * set new rowWeights value and also invoke invalidate
     *
     * @param rowWeights new rowWeights value
     */
    public void setRowWeights(String rowWeights) {
        if (!isWeightsValid(rowWeights)) {
            return;
        }

        if (mStrRowWeights != null && mStrRowWeights.equals(rowWeights)) {
            return;
        }

        mStrRowWeights = rowWeights;
        generateGrid(true);
        invalidate();
    }

    /**
     * get the string value of columnWeights
     *
     * @return the string value of columnWeights
     */
    public String getColumnWeights() {
        return mStrColumnWeights;
    }

    /**
     * set new columnWeights value and also invoke invalidate
     *
     * @param columnWeights new columnWeights value
     */
    public void setColumnWeights(String columnWeights) {
        if (!isWeightsValid(columnWeights)) {
            return;
        }

        if (mStrColumnWeights != null && mStrColumnWeights.equals(columnWeights)) {
            return;
        }

        mStrColumnWeights = columnWeights;
        generateGrid(true);
        invalidate();
    }

    /**
     * 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;
        generateGrid(true);
        invalidate();
    }

    /**
     * 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;
        generateGrid(true);
        invalidate();
    }
}