public class

GridEngine

extends java.lang.Object

 java.lang.Object

↳androidx.constraintlayout.core.utils.GridEngine

Gradle dependencies

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

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout-core
  • version: 1.1.0-beta01

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

Overview

GridEngine class contains the main logic of the Grid Helper

Summary

Fields
public static final intHORIZONTAL

public static final intVERTICAL

Constructors
publicGridEngine()

publicGridEngine(int rows, int columns)

publicGridEngine(int rows, int columns, int numWidgets)

Methods
public intbottomOfWidget(int i)

Get the boxView for the widget i to add a constraint on the bottom

public intleftOfWidget(int i)

Get the boxView for the widget i to add a constraint on the left

public intrightOfWidget(int i)

Get the boxView for the widget i to add a constraint on the right

public voidsetColumns(int columns)

set new columns value

public voidsetNumWidgets(int num)

Set new NumWidgets value

public voidsetOrientation(int orientation)

set new orientation value

public voidsetRows(int rows)

set new rows value

public voidsetSkips(java.lang.String skips)

set new skips value

public voidsetSpans(java.lang.CharSequence spans)

set new spans value

public voidsetup()

Set up the Grid engine.

public inttopOfWidget(int i)

Get the boxView for the widget i to add a constraint on the top

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 GridEngine()

public GridEngine(int rows, int columns)

public GridEngine(int rows, int columns, int numWidgets)

Methods

public void setup()

Set up the Grid engine.

public void setSpans(java.lang.CharSequence spans)

set new spans value

Parameters:

spans: new spans value

public void setSkips(java.lang.String skips)

set new skips value

Parameters:

skips: new spans value

public void setOrientation(int orientation)

set new orientation value

Parameters:

orientation: new orientation value

public void setNumWidgets(int num)

Set new NumWidgets value

Parameters:

num: how many widgets to be arranged in Grid

public void setRows(int rows)

set new rows value

Parameters:

rows: new rows value

public void setColumns(int columns)

set new columns value

Parameters:

columns: new rows value

public int leftOfWidget(int i)

Get the boxView for the widget i to add a constraint on the left

Parameters:

i: the widget that has the order as i in the constraint_reference_ids

Returns:

the boxView to add a constraint on the left

public int topOfWidget(int i)

Get the boxView for the widget i to add a constraint on the top

Parameters:

i: the widget that has the order as i in the constraint_reference_ids

Returns:

the boxView to add a constraint on the top

public int rightOfWidget(int i)

Get the boxView for the widget i to add a constraint on the right

Parameters:

i: the widget that has the order as i in the constraint_reference_ids

Returns:

the boxView to add a constraint on the right

public int bottomOfWidget(int i)

Get the boxView for the widget i to add a constraint on the bottom

Parameters:

i: the widget that has the order as i in the constraint_reference_ids

Returns:

the boxView to add a constraint on the bottom

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.core.utils;

import java.util.Arrays;

/**
 * GridEngine class contains the main logic of the Grid Helper
 */
public class GridEngine {

    public static final int VERTICAL = 1;
    public static final int HORIZONTAL = 0;
    private static final int MAX_ROWS = 50; // maximum number of rows can be specified.
    private static final int MAX_COLUMNS = 50; // maximum number of columns can be specified.
    private static final int DEFAULT_SIZE = 3; // default rows and columns.

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

    /**
     * number of rows set by the XML or API
     */
    private int mRowsSet;

    /**
     * How many widgets need to be placed in the Grid
     */
    private int mNumWidgets;

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

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

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

    /**
     * A int matrix that contains the positions where a widget would constraint to at each direction
     * Each row contains 4 values that indicate the position to constraint of a widget.
     * Example row: [left, top, right, bottom]
     */
    private int[][] mConstraintMatrix;

    public GridEngine() {}

    public GridEngine(int rows, int columns) {
        mRowsSet = rows;
        mColumnsSet = columns;
        if (rows > MAX_ROWS) {
            mRowsSet = DEFAULT_SIZE;
        }

        if (columns > MAX_COLUMNS) {
            mColumnsSet = DEFAULT_SIZE;
        }

        updateActualRowsAndColumns();
        initVariables();
    }

    public GridEngine(int rows, int columns, int numWidgets) {
        mRowsSet = rows;
        mColumnsSet = columns;
        mNumWidgets = numWidgets;

        if (rows > MAX_ROWS) {
            mRowsSet = DEFAULT_SIZE;
        }

        if (columns > MAX_COLUMNS) {
            mColumnsSet = DEFAULT_SIZE;
        }

        updateActualRowsAndColumns();

        if (numWidgets > mRows * mColumns || numWidgets < 1) {
            mNumWidgets = mRows * mColumns;
        }

        initVariables();
        fillConstraintMatrix(false);
    }

    /**
     * Initialize the relevant variables
     */
    private void initVariables() {
        mPositionMatrix = new boolean[mRows][mColumns];
        for (boolean[] row : mPositionMatrix) {
            Arrays.fill(row, true);
        }

        if (mNumWidgets > 0) {
            mConstraintMatrix = new int[mNumWidgets][4];
            for (int[] row : mConstraintMatrix) {
                Arrays.fill(row, -1);
            }
        }
    }

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

    /**
     * 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(CharSequence str) {
        if (str == null) {
            return false;
        }
        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;
    }

    /**
     * fill the constraintMatrix based on the input attributes
     *
     * @param isUpdate whether to update the existing grid (true) or create a new one (false)
     */
    private void fillConstraintMatrix(boolean isUpdate) {
        if (isUpdate) {
            for (int i = 0; i < mPositionMatrix.length; i++) {
                for (int j = 0; j < mPositionMatrix[0].length; j++) {
                    mPositionMatrix[i][j] = true;
                }
            }

            for (int i = 0; i < mConstraintMatrix.length; i++) {
                for (int j = 0; j < mConstraintMatrix[0].length; j++) {
                    mConstraintMatrix[i][j] = -1;
                }
            }
        }

        mNextAvailableIndex = 0;

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

        if (mStrSpans != null && !mStrSpans.trim().isEmpty()) {
            int[][] mSpans = parseSpans(mStrSpans);
            if (mSpans != null) {
                handleSpans(mSpans);
            }
        }

        addAllConstraintPositions();
    }

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

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

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

            mNextAvailableIndex++;
        }
        return position;
    }

    /**
     * add the constraint position info of a widget based on the input params
     *
     * @param widgetId the Id of the widget
     * @param row row position to place the view
     * @param column column position to place the view
     */
    private void addConstraintPosition(int widgetId, int row, int column,
                                       int rowSpan, int columnSpan) {

        mConstraintMatrix[widgetId][0] = column;
        mConstraintMatrix[widgetId][1] = row;
        mConstraintMatrix[widgetId][2] = column + columnSpan - 1;
        mConstraintMatrix[widgetId][3] = row + rowSpan - 1;
    }

    /**
     * Handle the span use cases
     *
     * @param spansMatrix a int matrix that contains span information
     */
    private void handleSpans(int[][] spansMatrix) {
        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;
            }
            addConstraintPosition(i, row, col,
                    spansMatrix[i][1], spansMatrix[i][2]);
        }
    }

    /**
     * Make positions in the grid unavailable based on the skips attr
     *
     * @param skipsMatrix a int matrix that contains skip information
     */
    private void 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;
            }
        }
    }

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

    /**
     * Arrange the views in the constraint_referenced_ids
     */
    private void addAllConstraintPositions() {
        int position;

        for (int i = 0; i < mNumWidgets; i++) {

            // Already added ConstraintPosition
            if (leftOfWidget(i) != -1) {
                continue;
            }

            position = getNextPosition();
            int row = getRowByIndex(position);
            int col = getColByIndex(position);
            if (position == -1) {
                // no more available position.
                return;
            }
            addConstraintPosition(i, row, col, 1, 1);
        }
    }

    /**
     * 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 = (mNumWidgets + mColumns - 1) / mColumnsSet; // round up
            } else  if (mRowsSet > 0) {
                mRows = mRowsSet;
                mColumns = (mNumWidgets + mRowsSet - 1) / mRowsSet; // round up
            } else { // as close to square as possible favoring more rows
                mRows = (int)  (1.5 + Math.sqrt(mNumWidgets));
                mColumns = (mNumWidgets + mRows - 1) / mRows;
            }
        } else {
            mRows = mRowsSet;
            mColumns = mColumnsSet;
        }
    }

    /**
     * Set up the Grid engine.
     */
    public void setup() {
        boolean isUpdate = true;

        if (mConstraintMatrix == null
                || mConstraintMatrix.length != mNumWidgets
                || mPositionMatrix == null
                || mPositionMatrix.length != mRows
                || mPositionMatrix[0].length != mColumns) {
            isUpdate = false;
        }

        if (!isUpdate) {
            initVariables();
        }

        fillConstraintMatrix(isUpdate);
    }

    /**
     * set new spans value
     *
     * @param spans new spans value
     */
    public void setSpans(CharSequence spans) {
        if (mStrSpans != null && mStrSpans.equals(spans.toString())) {
            return;
        }

        mStrSpans = spans.toString();
    }

    /**
     * set new skips value
     *
     * @param skips new spans value
     */
    public void setSkips(String skips) {
        if (mStrSkips != null && mStrSkips.equals(skips)) {
            return;
        }

        mStrSkips = skips;

    }

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

        if (mOrientation == orientation) {
            return;
        }

        mOrientation = orientation;
    }

    /**
     * Set new NumWidgets value
     * @param num how many widgets to be arranged in Grid
     */
    public void setNumWidgets(int num) {
        if (num > mRows * mColumns) {
            return;
        }

        mNumWidgets = num;
    }

    /**
     * set new rows value
     *
     * @param rows new rows value
     */
    public void setRows(int rows) {
        if (rows > MAX_ROWS) {
            return;
        }

        if (mRowsSet == rows) {
            return;
        }

        mRowsSet = rows;
        updateActualRowsAndColumns();

    }

    /**
     * set new columns value
     *
     * @param columns new rows value
     */
    public void setColumns(int columns) {
        if (columns > MAX_COLUMNS) {
            return;
        }

        if (mColumnsSet == columns) {
            return;
        }

        mColumnsSet = columns;
        updateActualRowsAndColumns();
    }

    /**
     * Get the boxView for the widget i to add a constraint on the left
     *
     * @param i the widget that has the order as i in the constraint_reference_ids
     * @return the boxView to add a constraint on the left
     */
    public int leftOfWidget(int i) {
        if (mConstraintMatrix == null || i >= mConstraintMatrix.length) {
            return 0;
        }
        return mConstraintMatrix[i][0];
    }

    /**
     * Get the boxView for the widget i to add a constraint on the top
     *
     * @param i the widget that has the order as i in the constraint_reference_ids
     * @return the boxView to add a constraint on the top
     */
    public int topOfWidget(int i) {
        if (mConstraintMatrix == null || i >= mConstraintMatrix.length) {
            return 0;
        }
        return mConstraintMatrix[i][1];
    }

    /**
     * Get the boxView for the widget i to add a constraint on the right
     *
     * @param i the widget that has the order as i in the constraint_reference_ids
     * @return the boxView to add a constraint on the right
     */
    public int rightOfWidget(int i) {
        if (mConstraintMatrix == null || i >= mConstraintMatrix.length) {
            return 0;
        }
        return mConstraintMatrix[i][2];
    }

    /**
     * Get the boxView for the widget i to add a constraint on the bottom
     *
     * @param i the widget that has the order as i in the constraint_reference_ids
     * @return the boxView to add a constraint on the bottom
     */
    public int bottomOfWidget(int i) {
        if (mConstraintMatrix == null || i >= mConstraintMatrix.length) {
            return 0;
        }
        return mConstraintMatrix[i][3];
    }
}