StaggeredGrid.java revision e0e66a21916f94ebbced0d1ffe3dc652c9c7a15e
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.CircularArray;
17
18import java.io.PrintWriter;
19import java.util.ArrayList;
20import java.util.List;
21
22/**
23 * A dynamic data structure that maintains staggered grid position information
24 * for each individual child. The algorithm ensures that each row will be kept
25 * as balanced as possible when prepending and appending a child.
26 *
27 * <p>
28 * You may keep view {@link StaggeredGrid.Location} inside StaggeredGrid as much
29 * as possible since prepending and appending views is not symmetric: layout
30 * going from 0 to N will likely produce a different result than layout going
31 * from N to 0 for the staggered cases. If a user scrolls from 0 to N then
32 * scrolls back to 0 and we don't keep history location information, edges of
33 * the very beginning of rows will not be aligned. It is recommended to keep a
34 * list of tens of thousands of {@link StaggeredGrid.Location}s which will be
35 * big enough to remember a typical user's scroll history. There are situations
36 * where StaggeredGrid falls back to the simple case where we do not need save a
37 * huge list of locations inside StaggeredGrid:
38 * <ul>
39 *   <li>Only one row (e.g., a single row listview)</li>
40 *   <li> Each item has the same length (not staggered at all)</li>
41 * </ul>
42 *
43 * <p>
44 * This class is abstract and can be replaced with different implementations.
45 */
46abstract class StaggeredGrid {
47
48    /**
49     * TODO: document this
50     */
51    public static interface Provider {
52        /**
53         * Return how many items are in the adapter.
54         */
55        public abstract int getCount();
56
57        /**
58         * Create the object at a given row.
59         */
60        public abstract void createItem(int index, int row, boolean append);
61    }
62
63    /**
64     * Location of an item in the grid. For now it only saves row index but
65     * more information may be added in the future.
66     */
67    public final static class Location {
68        /**
69         * The index of the row for this Location.
70         */
71        public final int row;
72
73        /**
74         * Create a Location with the given row index.
75         */
76        public Location(int row) {
77            this.row = row;
78        }
79    }
80
81    /**
82     * TODO: document this
83     */
84    public final static class Row {
85        /**
86         * first view start location
87         */
88        public int low;
89        /**
90         * last view end location
91         */
92        public int high;
93    }
94
95    protected Provider mProvider;
96    protected int mNumRows = 1; // mRows.length
97    protected Row[] mRows;
98    protected CircularArray<Location> mLocations = new CircularArray<Location>(64);
99    private ArrayList<Integer>[] mTmpItemPositionsInRows;
100    protected boolean mReversedFlow;
101
102    /**
103     * A constant representing a default starting index, indicating that the
104     * developer did not provide a start index.
105     */
106    public static final int START_DEFAULT = -1;
107
108    // the first index that grid will layout
109    protected int mStartIndex = START_DEFAULT;
110    // the row to layout the first index
111    protected int mStartRow = START_DEFAULT;
112
113    protected int mFirstIndex = -1;
114
115    public void setReversedFlow(boolean reversedFlow) {
116        mReversedFlow = reversedFlow;
117    }
118
119    /**
120     * Sets the {@link Provider} for this staggered grid.
121     *
122     * @param provider The provider for this staggered grid.
123     */
124    public void setProvider(Provider provider) {
125        mProvider = provider;
126    }
127
128    /**
129     * Sets the array of {@link Row}s to fill into. For views that represent a
130     * horizontal list, this will be the rows of the view. For views that
131     * represent a vertical list, this will be the columns.
132     *
133     * @param row The array of {@link Row}s to be filled.
134     */
135    public final void setRows(Row[] row) {
136        if (row == null || row.length == 0) {
137            throw new IllegalArgumentException();
138        }
139        mNumRows = row.length;
140        mRows = row;
141        mTmpItemPositionsInRows = new ArrayList[mNumRows];
142        for (int i = 0; i < mNumRows; i++) {
143            mTmpItemPositionsInRows[i] = new ArrayList(32);
144        }
145    }
146
147    /**
148     * Returns the number of rows in the staggered grid.
149     */
150    public final int getNumRows() {
151        return mNumRows;
152    }
153
154    /**
155     * Set the first item index and the row index to load when there are no
156     * items.
157     *
158     * @param startIndex the index of the first item
159     * @param startRow the index of the row
160     */
161    public final void setStart(int startIndex, int startRow) {
162        mStartIndex = startIndex;
163        mStartRow = startRow;
164    }
165
166    /**
167     * Returns the first index in the staggered grid.
168     */
169    public final int getFirstIndex() {
170        return mFirstIndex;
171    }
172
173    /**
174     * Returns the last index in the staggered grid.
175     */
176    public final int getLastIndex() {
177        return mFirstIndex + mLocations.size() - 1;
178    }
179
180    /**
181     * Returns the size of the saved {@link Location}s.
182     */
183    public final int getSize() {
184        return mLocations.size();
185    }
186
187    /**
188     * Returns the {@link Location} at the given index.
189     */
190    public final Location getLocation(int index) {
191        if (mLocations.size() == 0) {
192            return null;
193        }
194        return mLocations.get(index - mFirstIndex);
195    }
196
197    /**
198     * Removes the first element.
199     */
200    public final void removeFirst() {
201        mFirstIndex++;
202        mLocations.popFirst();
203    }
204
205    /**
206     * Removes the last element.
207     */
208    public final void removeLast() {
209        mLocations.popLast();
210    }
211
212    public final void debugPrint(PrintWriter pw) {
213        for (int i = 0, size = mLocations.size(); i < size; i++) {
214            Location loc = mLocations.get(i);
215            pw.print("<" + (mFirstIndex + i) + "," + loc.row + ">");
216            pw.print(" ");
217            pw.println();
218        }
219    }
220
221    protected final int getMaxHighRowIndex() {
222        int maxHighRowIndex = 0;
223        for (int i = 1; i < mNumRows; i++) {
224            if (mRows[i].high > mRows[maxHighRowIndex].high) {
225                maxHighRowIndex = i;
226            }
227        }
228        return maxHighRowIndex;
229    }
230
231    protected final int getMinHighRowIndex() {
232        int minHighRowIndex = 0;
233        for (int i = 1; i < mNumRows; i++) {
234            if (mRows[i].high < mRows[minHighRowIndex].high) {
235                minHighRowIndex = i;
236            }
237        }
238        return minHighRowIndex;
239    }
240
241    protected final Location appendItemToRow(int itemIndex, int rowIndex) {
242        Location loc = new Location(rowIndex);
243        if (mLocations.size() == 0) {
244            mFirstIndex = itemIndex;
245        }
246        mLocations.addLast(loc);
247        mProvider.createItem(itemIndex, rowIndex, true);
248        return loc;
249    }
250
251    /**
252     * Append items until the high edge reaches upTo.
253     */
254    public abstract void appendItems(int toLimit);
255
256    protected final int getMaxLowRowIndex() {
257        int maxLowRowIndex = 0;
258        for (int i = 1; i < mNumRows; i++) {
259            if (mRows[i].low > mRows[maxLowRowIndex].low) {
260                maxLowRowIndex = i;
261            }
262        }
263        return maxLowRowIndex;
264    }
265
266    protected final int getMinLowRowIndex() {
267        int minLowRowIndex = 0;
268        for (int i = 1; i < mNumRows; i++) {
269            if (mRows[i].low < mRows[minLowRowIndex].low) {
270                minLowRowIndex = i;
271            }
272        }
273        return minLowRowIndex;
274    }
275
276    protected final Location prependItemToRow(int itemIndex, int rowIndex) {
277        Location loc = new Location(rowIndex);
278        mFirstIndex = itemIndex;
279        mLocations.addFirst(loc);
280        mProvider.createItem(itemIndex, rowIndex, false);
281        return loc;
282    }
283
284    /**
285     * Return array of Lists for all rows, each List contains item positions
286     * on that row between startPos(included) and endPositions(included).
287     * Returned value is read only, do not change it.
288     */
289    public final List<Integer>[] getItemPositionsInRows(int startPos, int endPos) {
290        for (int i = 0; i < mNumRows; i++) {
291            mTmpItemPositionsInRows[i].clear();
292        }
293        if (startPos >= 0) {
294            for (int i = startPos; i <= endPos; i++) {
295                mTmpItemPositionsInRows[getLocation(i).row].add(i);
296            }
297        }
298        return mTmpItemPositionsInRows;
299    }
300
301    /**
302     * Prepend items until the low edge reaches downTo.
303     */
304    public abstract void prependItems(int toLimit);
305
306    /**
307     * Strip items, keep a contiguous subset of items; the subset should include
308     * at least one item on every row that currently has at least one item.
309     *
310     * <p>
311     * TODO: document this better
312     */
313    public abstract void stripDownTo(int itemIndex);
314}
315