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