/*
* 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.os.Build;
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());
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;
}
/**
* 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();
}
}