StaggeredGridDefault.java revision 3e534fdedcd360d1dd5bcc51661d93f71e57b31e
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        boolean breakWrapped = false;
139        if (mReversedFlow) {
140            value = edge;
141            if (mNumRows > 1) {
142                for (int i = indexLimit + 1; i <= mLastVisibleIndex; i++) {
143                    loc = getLocation(i);
144                    if (loc.row == mNumRows - 1) {
145                        breakWrapped = true;
146                    } else if (breakWrapped && loc.row != mNumRows - 1) {
147                        break;
148                    }
149                    edge += loc.offset;
150                    if (findLarge ? edge > value : edge < value) {
151                        value = edge;
152                        row = loc.row;
153                        index = i;
154                    }
155                }
156            }
157        } else {
158            value = edge + mProvider.getSize(indexLimit);
159            if (mNumRows > 1) {
160                for (int i = indexLimit - 1; i >= mFirstVisibleIndex; i--) {
161                    edge -= loc.offset;
162                    loc = getLocation(i);
163                    if (loc.row == 0) {
164                        breakWrapped = true;
165                    } else if (breakWrapped && loc.row != 0) {
166                        break;
167                    }
168                    int newValue = edge + mProvider.getSize(i);
169                    if (findLarge ? newValue > value : newValue < value) {
170                        value = newValue;
171                        row = loc.row;
172                        index = i;
173                    }
174                }
175            }
176        }
177        if (indices != null) {
178            indices[0] = row;
179            indices[1] = index;
180        }
181        return value;
182    }
183
184    /**
185     * Note this method has assumption that item is filled either in the same row
186     * next row of last item.  Search until row index wrapped.
187     */
188    @Override
189    public int findRowMin(boolean findLarge, int indexLimit, int[] indices) {
190        int value;
191        int edge = mProvider.getEdge(indexLimit);
192        Location loc = getLocation(indexLimit);
193        int row = loc.row;
194        int index = indexLimit;
195        boolean breakWrapped = false;
196        if (mReversedFlow) {
197            value = edge - mProvider.getSize(indexLimit);
198            if (mNumRows > 1) {
199                for (int i = indexLimit - 1; i >= mFirstVisibleIndex; i--) {
200                    edge -= loc.offset;
201                    loc = getLocation(i);
202                    if (loc.row == 0) {
203                        breakWrapped = true;
204                    } else if (breakWrapped && loc.row != 0) {
205                        break;
206                    }
207                    value = findLarge ? Math.max(value, edge - mProvider.getSize(i))
208                            : Math.min(value, edge - mProvider.getSize(i));
209                }
210            }
211        } else {
212            value = edge;
213            if (mNumRows > 1) {
214                for (int i = indexLimit + 1; i <= mLastVisibleIndex; i++) {
215                    loc = getLocation(i);
216                    if (loc.row == mNumRows - 1) {
217                        breakWrapped = true;
218                    } else if (breakWrapped && loc.row != mNumRows - 1) {
219                        break;
220                    }
221                    edge += loc.offset;
222                    value = findLarge ? Math.max(value, edge) : Math.min(value, edge);
223                    index = i;
224                }
225            }
226        }
227        if (indices != null) {
228            indices[0] = row;
229            indices[1] = index;
230        }
231        return value;
232    }
233
234    private int findRowEdgeLimitSearchIndex(boolean append) {
235        boolean wrapped = false;
236        if (append) {
237            for (int index = mLastVisibleIndex; index >= mFirstVisibleIndex; index--) {
238                int row = getLocation(index).row;
239                if (row == 0) {
240                    wrapped = true;
241                } else if (wrapped && row == mNumRows - 1) {
242                    return index;
243                }
244            }
245        } else {
246            for (int index = mFirstVisibleIndex; index <= mLastVisibleIndex; index++) {
247                int row = getLocation(index).row;
248                if (row == mNumRows - 1) {
249                    wrapped = true;
250                } else if (wrapped && row == 0) {
251                    return index;
252                }
253            }
254        }
255        return -1;
256    }
257
258    @Override
259    protected boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) {
260        final int count = mProvider.getCount();
261        int itemIndex;
262        int rowIndex;
263        int edgeLimit;
264        boolean edgeLimitIsValid;
265        if (mLastVisibleIndex >= 0) {
266            if (mLastVisibleIndex < getLastIndex()) {
267                // should fill using cache instead
268                return false;
269            }
270            itemIndex = mLastVisibleIndex + 1;
271            rowIndex = getLocation(mLastVisibleIndex).row;
272            // find start item index of "previous column"
273            int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(true);
274            if (edgeLimitSearchIndex < 0) {
275                // if "previous colummn" is not found, using edgeLimit of
276                // first row currently in grid
277                edgeLimit = Integer.MIN_VALUE;
278                for (int i = 0; i < mNumRows; i++) {
279                    edgeLimit = mReversedFlow ? getRowMin(i) : getRowMax(i);
280                    if (edgeLimit != Integer.MIN_VALUE) {
281                        break;
282                    }
283                }
284            } else {
285                edgeLimit = mReversedFlow ? findRowMin(false, edgeLimitSearchIndex, null) :
286                        findRowMax(true, edgeLimitSearchIndex, null);
287            }
288            if (mReversedFlow ? getRowMin(rowIndex) <= edgeLimit
289                    : getRowMax(rowIndex) >= edgeLimit) {
290                // if current row exceeds previous column, fill from next row
291                rowIndex = rowIndex + 1;
292                if (rowIndex == mNumRows) {
293                    // start a new column and using edge limit of current column
294                    rowIndex = 0;
295                    edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null);
296                }
297            }
298            edgeLimitIsValid = true;
299        } else {
300            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
301            rowIndex = itemIndex % mNumRows;
302            edgeLimit = 0;
303            edgeLimitIsValid = false;
304        }
305
306        boolean filledOne = false;
307        while (true) {
308            // find end-most row edge (.high is biggest, or .low is smallest in reversed flow)
309            // fill from current row till last row so that each row will grow longer than
310            // the previous highest row.
311            for (; rowIndex < mNumRows; rowIndex++) {
312                // fill one item to a row
313                if (itemIndex == count) {
314                    return filledOne;
315                }
316                int location = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex);
317                if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) {
318                    // nothing on the row
319                    if (rowIndex == 0) {
320                        location = mReversedFlow ? getRowMin(mNumRows - 1) : getRowMax(mNumRows - 1);
321                        if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) {
322                            location = location + (mReversedFlow ? -mMargin : mMargin);
323                        }
324                    } else {
325                        location = mReversedFlow ? getRowMax(rowIndex - 1) : getRowMin(rowIndex - 1);
326                    }
327                } else {
328                    location = location + (mReversedFlow ? -mMargin : mMargin);
329                }
330                int size = appendVisibleItemToRow(itemIndex++, rowIndex, location);
331                filledOne = true;
332                // fill more item to the row to make sure this row is longer than
333                // the previous highest row.
334                if (edgeLimitIsValid) {
335                    while (mReversedFlow ? location - size -mMargin > edgeLimit :
336                            location + size + mMargin < edgeLimit) {
337                        if (itemIndex == count) {
338                            return filledOne;
339                        }
340                        location = location + (mReversedFlow ? - size - mMargin : size + mMargin);
341                        size = appendVisibleItemToRow(itemIndex++, rowIndex, location);
342                    }
343                } else {
344                    edgeLimitIsValid = true;
345                    edgeLimit = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex);
346                }
347            }
348            edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null);
349            if (oneColumnMode || (mReversedFlow ? edgeLimit <= toLimit : edgeLimit >= toLimit)) {
350                return filledOne;
351            }
352            // start fill from row 0 again
353            rowIndex = 0;
354        }
355    }
356
357    @Override
358    protected boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) {
359        int itemIndex;
360        int rowIndex;
361        int edgeLimit;
362        boolean edgeLimitIsValid;
363        if (mFirstVisibleIndex >= 0) {
364            if (mFirstVisibleIndex > getFirstIndex()) {
365                // should fill using cache instead
366                return false;
367            }
368            itemIndex = mFirstVisibleIndex - 1;
369            rowIndex = getLocation(mFirstVisibleIndex).row;
370            // find start item index of "previous column"
371            int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(false);
372            if (edgeLimitSearchIndex < 0) {
373                // if "previous colummn" is not found, using edgeLimit of
374                // last row currently in grid and fill from upper row
375                rowIndex = rowIndex - 1;
376                edgeLimit = Integer.MAX_VALUE;
377                for (int i = mNumRows - 1; i >= 0; i--) {
378                    edgeLimit = mReversedFlow ? getRowMax(i) : getRowMin(i);
379                    if (edgeLimit != Integer.MAX_VALUE) {
380                        break;
381                    }
382                }
383            } else {
384                edgeLimit = mReversedFlow ? findRowMax(true, edgeLimitSearchIndex, null) :
385                        findRowMin(false, edgeLimitSearchIndex, null);
386            }
387            if (mReversedFlow ? getRowMax(rowIndex) >= edgeLimit
388                    : getRowMin(rowIndex) <= edgeLimit) {
389                // if current row exceeds previous column, fill from next row
390                rowIndex = rowIndex - 1;
391                if (rowIndex < 0) {
392                    // start a new column and using edge limit of current column
393                    rowIndex = mNumRows - 1;
394                    edgeLimit = mReversedFlow ? findRowMax(true, null) :
395                            findRowMin(false, null);
396                }
397            }
398            edgeLimitIsValid = true;
399        } else {
400            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
401            rowIndex = itemIndex % mNumRows;
402            edgeLimit = 0;
403            edgeLimitIsValid = false;
404        }
405        boolean filledOne = false;
406        while (true) {
407            // find start-most row edge (.low is smallest, or .high is largest in reversed flow)
408            // fill from current row till first row so that each row will grow longer than
409            // the previous lowest row.
410            for (; rowIndex >= 0; rowIndex--) {
411                // fill one item to a row
412                if (itemIndex < 0) {
413                    return filledOne;
414                }
415                int location = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex);
416                if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) {
417                    // nothing on the row
418                    if (rowIndex == mNumRows - 1) {
419                        location = mReversedFlow ? getRowMax(0) : getRowMin(0);
420                        if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) {
421                            location = location + (mReversedFlow ? mMargin : -mMargin);
422                        }
423                    } else {
424                        location = mReversedFlow ? getRowMin(rowIndex + 1) : getRowMax(rowIndex + 1);
425                    }
426                } else {
427                    location = location + (mReversedFlow ? mMargin : -mMargin);
428                }
429                int size = prependVisibleItemToRow(itemIndex--, rowIndex, location);
430                filledOne = true;
431
432                // fill more item to the row to make sure this row is longer than
433                // the previous highest row.
434                if (edgeLimitIsValid) {
435                    while (mReversedFlow ? location + size + mMargin < edgeLimit :
436                            location - size - mMargin > edgeLimit) {
437                        if (itemIndex < 0) {
438                            return filledOne;
439                        }
440                        location = location + (mReversedFlow ? size + mMargin : -size - mMargin);
441                        size = prependVisibleItemToRow(itemIndex--, rowIndex, location);
442                    }
443                } else {
444                    edgeLimitIsValid = true;
445                    edgeLimit = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex);
446                }
447            }
448            edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null);
449            if (oneColumnMode || (mReversedFlow ? edgeLimit >= toLimit : edgeLimit <= toLimit)) {
450                return filledOne;
451            }
452            // start fill from last row again
453            rowIndex = mNumRows - 1;
454        }
455    }
456
457
458}
459