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