StaggeredGridDefault.java revision 89fac6fa5d32123cc79d1d4127a4a7bcf86c498a
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
16/**
17 * A default implementation of {@link StaggeredGrid}.
18 *
19 * This implementation tries to fill items in consecutive row order. The next
20 * item is always in same row or in the next row.
21 */
22final class StaggeredGridDefault extends StaggeredGrid {
23
24    @Override
25    protected int updateFirstVisibleOffset(int prependedItemSize) {
26        Location firstVisible = getLocation(mFirstVisibleIndex);
27        // Find a cached item in same row, if not found, just use last item.
28        int index = mFirstVisibleIndex - 1;
29        boolean foundCachedItemInSameRow = false;
30        while (index >= mFirstIndex) {
31            Location loc = getLocation(index);
32            if (loc.row == firstVisible.row) {
33                foundCachedItemInSameRow = true;
34                break;
35            }
36            index--;
37        }
38        if (!foundCachedItemInSameRow) {
39            index = mFirstVisibleIndex - 1;
40        }
41        // Assuming mFirstVisibleIndex is next to index on the same row, so the
42        // sum of offset of [index + 1, mFirstVisibleIndex] should be size of the item
43        // plus margin.
44        int offset = isReversedFlow() ?  -getLocation(index).size - mMargin:
45                getLocation(index).size + mMargin;
46        for (int i = index + 1; i < mFirstVisibleIndex; i++) {
47            offset -= getLocation(index).offset;
48        }
49        firstVisible.offset = offset;
50        return offset;
51    }
52
53    /**
54     * Returns the max edge value of item (visible or cached) in a row.  This
55     * will be the place to append or prepend item not in cache.
56     */
57    int getRowMax(int rowIndex) {
58        if (mFirstVisibleIndex < 0) {
59            return Integer.MIN_VALUE;
60        }
61        if (mReversedFlow) {
62            int edge = mProvider.getEdge(mFirstVisibleIndex);
63            if (getLocation(mFirstVisibleIndex).row == rowIndex) {
64                return edge;
65            }
66            for (int i = mFirstVisibleIndex + 1; i <= getLastIndex(); i++) {
67                Location loc = getLocation(i);
68                edge += loc.offset;
69                if (loc.row == rowIndex) {
70                    return edge;
71                }
72            }
73        } else {
74            int edge = mProvider.getEdge(mLastVisibleIndex);
75            Location loc = getLocation(mLastVisibleIndex);
76            if (loc.row == rowIndex) {
77                return edge + loc.size;
78            }
79            for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) {
80                edge -= loc.offset;
81                loc = getLocation(i);
82                if (loc.row == rowIndex) {
83                    return edge + loc.size;
84                }
85            }
86        }
87        return Integer.MIN_VALUE;
88    }
89
90    /**
91     * Returns the min edge value of item (visible or cached) in a row.  This
92     * will be the place to prepend or append item not in cache.
93     */
94    int getRowMin(int rowIndex) {
95        if (mFirstVisibleIndex < 0) {
96            return Integer.MAX_VALUE;
97        }
98        if (mReversedFlow) {
99            int edge = mProvider.getEdge(mLastVisibleIndex);
100            Location loc = getLocation(mLastVisibleIndex);
101            if (loc.row == rowIndex) {
102                return edge - loc.size;
103            }
104            for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) {
105                edge -= loc.offset;
106                loc = getLocation(i);
107                if (loc.row == rowIndex) {
108                    return edge - loc.size;
109                }
110            }
111        } else {
112            int edge = mProvider.getEdge(mFirstVisibleIndex);
113            if (getLocation(mFirstVisibleIndex).row == rowIndex) {
114                return edge;
115            }
116            for (int i = mFirstVisibleIndex + 1; i <= getLastIndex() ; i++) {
117                Location loc = getLocation(i);
118                edge += loc.offset;
119                if (loc.row == rowIndex) {
120                    return edge;
121                }
122            }
123        }
124        return Integer.MAX_VALUE;
125    }
126
127    /**
128     * Note this method has assumption that item is filled either in the same row
129     * next row of last item.  Search until row index wrapped.
130     */
131    @Override
132    public int findRowMax(boolean findLarge, int indexLimit, int[] indices) {
133        int value;
134        int edge = mProvider.getEdge(indexLimit);
135        Location loc = getLocation(indexLimit);
136        int row = loc.row;
137        int index = indexLimit;
138        int visitedRows = 1;
139        int visitRow = row;
140        if (mReversedFlow) {
141            value = edge;
142            for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) {
143                loc = getLocation(i);
144                edge += loc.offset;
145                if (loc.row != visitRow) {
146                    visitRow = loc.row;
147                    visitedRows++;
148                    if (findLarge ? edge > value : edge < value) {
149                        row = visitRow;
150                        value = edge;
151                        index = i;
152                    }
153                }
154            }
155        } else {
156            value = edge + mProvider.getSize(indexLimit);
157            for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) {
158                edge -= loc.offset;
159                loc = getLocation(i);
160                if (loc.row != visitRow) {
161                    visitRow = loc.row;
162                    visitedRows++;
163                    int newValue = edge + mProvider.getSize(i);
164                    if (findLarge ? newValue > value : newValue < value) {
165                        row = visitRow;
166                        value = newValue;
167                        index = i;
168                    }
169                }
170            }
171        }
172        if (indices != null) {
173            indices[0] = row;
174            indices[1] = index;
175        }
176        return value;
177    }
178
179    /**
180     * Note this method has assumption that item is filled either in the same row
181     * next row of last item.  Search until row index wrapped.
182     */
183    @Override
184    public int findRowMin(boolean findLarge, int indexLimit, int[] indices) {
185        int value;
186        int edge = mProvider.getEdge(indexLimit);
187        Location loc = getLocation(indexLimit);
188        int row = loc.row;
189        int index = indexLimit;
190        int visitedRows = 1;
191        int visitRow = row;
192        if (mReversedFlow) {
193            value = edge - mProvider.getSize(indexLimit);
194            for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) {
195                edge -= loc.offset;
196                loc = getLocation(i);
197                if (loc.row != visitRow) {
198                    visitRow = loc.row;
199                    visitedRows++;
200                    int newValue = edge - mProvider.getSize(i);
201                    if (findLarge ? newValue > value : newValue < value) {
202                        value = newValue;
203                        row = visitRow;
204                        index = i;
205                    }
206                }
207            }
208        } else {
209            value = edge;
210            for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) {
211                loc = getLocation(i);
212                edge += loc.offset;
213                if (loc.row != visitRow) {
214                    visitRow = loc.row;
215                    visitedRows++;
216                    if (findLarge ? edge > value : edge < value) {
217                        value = edge;
218                        row = visitRow;
219                        index = i;
220                    }
221                }
222            }
223        }
224        if (indices != null) {
225            indices[0] = row;
226            indices[1] = index;
227        }
228        return value;
229    }
230
231    private int findRowEdgeLimitSearchIndex(boolean append) {
232        boolean wrapped = false;
233        if (append) {
234            for (int index = mLastVisibleIndex; index >= mFirstVisibleIndex; index--) {
235                int row = getLocation(index).row;
236                if (row == 0) {
237                    wrapped = true;
238                } else if (wrapped && row == mNumRows - 1) {
239                    return index;
240                }
241            }
242        } else {
243            for (int index = mFirstVisibleIndex; index <= mLastVisibleIndex; index++) {
244                int row = getLocation(index).row;
245                if (row == mNumRows - 1) {
246                    wrapped = true;
247                } else if (wrapped && row == 0) {
248                    return index;
249                }
250            }
251        }
252        return -1;
253    }
254
255    @Override
256    protected boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) {
257        final int count = mProvider.getCount();
258        int itemIndex;
259        int rowIndex;
260        int edgeLimit;
261        boolean edgeLimitIsValid;
262        if (mLastVisibleIndex >= 0) {
263            if (mLastVisibleIndex < getLastIndex()) {
264                // should fill using cache instead
265                return false;
266            }
267            itemIndex = mLastVisibleIndex + 1;
268            rowIndex = getLocation(mLastVisibleIndex).row;
269            // find start item index of "previous column"
270            int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(true);
271            if (edgeLimitSearchIndex < 0) {
272                // if "previous colummn" is not found, using edgeLimit of
273                // first row currently in grid
274                edgeLimit = Integer.MIN_VALUE;
275                for (int i = 0; i < mNumRows; i++) {
276                    edgeLimit = mReversedFlow ? getRowMin(i) : getRowMax(i);
277                    if (edgeLimit != Integer.MIN_VALUE) {
278                        break;
279                    }
280                }
281            } else {
282                edgeLimit = mReversedFlow ? findRowMin(false, edgeLimitSearchIndex, null) :
283                        findRowMax(true, edgeLimitSearchIndex, null);
284            }
285            if (mReversedFlow ? getRowMin(rowIndex) <= edgeLimit
286                    : getRowMax(rowIndex) >= edgeLimit) {
287                // if current row exceeds previous column, fill from next row
288                rowIndex = rowIndex + 1;
289                if (rowIndex == mNumRows) {
290                    // start a new column and using edge limit of current column
291                    rowIndex = 0;
292                    edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null);
293                }
294            }
295            edgeLimitIsValid = true;
296        } else {
297            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
298            rowIndex = itemIndex % mNumRows;
299            edgeLimit = 0;
300            edgeLimitIsValid = false;
301        }
302
303        boolean filledOne = false;
304        while (true) {
305            // find end-most row edge (.high is biggest, or .low is smallest in reversed flow)
306            // fill from current row till last row so that each row will grow longer than
307            // the previous highest row.
308            for (; rowIndex < mNumRows; rowIndex++) {
309                // fill one item to a row
310                if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) {
311                    return filledOne;
312                }
313                int location = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex);
314                if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) {
315                    // nothing on the row
316                    if (rowIndex == 0) {
317                        location = mReversedFlow ? getRowMin(mNumRows - 1) : getRowMax(mNumRows - 1);
318                        if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) {
319                            location = location + (mReversedFlow ? -mMargin : mMargin);
320                        }
321                    } else {
322                        location = mReversedFlow ? getRowMax(rowIndex - 1) : getRowMin(rowIndex - 1);
323                    }
324                } else {
325                    location = location + (mReversedFlow ? -mMargin : mMargin);
326                }
327                int size = appendVisibleItemToRow(itemIndex++, rowIndex, location);
328                filledOne = true;
329                // fill more item to the row to make sure this row is longer than
330                // the previous highest row.
331                if (edgeLimitIsValid) {
332                    while (mReversedFlow ? location - size > edgeLimit :
333                            location + size < edgeLimit) {
334                        if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) {
335                            return filledOne;
336                        }
337                        location = location + (mReversedFlow ? - size - mMargin : size + mMargin);
338                        size = appendVisibleItemToRow(itemIndex++, rowIndex, location);
339                    }
340                } else {
341                    edgeLimitIsValid = true;
342                    edgeLimit = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex);
343                }
344            }
345            if (oneColumnMode) {
346                return filledOne;
347            }
348            edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null);
349            // start fill from row 0 again
350            rowIndex = 0;
351        }
352    }
353
354    @Override
355    protected boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) {
356        int itemIndex;
357        int rowIndex;
358        int edgeLimit;
359        boolean edgeLimitIsValid;
360        if (mFirstVisibleIndex >= 0) {
361            if (mFirstVisibleIndex > getFirstIndex()) {
362                // should fill using cache instead
363                return false;
364            }
365            itemIndex = mFirstVisibleIndex - 1;
366            rowIndex = getLocation(mFirstVisibleIndex).row;
367            // find start item index of "previous column"
368            int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(false);
369            if (edgeLimitSearchIndex < 0) {
370                // if "previous colummn" is not found, using edgeLimit of
371                // last row currently in grid and fill from upper row
372                rowIndex = rowIndex - 1;
373                edgeLimit = Integer.MAX_VALUE;
374                for (int i = mNumRows - 1; i >= 0; i--) {
375                    edgeLimit = mReversedFlow ? getRowMax(i) : getRowMin(i);
376                    if (edgeLimit != Integer.MAX_VALUE) {
377                        break;
378                    }
379                }
380            } else {
381                edgeLimit = mReversedFlow ? findRowMax(true, edgeLimitSearchIndex, null) :
382                        findRowMin(false, edgeLimitSearchIndex, null);
383            }
384            if (mReversedFlow ? getRowMax(rowIndex) >= edgeLimit
385                    : getRowMin(rowIndex) <= edgeLimit) {
386                // if current row exceeds previous column, fill from next row
387                rowIndex = rowIndex - 1;
388                if (rowIndex < 0) {
389                    // start a new column and using edge limit of current column
390                    rowIndex = mNumRows - 1;
391                    edgeLimit = mReversedFlow ? findRowMax(true, null) :
392                            findRowMin(false, null);
393                }
394            }
395            edgeLimitIsValid = true;
396        } else {
397            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
398            rowIndex = itemIndex % mNumRows;
399            edgeLimit = 0;
400            edgeLimitIsValid = false;
401        }
402        boolean filledOne = false;
403        while (true) {
404            // find start-most row edge (.low is smallest, or .high is largest in reversed flow)
405            // fill from current row till first row so that each row will grow longer than
406            // the previous lowest row.
407            for (; rowIndex >= 0; rowIndex--) {
408                // fill one item to a row
409                if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) {
410                    return filledOne;
411                }
412                int location = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex);
413                if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) {
414                    // nothing on the row
415                    if (rowIndex == mNumRows - 1) {
416                        location = mReversedFlow ? getRowMax(0) : getRowMin(0);
417                        if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) {
418                            location = location + (mReversedFlow ? mMargin : -mMargin);
419                        }
420                    } else {
421                        location = mReversedFlow ? getRowMin(rowIndex + 1) : getRowMax(rowIndex + 1);
422                    }
423                } else {
424                    location = location + (mReversedFlow ? mMargin : -mMargin);
425                }
426                int size = prependVisibleItemToRow(itemIndex--, rowIndex, location);
427                filledOne = true;
428
429                // fill more item to the row to make sure this row is longer than
430                // the previous highest row.
431                if (edgeLimitIsValid) {
432                    while (mReversedFlow ? location + size < edgeLimit :
433                            location - size > edgeLimit) {
434                        if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) {
435                            return filledOne;
436                        }
437                        location = location + (mReversedFlow ? size + mMargin : -size - mMargin);
438                        size = prependVisibleItemToRow(itemIndex--, rowIndex, location);
439                    }
440                } else {
441                    edgeLimitIsValid = true;
442                    edgeLimit = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex);
443                }
444            }
445            if (oneColumnMode) {
446                return filledOne;
447            }
448            edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null);
449            // start fill from last row again
450            rowIndex = mNumRows - 1;
451        }
452    }
453
454
455}
456