• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
14 package android.support.v17.leanback.widget;
15 
16 import android.support.annotation.NonNull;
17 import android.support.annotation.Nullable;
18 import android.support.v4.util.CircularIntArray;
19 import android.support.v7.widget.RecyclerView;
20 import android.util.SparseIntArray;
21 
22 import java.io.PrintWriter;
23 import java.util.Arrays;
24 
25 /**
26  * A grid is representation of single or multiple rows layout data structure and algorithm.
27  * Grid is the base class for single row, non-staggered grid and staggered grid.
28  * <p>
29  * To use the Grid, user must implement a Provider to create or remove visible item.
30  * Grid maintains a list of visible items.  Visible items are created when
31  * user calls appendVisibleItems() or prependVisibleItems() with certain limitation
32  * (e.g. a max edge that append up to).  Visible items are deleted when user calls
33  * removeInvisibleItemsAtEnd() or removeInvisibleItemsAtFront().  Grid's algorithm
34  * uses size of visible item returned from Provider.createItem() to decide which row
35  * to add a new visible item and may cache the algorithm results.   User must call
36  * invalidateItemsAfter() when it detects item size changed to ask Grid to remove cached
37  * results.
38  */
39 abstract class Grid {
40 
41     /**
42      * A constant representing a default starting index, indicating that the
43      * developer did not provide a start index.
44      */
45     public static final int START_DEFAULT = -1;
46 
47     Object[] mTmpItem = new Object[1];
48 
49     /**
50      * When user uses Grid,  he should provide count of items and
51      * the method to create item and remove item.
52      */
53     public static interface Provider {
54 
55         /**
56          * Return how many items (are in the adapter).
57          */
getCount()58         int getCount();
59 
60         /**
61          * @return Min index to prepend, usually it's 0; but in the preLayout case,
62          * when grid was showing 5,6,7.  Removing 3,4,5 will make the layoutPosition to
63          * be 3(deleted),4,5 in prelayout pass; Grid's index is still 5,6,7 in prelayout.
64          * When we prepend in prelayout, we can call createItem(4), createItem(3), createItem(2),
65          * the minimal index is 2, which is also the delta to mapping to layoutPosition in
66          * prelayout pass.
67          */
getMinIndex()68         int getMinIndex();
69 
70         /**
71          * Create visible item and where the provider should measure it.
72          * The call is always followed by addItem().
73          * @param index     0-based index of the item in provider
74          * @param append  True if new item is after last visible item, false if new item is
75          *                before first visible item.
76          * @param item    item[0] returns created item that will be passed in addItem() call.
77          * @param disappearingItem The item is a disappearing item added by
78          *                         {@link Grid#fillDisappearingItems(int[], int, SparseIntArray)}.
79          *
80          * @return length of the item.
81          */
createItem(int index, boolean append, Object[] item, boolean disappearingItem)82         int createItem(int index, boolean append, Object[] item, boolean disappearingItem);
83 
84         /**
85          * add item to given row and given edge.  The call is always after createItem().
86          * @param item      The object returned by createItem()
87          * @param index     0-based index of the item in provider
88          * @param length    The size of the object
89          * @param rowIndex  Row index to put the item
90          * @param edge      min_edge if not reversed or max_edge if reversed.
91          */
addItem(Object item, int index, int length, int rowIndex, int edge)92         void addItem(Object item, int index, int length, int rowIndex, int edge);
93 
94         /**
95          * Remove visible item at index.
96          * @param index     0-based index of the item in provider
97          */
removeItem(int index)98         void removeItem(int index);
99 
100         /**
101          * Get edge of an existing visible item. edge will be the min_edge
102          * if not reversed or the max_edge if reversed.
103          * @param index     0-based index of the item in provider
104          */
getEdge(int index)105         int getEdge(int index);
106 
107         /**
108          * Get size of an existing visible item.
109          * @param index     0-based index of the item in provider
110          */
getSize(int index)111         int getSize(int index);
112     }
113 
114     /**
115      * Cached representation of an item in Grid.  May be subclassed.
116      */
117     public static class Location {
118         /**
119          * The index of the row for this Location.
120          */
121         public int row;
122 
Location(int row)123         public Location(int row) {
124             this.row = row;
125         }
126     }
127 
128     protected Provider mProvider;
129     protected boolean mReversedFlow;
130     protected int mSpacing;
131     protected int mNumRows;
132     protected int mFirstVisibleIndex = -1;
133     protected int mLastVisibleIndex = -1;
134 
135     protected CircularIntArray[] mTmpItemPositionsInRows;
136 
137     // the first index that grid will layout
138     protected int mStartIndex = START_DEFAULT;
139 
140     /**
141      * Creates a single or multiple rows (can be staggered or not staggered) grid
142      */
createGrid(int rows)143     public static Grid createGrid(int rows) {
144         Grid grid;
145         if (rows == 1) {
146             grid = new SingleRow();
147         } else {
148             // TODO support non staggered multiple rows grid
149             grid = new StaggeredGridDefault();
150             grid.setNumRows(rows);
151         }
152         return grid;
153     }
154 
155     /**
156      * Sets the space between items in a row
157      */
setSpacing(int spacing)158     public final void setSpacing(int spacing) {
159         mSpacing = spacing;
160     }
161 
162     /**
163      * Sets if reversed flow (rtl)
164      */
setReversedFlow(boolean reversedFlow)165     public final void setReversedFlow(boolean reversedFlow) {
166         mReversedFlow = reversedFlow;
167     }
168 
169     /**
170      * Returns true if reversed flow (rtl)
171      */
isReversedFlow()172     public boolean isReversedFlow() {
173         return mReversedFlow;
174     }
175 
176     /**
177      * Sets the {@link Provider} for this grid.
178      *
179      * @param provider The provider for this grid.
180      */
setProvider(Provider provider)181     public void setProvider(Provider provider) {
182         mProvider = provider;
183     }
184 
185     /**
186      * Sets the first item index to create when there are no items.
187      *
188      * @param startIndex the index of the first item
189      */
setStart(int startIndex)190     public void setStart(int startIndex) {
191         mStartIndex = startIndex;
192     }
193 
194     /**
195      * Returns the number of rows in the grid.
196      */
getNumRows()197     public int getNumRows() {
198         return mNumRows;
199     }
200 
201     /**
202      * Sets number of rows to fill into. For views that represent a
203      * horizontal list, this will be the rows of the view. For views that
204      * represent a vertical list, this will be the columns.
205      *
206      * @param numRows numberOfRows
207      */
setNumRows(int numRows)208     void setNumRows(int numRows) {
209         if (numRows <= 0) {
210             throw new IllegalArgumentException();
211         }
212         if (mNumRows == numRows) {
213             return;
214         }
215         mNumRows = numRows;
216         mTmpItemPositionsInRows = new CircularIntArray[mNumRows];
217         for (int i = 0; i < mNumRows; i++) {
218             mTmpItemPositionsInRows[i] = new CircularIntArray();
219         }
220     }
221 
222     /**
223      * Returns index of first visible item in the staggered grid.  Returns negative value
224      * if no visible item.
225      */
getFirstVisibleIndex()226     public final int getFirstVisibleIndex() {
227         return mFirstVisibleIndex;
228     }
229 
230     /**
231      * Returns index of last visible item in the staggered grid.  Returns negative value
232      * if no visible item.
233      */
getLastVisibleIndex()234     public final int getLastVisibleIndex() {
235         return mLastVisibleIndex;
236     }
237 
238     /**
239      * Reset visible indices and keep cache (if exists)
240      */
resetVisibleIndex()241     public void resetVisibleIndex() {
242         mFirstVisibleIndex = mLastVisibleIndex = -1;
243     }
244 
245     /**
246      * Invalidate items after or equal to index. This will remove visible items
247      * after that and invalidate cache of layout results after that. Note that it's client's
248      * responsibility to perform removing child action, {@link Provider#removeItem(int)} will not
249      * be called because the index might be invalidated.
250      */
invalidateItemsAfter(int index)251     public void invalidateItemsAfter(int index) {
252         if (index < 0) {
253             return;
254         }
255         if (mLastVisibleIndex < 0) {
256             return;
257         }
258         if (mLastVisibleIndex >= index) {
259             mLastVisibleIndex = index - 1;
260         }
261         resetVisibleIndexIfEmpty();
262         if (getFirstVisibleIndex() < 0) {
263             setStart(index);
264         }
265     }
266 
267     /**
268      * Gets the row index of item at given index.
269      */
getRowIndex(int index)270     public final int getRowIndex(int index) {
271         Location location = getLocation(index);
272         if (location == null) {
273             return -1;
274         }
275         return location.row;
276     }
277 
278     /**
279      * Gets {@link Location} of item.  The return object is read only and temporarily.
280      */
getLocation(int index)281     public abstract Location getLocation(int index);
282 
283     /**
284      * Finds the largest or smallest row min edge of visible items,
285      * the row index is returned in indices[0], the item index is returned in indices[1].
286      */
findRowMin(boolean findLarge, @Nullable int[] indices)287     public final int findRowMin(boolean findLarge, @Nullable int[] indices) {
288         return findRowMin(findLarge, mReversedFlow ? mLastVisibleIndex : mFirstVisibleIndex,
289                 indices);
290     }
291 
292     /**
293      * Finds the largest or smallest row min edge of visible items, starts searching from
294      * indexLimit, the row index is returned in indices[0], the item index is returned in indices[1].
295      */
findRowMin(boolean findLarge, int indexLimit, int[] rowIndex)296     protected abstract int findRowMin(boolean findLarge, int indexLimit, int[] rowIndex);
297 
298     /**
299      * Finds the largest or smallest row max edge of visible items, the row index is returned in
300      * indices[0], the item index is returned in indices[1].
301      */
findRowMax(boolean findLarge, @Nullable int[] indices)302     public final int findRowMax(boolean findLarge, @Nullable int[] indices) {
303         return findRowMax(findLarge, mReversedFlow ? mFirstVisibleIndex : mLastVisibleIndex,
304                 indices);
305     }
306 
307     /**
308      * Find largest or smallest row max edge of visible items, starts searching from indexLimit,
309      * the row index is returned in indices[0], the item index is returned in indices[1].
310      */
findRowMax(boolean findLarge, int indexLimit, int[] indices)311     protected abstract int findRowMax(boolean findLarge, int indexLimit, int[] indices);
312 
313     /**
314      * Returns true if appending item has reached "toLimit"
315      */
checkAppendOverLimit(int toLimit)316     protected final boolean checkAppendOverLimit(int toLimit) {
317         if (mLastVisibleIndex < 0) {
318             return false;
319         }
320         return mReversedFlow ? findRowMin(true, null) <= toLimit + mSpacing :
321                     findRowMax(false, null) >= toLimit - mSpacing;
322     }
323 
324     /**
325      * Returns true if prepending item has reached "toLimit"
326      */
checkPrependOverLimit(int toLimit)327     protected final boolean checkPrependOverLimit(int toLimit) {
328         if (mLastVisibleIndex < 0) {
329             return false;
330         }
331         return mReversedFlow ? findRowMax(false, null) >= toLimit - mSpacing :
332                     findRowMin(true, null) <= toLimit + mSpacing;
333     }
334 
335     /**
336      * Return array of int array for all rows, each int array contains visible item positions
337      * in pair on that row between startPos(included) and endPositions(included).
338      * Returned value is read only, do not change it.
339      * <p>
340      * E.g. First row has 3,7,8, second row has 4,5,6.
341      * getItemPositionsInRows(3, 8) returns { {3,3,7,8}, {4,6} }
342      */
getItemPositionsInRows(int startPos, int endPos)343     public abstract CircularIntArray[] getItemPositionsInRows(int startPos, int endPos);
344 
345     /**
346      * Return array of int array for all rows, each int array contains visible item positions
347      * in pair on that row.
348      * Returned value is read only, do not change it.
349      * <p>
350      * E.g. First row has 3,7,8, second row has 4,5,6  { {3,3,7,8}, {4,6} }
351      */
getItemPositionsInRows()352     public final CircularIntArray[] getItemPositionsInRows() {
353         return getItemPositionsInRows(getFirstVisibleIndex(), getLastVisibleIndex());
354     }
355 
356     /**
357      * Prepends items and stops after one column is filled.
358      * (i.e. filled items from row 0 to row mNumRows - 1)
359      * @return true if at least one item is filled.
360      */
prependOneColumnVisibleItems()361     public final boolean prependOneColumnVisibleItems() {
362         return prependVisibleItems(mReversedFlow ? Integer.MIN_VALUE : Integer.MAX_VALUE, true);
363     }
364 
365     /**
366      * Prepends items until first item or reaches toLimit (min edge when not reversed or
367      * max edge when reversed)
368      */
prependVisibleItems(int toLimit)369     public final void prependVisibleItems(int toLimit) {
370         prependVisibleItems(toLimit, false);
371     }
372 
373     /**
374      * Prepends items until first item or reaches toLimit (min edge when not reversed or
375      * max edge when reversed).
376      * @param oneColumnMode  true when fills one column and stops,  false
377      * when checks if condition matches before filling first column.
378      * @return true if at least one item is filled.
379      */
prependVisibleItems(int toLimit, boolean oneColumnMode)380     protected abstract boolean prependVisibleItems(int toLimit, boolean oneColumnMode);
381 
382     /**
383      * Appends items and stops after one column is filled.
384      * (i.e. filled items from row 0 to row mNumRows - 1)
385      * @return true if at least one item is filled.
386      */
appendOneColumnVisibleItems()387     public boolean appendOneColumnVisibleItems() {
388         return appendVisibleItems(mReversedFlow ? Integer.MAX_VALUE : Integer.MIN_VALUE, true);
389     }
390 
391     /**
392      * Append items until last item or reaches toLimit (max edge when not
393      * reversed or min edge when reversed)
394      */
appendVisibleItems(int toLimit)395     public final void appendVisibleItems(int toLimit) {
396         appendVisibleItems(toLimit, false);
397     }
398 
399     /**
400      * Appends items until last or reaches toLimit (high edge when not
401      * reversed or low edge when reversed).
402      * @param oneColumnMode True when fills one column and stops,  false
403      * when checks if condition matches before filling first column.
404      * @return true if filled at least one item
405      */
appendVisibleItems(int toLimit, boolean oneColumnMode)406     protected abstract boolean appendVisibleItems(int toLimit, boolean oneColumnMode);
407 
408     /**
409      * Removes invisible items from end until reaches item at aboveIndex or toLimit.
410      * @param aboveIndex Don't remove items whose index is equals or smaller than aboveIndex
411      * @param toLimit Don't remove items whose left edge is less than toLimit.
412      */
removeInvisibleItemsAtEnd(int aboveIndex, int toLimit)413     public void removeInvisibleItemsAtEnd(int aboveIndex, int toLimit) {
414         while(mLastVisibleIndex >= mFirstVisibleIndex && mLastVisibleIndex > aboveIndex) {
415             boolean offEnd = !mReversedFlow ? mProvider.getEdge(mLastVisibleIndex) >= toLimit
416                     : mProvider.getEdge(mLastVisibleIndex) <= toLimit;
417             if (offEnd) {
418                 mProvider.removeItem(mLastVisibleIndex);
419                 mLastVisibleIndex--;
420             } else {
421                 break;
422             }
423         }
424         resetVisibleIndexIfEmpty();
425     }
426 
427     /**
428      * Removes invisible items from front until reaches item at belowIndex or toLimit.
429      * @param belowIndex Don't remove items whose index is equals or larger than belowIndex
430      * @param toLimit Don't remove items whose right edge is equals or greater than toLimit.
431      */
removeInvisibleItemsAtFront(int belowIndex, int toLimit)432     public void removeInvisibleItemsAtFront(int belowIndex, int toLimit) {
433         while(mLastVisibleIndex >= mFirstVisibleIndex && mFirstVisibleIndex < belowIndex) {
434             final int size = mProvider.getSize(mFirstVisibleIndex);
435             boolean offFront = !mReversedFlow
436                     ? mProvider.getEdge(mFirstVisibleIndex) + size <= toLimit
437                     : mProvider.getEdge(mFirstVisibleIndex) - size >= toLimit;
438             if (offFront) {
439                 mProvider.removeItem(mFirstVisibleIndex);
440                 mFirstVisibleIndex++;
441             } else {
442                 break;
443             }
444         }
445         resetVisibleIndexIfEmpty();
446     }
447 
resetVisibleIndexIfEmpty()448     private void resetVisibleIndexIfEmpty() {
449         if (mLastVisibleIndex < mFirstVisibleIndex) {
450             resetVisibleIndex();
451         }
452     }
453 
454     /**
455      * Fill disappearing items, i.e. the items are moved out of window, we need give them final
456      * location so recyclerview will run a slide out animation. The positions that was greater than
457      * last visible index will be appended to end, the positions that was smaller than first visible
458      * index will be prepend to beginning.
459      * @param positions Sorted list of positions of disappearing items.
460      * @param positionToRow Which row we want to put the disappearing item.
461      */
fillDisappearingItems(int[] positions, int positionsLength, SparseIntArray positionToRow)462     public void fillDisappearingItems(int[] positions, int positionsLength,
463             SparseIntArray positionToRow) {
464         final int lastPos = getLastVisibleIndex();
465         final int resultSearchLast = lastPos >= 0
466                 ? Arrays.binarySearch(positions, 0, positionsLength, lastPos) : 0;
467         if (resultSearchLast < 0) {
468             // we shouldn't find lastPos in disappearing position list.
469             int firstDisappearingIndex = -resultSearchLast - 1;
470             int edge;
471             if (mReversedFlow) {
472                 edge = mProvider.getEdge(lastPos) - mProvider.getSize(lastPos) - mSpacing;
473             } else {
474                 edge = mProvider.getEdge(lastPos) + mProvider.getSize(lastPos) + mSpacing;
475             }
476             for (int i = firstDisappearingIndex; i < positionsLength; i++) {
477                 int disappearingIndex = positions[i];
478                 int disappearingRow = positionToRow.get(disappearingIndex);
479                 if (disappearingRow < 0) {
480                     disappearingRow = 0; // if not found put in row 0
481                 }
482                 int size = mProvider.createItem(disappearingIndex, true, mTmpItem, true);
483                 mProvider.addItem(mTmpItem[0], disappearingIndex, size, disappearingRow, edge);
484                 if (mReversedFlow) {
485                     edge = edge - size - mSpacing;
486                 } else {
487                     edge = edge + size + mSpacing;
488                 }
489             }
490         }
491 
492         final int firstPos = getFirstVisibleIndex();
493         final int resultSearchFirst = firstPos >= 0
494                 ? Arrays.binarySearch(positions, 0, positionsLength, firstPos) : 0;
495         if (resultSearchFirst < 0) {
496             // we shouldn't find firstPos in disappearing position list.
497             int firstDisappearingIndex = -resultSearchFirst - 2;
498             int edge;
499             if (mReversedFlow) {
500                 edge = mProvider.getEdge(firstPos);
501             } else {
502                 edge = mProvider.getEdge(firstPos);
503             }
504             for (int i = firstDisappearingIndex; i >= 0; i--) {
505                 int disappearingIndex = positions[i];
506                 int disappearingRow = positionToRow.get(disappearingIndex);
507                 if (disappearingRow < 0) {
508                     disappearingRow = 0; // if not found put in row 0
509                 }
510                 int size = mProvider.createItem(disappearingIndex, false, mTmpItem, true);
511                 if (mReversedFlow) {
512                     edge = edge + mSpacing + size;
513                 } else {
514                     edge = edge - mSpacing - size;
515                 }
516                 mProvider.addItem(mTmpItem[0], disappearingIndex, size, disappearingRow, edge);
517             }
518         }
519     }
520 
521     /**
522      * Queries items adjacent to the viewport (in the direction of da) into the prefetch registry.
523      */
collectAdjacentPrefetchPositions(int fromLimit, int da, @NonNull RecyclerView.LayoutManager.LayoutPrefetchRegistry layoutPrefetchRegistry)524     public void collectAdjacentPrefetchPositions(int fromLimit, int da,
525             @NonNull RecyclerView.LayoutManager.LayoutPrefetchRegistry layoutPrefetchRegistry) {
526     }
527 
debugPrint(PrintWriter pw)528     public abstract void debugPrint(PrintWriter pw);
529 }
530