1

I am trying to create a layout manager that would lay it's children horizontally until it encounters the width of the recycler view. If it reaches the edge, it should layout the children in the next row.

For example suppose there are 4 items in the recycler view - item1, item2, item3 and item4. View's of item1 and item2 are being laid out next to each other. There is still some space left. But item3's view cannot fit into that width. So item3 goes to the next line. But the gap that was left should now be equally divided between item1 and item2.

| <item1><item2><--gap-->|
|<-----item3---->        |

this should become

| <--item1--> <--item2-->|
|<-----item3---->        |

and if item4's view fits within the space after item3, it should be laid out there.

| <--item1--> <--item2--> |
|<-----item3----><-item4->|


This couldn't be achieved through GridLayoutManager or StaggeredGridLayoutManager because they don't account for varying width of individual items.

To write a custom layout manager, I have a feeling that I should override onLayoutChildren method of layout manager. But I am a bit stuck at this point. I am not sure how to go about this. Any help would be appreciated. Thanks.

Ashwin
  • 12,691
  • 31
  • 118
  • 190

1 Answers1

0

I've had similar issue, but solved this using GridLayout. The minuses that all views should be created from the start:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

        <android.support.v7.widget.GridLayout
            android:id="@+id/bubble_grid"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical"
            android:scrollbarStyle="outsideOverlay"
            android:layout_gravity="top|left"
            app:orientation="vertical"
            app:columnCount="@integer/grid_cells">
        </android.support.v7.widget.GridLayout>

    </ScrollView>

Here is an adapter:

public class GridAdapter {
    /**
     * Indicates maximum filled row in current column, column is the index of arr, row is the value
     * [224444555550000000]
     * It equals to:
     * [***********-------]
     * [***********-------]
     * [--*********-------]
     * [--*********-------]
     * [------*****-------]
     * [------------------]
     */
    private final int[] mRowMatrix;

    public GridAdapter(GridLayout gridLayout) {
        mGridLayout = gridLayout;
        mPlacedList = new ArrayList<>(10);
        mUnplacedList = new LinkedList<>();

        mNumColumns = ResHelper.getInteger(R.integer.grid_cells);
        mMinCells = ResHelper.getInteger(R.integer.min_cells);
        mMaxCells = ResHelper.getInteger(R.integer.max_cells);
        mRowMatrix = new int[mNumColumns];
    }

    public void notifyDataSetChanged() {

        while (mUnplacedList.size() > 0) {
            final int toCol = findColWithMinRow();
            final int gapSize = findGapSizeForCol(toCol);
            final CustomView view = findAppropriate(gapSize);
            if (view == null) {
                final int filledRow = toCol > 0
                        ? (toCol + gapSize < mRowMatrix.length ? Math.min(mRowMatrix[toCol - 1], mRowMatrix[toCol + gapSize]) : mRowMatrix[toCol - 1])
                        : mRowMatrix[gapSize];
                for (int j = toCol, jCount = toCol + gapSize; j < jCount; j++) {
                    mRowMatrix[j] = filledRow;
                }
            } else {
                placeView(view, toCol, mRowMatrix[toCol]);
            }
        }
    }

    /**
     * Put view in certain column and row in the gridlayout
     */
    private void placeView(CustomView view, int toCol, int toRow) {
        final int gridSize = view.getGridSize();
        final GridLayout.LayoutParams params = new GridLayout.LayoutParams();
        params.width = params.height = gridSize * mCellSize;
        params.columnSpec = GridLayout.spec(toCol, gridSize);
        params.rowSpec = GridLayout.spec(toRow, gridSize);

        mPlacedList.add(view);
        mUnplacedList.remove(view);
        mGridLayout.addView(view, params);

        final int filledRow = toRow + gridSize;
        for (int j = toCol, count = toCol + gridSize; j < count; j++) {
            mRowMap[j] = filledRow;
        }
    }

    /**
     * Find empty gap which starts from toCol
     * [*********************]
     * [******------*****----] here col = 6, size = 3
     * [******------*****----]
     * [******------*****----]
     * [*****************----]
     * @param toCol
     * @return
     */
    private int findGapSizeForCol(int toCol) {
        final int fromRow = mRowMatrix[toCol];
        int i = toCol;
        for (; i < mNumColumns; i++) {
            if (fromRow != mRowMatrix[i]) {
                break;
            }
        }
        return i - toCol;
    }

    /**
     * Find column with minimum filled row
     * [*********************]
     * [*****************----] here col = 17
     * [******------*****----]
     * [******------*****----]
     * @return
     */
    private int findColWithMinRow() {
        int minRow = Integer.MAX_VALUE, minCol = 0;
        for (int i = 0, count = mRowMatrix.length; i < count; i++) {
            if (minRow > mRowMatrix[i]) {
                minRow = mRowMatrix[i];
                minCol = i;
            }
        }
        return minCol;
    }

    /**
     * Find customView with appropriate size for the empty gap
     */
    private CustomView findAppropriate(int size) {
        for (int j = mUnplacedList.size() - 1; j >= 0; j--) {
            if (mUnplacedList.get(j).getGridSize() <= size) {
                return mUnplacedList.get(j);
            }
        }
        return null;
    }

}
Oleksandr Albul
  • 1,611
  • 1
  • 23
  • 31