Grid.java revision ef4e1b371215f9fffa9245159250740b611dfb4d
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.support.v4.util.CircularIntArray;
17
18import java.io.PrintWriter;
19
20/**
21 * A grid is representation of single or multiple rows layout data structure and algorithm.
22 * Grid is the base class for single row, non-staggered grid and staggered grid.
23 * <p>
24 * To use the Grid, user must implement a Provider to create or remove visible item.
25 * Grid maintains a list of visible items.  Visible items are created when
26 * user calls appendVisibleItems() or prependVisibleItems() with certain limitation
27 * (e.g. a max edge that append up to).  Visible items are deleted when user calls
28 * removeInvisibleItemsAtEnd() or removeInvisibleItemsAtFront().  Grid's algorithm
29 * uses size of visible item returned from Provider.createItem() to decide which row
30 * to add a new visible item and may cache the algorithm results.   User must call
31 * invalidateItemsAfter() when it detects item size changed to ask Grid to remove cached
32 * results.
33 */
34abstract class Grid {
35
36    /**
37     * A constant representing a default starting index, indicating that the
38     * developer did not provide a start index.
39     */
40    public static final int START_DEFAULT = -1;
41
42    /**
43     * When user uses Grid,  he should provide count of items and
44     * the method to create item and remove item.
45     */
46    public static interface Provider {
47
48        /**
49         * Return how many items (are in the adapter).
50         */
51        public abstract int getCount();
52
53        /**
54         * Create visible item and where the provider should measure it.
55         * The call is always followed by addItem().
56         * @param index     0-based index of the item in provider
57         * @param append  True if new item is after last visible item, false if new item is
58         *                before first visible item.
59         * @param item    item[0] returns created item that will be passed in addItem() call.
60         * @return length of the item.
61         */
62        public abstract int createItem(int index, boolean append, Object[] item);
63
64        /**
65         * add item to given row and given edge.  The call is always after createItem().
66         * @param item      The object returned by createItem()
67         * @param index     0-based index of the item in provider
68         * @param length    The size of the object
69         * @param rowIndex  Row index to put the item
70         * @param edge      min_edge if not reversed or max_edge if reversed.
71         */
72        public abstract void addItem(Object item, int index, int length, int rowIndex, int edge);
73
74        /**
75         * Remove visible item at index.
76         * @param index     0-based index of the item in provider
77         */
78        public abstract void removeItem(int index);
79
80        /**
81         * Get edge of an existing visible item. edge will be the min_edge
82         * if not reversed or the max_edge if reversed.
83         * @param index     0-based index of the item in provider
84         */
85        public abstract int getEdge(int index);
86
87        /**
88         * Get size of an existing visible item.
89         * @param index     0-based index of the item in provider
90         */
91        public abstract int getSize(int index);
92    }
93
94    /**
95     * Cached representation of an item in Grid.  May be subclassed.
96     */
97    public static class Location {
98        /**
99         * The index of the row for this Location.
100         */
101        public int row;
102
103        public Location(int row) {
104            this.row = row;
105        }
106    }
107
108    protected Provider mProvider;
109    protected boolean mReversedFlow;
110    protected int mSpacing;
111    protected int mNumRows;
112    protected int mFirstVisibleIndex = -1;
113    protected int mLastVisibleIndex = -1;
114
115    protected CircularIntArray[] mTmpItemPositionsInRows;
116
117    // the first index that grid will layout
118    protected int mStartIndex = START_DEFAULT;
119
120    /**
121     * Creates a single or multiple rows (can be staggered or not staggered) grid
122     */
123    public static Grid createGrid(int rows) {
124        Grid grid;
125        if (rows == 1) {
126            grid = new SingleRow();
127        } else {
128            // TODO support non staggered multiple rows grid
129            grid = new StaggeredGridDefault();
130            grid.setNumRows(rows);
131        }
132        return grid;
133    }
134
135    /**
136     * Sets the space between items in a row
137     */
138    public final void setSpacing(int spacing) {
139        mSpacing = spacing;
140    }
141
142    /**
143     * Sets if reversed flow (rtl)
144     */
145    public final void setReversedFlow(boolean reversedFlow) {
146        mReversedFlow = reversedFlow;
147    }
148
149    /**
150     * Returns true if reversed flow (rtl)
151     */
152    public boolean isReversedFlow() {
153        return mReversedFlow;
154    }
155
156    /**
157     * Sets the {@link Provider} for this grid.
158     *
159     * @param provider The provider for this grid.
160     */
161    public void setProvider(Provider provider) {
162        mProvider = provider;
163    }
164
165    /**
166     * Sets the first item index to create when there are no items.
167     *
168     * @param startIndex the index of the first item
169     */
170    public void setStart(int startIndex) {
171        mStartIndex = startIndex;
172    }
173
174    /**
175     * Returns the number of rows in the grid.
176     */
177    public int getNumRows() {
178        return mNumRows;
179    }
180
181    /**
182     * Sets number of rows to fill into. For views that represent a
183     * horizontal list, this will be the rows of the view. For views that
184     * represent a vertical list, this will be the columns.
185     *
186     * @param numRows numberOfRows
187     */
188    void setNumRows(int numRows) {
189        if (numRows <= 0) {
190            throw new IllegalArgumentException();
191        }
192        if (mNumRows == numRows) {
193            return;
194        }
195        mNumRows = numRows;
196        mTmpItemPositionsInRows = new CircularIntArray[mNumRows];
197        for (int i = 0; i < mNumRows; i++) {
198            mTmpItemPositionsInRows[i] = new CircularIntArray();
199        }
200    }
201
202    /**
203     * Returns index of first visible item in the staggered grid.  Returns negative value
204     * if no visible item.
205     */
206    public final int getFirstVisibleIndex() {
207        return mFirstVisibleIndex;
208    }
209
210    /**
211     * Returns index of last visible item in the staggered grid.  Returns negative value
212     * if no visible item.
213     */
214    public final int getLastVisibleIndex() {
215        return mLastVisibleIndex;
216    }
217
218    /**
219     * Reset visible indices and keep cache (if exists)
220     */
221    public void resetVisibleIndex() {
222        mFirstVisibleIndex = mLastVisibleIndex = -1;
223    }
224
225    /**
226     * Invalidate items after or equal to index. This will remove visible items
227     * after that and invalidate cache of layout results after that.
228     */
229    public void invalidateItemsAfter(int index) {
230        if (index < 0) {
231            return;
232        }
233        if (mLastVisibleIndex < 0) {
234            return;
235        }
236        while (mLastVisibleIndex >= index) {
237            mProvider.removeItem(mLastVisibleIndex);
238            mLastVisibleIndex--;
239        }
240        resetVisibleIndexIfEmpty();
241        if (getFirstVisibleIndex() < 0) {
242            setStart(index);
243        }
244    }
245
246    /**
247     * Gets the row index of item at given index.
248     */
249    public final int getRowIndex(int index) {
250        return getLocation(index).row;
251    }
252
253    /**
254     * Gets {@link Location} of item.  The return object is read only and temporarily.
255     */
256    public abstract Location getLocation(int index);
257
258    /**
259     * Finds the largest or smallest row min edge of visible items,
260     * the row index is returned in indices[0], the item index is returned in indices[1].
261     */
262    public final int findRowMin(boolean findLarge, int[] indices) {
263        return findRowMin(findLarge, mReversedFlow ? mLastVisibleIndex : mFirstVisibleIndex,
264                indices);
265    }
266
267    /**
268     * Finds the largest or smallest row min edge of visible items, starts searching from
269     * indexLimit, the row index is returned in indices[0], the item index is returned in indices[1].
270     */
271    protected abstract int findRowMin(boolean findLarge, int indexLimit, int[] rowIndex);
272
273    /**
274     * Finds the largest or smallest row max edge of visible items, the row index is returned in
275     * indices[0], the item index is returned in indices[1].
276     */
277    public final int findRowMax(boolean findLarge, int[] indices) {
278        return findRowMax(findLarge, mReversedFlow ? mFirstVisibleIndex : mLastVisibleIndex,
279                indices);
280    }
281
282    /**
283     * Find largest or smallest row max edge of visible items, starts searching from indexLimit,
284     * the row index is returned in indices[0], the item index is returned in indices[1].
285     */
286    protected abstract int findRowMax(boolean findLarge, int indexLimit, int[] indices);
287
288    /**
289     * Returns true if appending item has reached "toLimit"
290     */
291    protected final boolean checkAppendOverLimit(int toLimit) {
292        if (mLastVisibleIndex < 0) {
293            return false;
294        }
295        return mReversedFlow ? findRowMin(true, null) <= toLimit + mSpacing :
296                    findRowMax(false, null) >= toLimit - mSpacing;
297    }
298
299    /**
300     * Returns true if prepending item has reached "toLimit"
301     */
302    protected final boolean checkPrependOverLimit(int toLimit) {
303        if (mLastVisibleIndex < 0) {
304            return false;
305        }
306        return mReversedFlow ? findRowMax(false, null) >= toLimit - mSpacing :
307                    findRowMin(true, null) <= toLimit + mSpacing;
308    }
309
310    /**
311     * Return array of int array for all rows, each int array contains visible item positions
312     * in pair on that row between startPos(included) and endPositions(included).
313     * Returned value is read only, do not change it.
314     * <p>
315     * E.g. First row has 3,7,8, second row has 4,5,6.
316     * getItemPositionsInRows(3, 8) returns { {3,3,7,8}, {4,6} }
317     */
318    public abstract CircularIntArray[] getItemPositionsInRows(int startPos, int endPos);
319
320    /**
321     * Return array of int array for all rows, each int array contains visible item positions
322     * in pair on that row.
323     * Returned value is read only, do not change it.
324     * <p>
325     * E.g. First row has 3,7,8, second row has 4,5,6  { {3,3,7,8}, {4,6} }
326     */
327    public final CircularIntArray[] getItemPositionsInRows() {
328        return getItemPositionsInRows(getFirstVisibleIndex(), getLastVisibleIndex());
329    }
330
331    /**
332     * Prepends items and stops after one column is filled.
333     * (i.e. filled items from row 0 to row mNumRows - 1)
334     * @return true if at least one item is filled.
335     */
336    public final boolean prependOneColumnVisibleItems() {
337        return prependVisibleItems(mReversedFlow ? Integer.MIN_VALUE : Integer.MAX_VALUE, true);
338    }
339
340    /**
341     * Prepends items until first item or reaches toLimit (min edge when not reversed or
342     * max edge when reversed)
343     */
344    public final void prependVisibleItems(int toLimit) {
345        prependVisibleItems(toLimit, false);
346    }
347
348    /**
349     * Prepends items until first item or reaches toLimit (min edge when not reversed or
350     * max edge when reversed).
351     * @param oneColumnMode  true when fills one column and stops,  false
352     * when checks if condition matches before filling first column.
353     * @return true if at least one item is filled.
354     */
355    protected abstract boolean prependVisibleItems(int toLimit, boolean oneColumnMode);
356
357    /**
358     * Appends items and stops after one column is filled.
359     * (i.e. filled items from row 0 to row mNumRows - 1)
360     * @return true if at least one item is filled.
361     */
362    public boolean appendOneColumnVisibleItems() {
363        return appendVisibleItems(mReversedFlow ? Integer.MAX_VALUE : Integer.MIN_VALUE, true);
364    }
365
366    /**
367     * Append items until last item or reaches toLimit (max edge when not
368     * reversed or min edge when reversed)
369     */
370    public final void appendVisibleItems(int toLimit) {
371        appendVisibleItems(toLimit, false);
372    }
373
374    /**
375     * Appends items until last or reaches toLimit (high edge when not
376     * reversed or low edge when reversed).
377     * @param oneColumnMode True when fills one column and stops,  false
378     * when checks if condition matches before filling first column.
379     * @return true if filled at least one item
380     */
381    protected abstract boolean appendVisibleItems(int toLimit, boolean oneColumnMode);
382
383    /**
384     * Removes invisible items from end until reaches item at aboveIndex or toLimit.
385     */
386    public void removeInvisibleItemsAtEnd(int aboveIndex, int toLimit) {
387        while(mLastVisibleIndex >= mFirstVisibleIndex && mLastVisibleIndex > aboveIndex) {
388            boolean offEnd = !mReversedFlow ? mProvider.getEdge(mLastVisibleIndex) >= toLimit
389                    : mProvider.getEdge(mLastVisibleIndex) <= toLimit;
390            if (offEnd) {
391                mProvider.removeItem(mLastVisibleIndex);
392                mLastVisibleIndex--;
393            } else {
394                break;
395            }
396        }
397        resetVisibleIndexIfEmpty();
398    }
399
400    /**
401     * Removes invisible items from front until reaches item at belowIndex or toLimit.
402     */
403    public void removeInvisibleItemsAtFront(int belowIndex, int toLimit) {
404        while(mLastVisibleIndex >= mFirstVisibleIndex && mFirstVisibleIndex < belowIndex) {
405            boolean offFront = !mReversedFlow ? mProvider.getEdge(mFirstVisibleIndex)
406                    + mProvider.getSize(mFirstVisibleIndex) <= toLimit
407                    : mProvider.getEdge(mFirstVisibleIndex)
408                            - mProvider.getSize(mFirstVisibleIndex) >= toLimit;
409            if (offFront) {
410                mProvider.removeItem(mFirstVisibleIndex);
411                mFirstVisibleIndex++;
412            } else {
413                break;
414            }
415        }
416        resetVisibleIndexIfEmpty();
417    }
418
419    private void resetVisibleIndexIfEmpty() {
420        if (mLastVisibleIndex < mFirstVisibleIndex) {
421            resetVisibleIndex();
422        }
423    }
424
425    public abstract void debugPrint(PrintWriter pw);
426}
427