• 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.v4.util.CircularIntArray;
17 
18 import 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  */
34 abstract 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          */
getCount()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          */
createItem(int index, boolean append, Object[] item)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          */
addItem(Object item, int index, int length, int rowIndex, int edge)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          */
removeItem(int index)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          */
getEdge(int index)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          */
getSize(int index)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 
Location(int row)103         public Location(int row) {
104             this.row = row;
105         }
106     }
107 
108     protected Provider mProvider;
109     protected boolean mReversedFlow;
110     protected int mMargin;
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      */
createGrid(int rows)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 margin between items in a row
137      */
setMargin(int margin)138     public final void setMargin(int margin) {
139         mMargin = margin;
140     }
141 
142     /**
143      * Sets if reversed flow (rtl)
144      */
setReversedFlow(boolean reversedFlow)145     public final void setReversedFlow(boolean reversedFlow) {
146         mReversedFlow = reversedFlow;
147     }
148 
149     /**
150      * Returns true if reversed flow (rtl)
151      */
isReversedFlow()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      */
setProvider(Provider provider)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      */
setStart(int startIndex)170     public void setStart(int startIndex) {
171         mStartIndex = startIndex;
172     }
173 
174     /**
175      * Returns the number of rows in the grid.
176      */
getNumRows()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      */
setNumRows(int numRows)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      */
getFirstVisibleIndex()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      */
getLastVisibleIndex()214     public final int getLastVisibleIndex() {
215         return mLastVisibleIndex;
216     }
217 
218     /**
219      * Reset visible indices and keep cache (if exists)
220      */
resetVisibleIndex()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      */
invalidateItemsAfter(int index)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      */
getRowIndex(int index)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      */
getLocation(int index)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      */
findRowMin(boolean findLarge, int[] indices)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      */
findRowMin(boolean findLarge, int indexLimit, int[] rowIndex)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      */
findRowMax(boolean findLarge, int[] indices)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      */
findRowMax(boolean findLarge, int indexLimit, int[] indices)286     protected abstract int findRowMax(boolean findLarge, int indexLimit, int[] indices);
287 
288     /**
289      * Returns true if appending item has reached "toLimit"
290      */
checkAppendOverLimit(int toLimit)291     protected final boolean checkAppendOverLimit(int toLimit) {
292         if (mLastVisibleIndex < 0) {
293             return false;
294         }
295         return mReversedFlow ? findRowMin(true, null) <= toLimit + mMargin :
296                     findRowMax(false, null) >= toLimit - mMargin;
297     }
298 
299     /**
300      * Returns true if prepending item has reached "toLimit"
301      */
checkPrependOverLimit(int toLimit)302     protected final boolean checkPrependOverLimit(int toLimit) {
303         if (mLastVisibleIndex < 0) {
304             return false;
305         }
306         return mReversedFlow ? findRowMax(false, null) >= toLimit - mMargin :
307                     findRowMin(true, null) <= toLimit + mMargin;
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      */
getItemPositionsInRows(int startPos, int endPos)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      */
getItemPositionsInRows()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      */
prependOneColumnVisibleItems()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      */
prependVisibleItems(int toLimit)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      */
prependVisibleItems(int toLimit, boolean oneColumnMode)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      */
appendOneColumnVisibleItems()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      */
appendVisibleItems(int toLimit)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      */
appendVisibleItems(int toLimit, boolean oneColumnMode)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      */
removeInvisibleItemsAtEnd(int aboveIndex, int toLimit)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      */
removeInvisibleItemsAtFront(int belowIndex, int toLimit)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 
resetVisibleIndexIfEmpty()419     private void resetVisibleIndexIfEmpty() {
420         if (mLastVisibleIndex < mFirstVisibleIndex) {
421             resetVisibleIndex();
422         }
423     }
424 
debugPrint(PrintWriter pw)425     public abstract void debugPrint(PrintWriter pw);
426 }
427