StaggeredGrid.java revision 79b86b227e6794937ec311522b50e727f8eec263
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 101 /** 102 * A constant representing a default starting index, indicating that the 103 * developer did not provide a start index. 104 */ 105 public static final int START_DEFAULT = -1; 106 107 // the first index that grid will layout 108 protected int mStartIndex = START_DEFAULT; 109 // the row to layout the first index 110 protected int mStartRow = START_DEFAULT; 111 112 protected int mFirstIndex = -1; 113 114 /** 115 * Sets the {@link Provider} for this staggered grid. 116 * 117 * @param provider The provider for this staggered grid. 118 */ 119 public void setProvider(Provider provider) { 120 mProvider = provider; 121 } 122 123 /** 124 * Sets the array of {@link Row}s to fill into. For views that represent a 125 * horizontal list, this will be the rows of the view. For views that 126 * represent a vertical list, this will be the columns. 127 * 128 * @param row The array of {@link Row}s to be filled. 129 */ 130 public final void setRows(Row[] row) { 131 if (row == null || row.length == 0) { 132 throw new IllegalArgumentException(); 133 } 134 mNumRows = row.length; 135 mRows = row; 136 mTmpItemPositionsInRows = new ArrayList[mNumRows]; 137 for (int i = 0; i < mNumRows; i++) { 138 mTmpItemPositionsInRows[i] = new ArrayList(32); 139 } 140 } 141 142 /** 143 * Returns the number of rows in the staggered grid. 144 */ 145 public final int getNumRows() { 146 return mNumRows; 147 } 148 149 /** 150 * Set the first item index and the row index to load when there are no 151 * items. 152 * 153 * @param startIndex the index of the first item 154 * @param startRow the index of the row 155 */ 156 public final void setStart(int startIndex, int startRow) { 157 mStartIndex = startIndex; 158 mStartRow = startRow; 159 } 160 161 /** 162 * Returns the first index in the staggered grid. 163 */ 164 public final int getFirstIndex() { 165 return mFirstIndex; 166 } 167 168 /** 169 * Returns the last index in the staggered grid. 170 */ 171 public final int getLastIndex() { 172 return mFirstIndex + mLocations.size() - 1; 173 } 174 175 /** 176 * Returns the size of the saved {@link Location}s. 177 */ 178 public final int getSize() { 179 return mLocations.size(); 180 } 181 182 /** 183 * Returns the {@link Location} at the given index. 184 */ 185 public final Location getLocation(int index) { 186 if (mLocations.size() == 0) { 187 return null; 188 } 189 return mLocations.get(index - mFirstIndex); 190 } 191 192 /** 193 * Removes the first element. 194 */ 195 public final void removeFirst() { 196 mFirstIndex++; 197 mLocations.popFirst(); 198 } 199 200 /** 201 * Removes the last element. 202 */ 203 public final void removeLast() { 204 mLocations.popLast(); 205 } 206 207 public final void debugPrint(PrintWriter pw) { 208 for (int i = 0, size = mLocations.size(); i < size; i++) { 209 Location loc = mLocations.get(i); 210 pw.print("<" + (mFirstIndex + i) + "," + loc.row + ">"); 211 pw.print(" "); 212 pw.println(); 213 } 214 } 215 216 protected final int getMaxHighRowIndex() { 217 int maxHighRowIndex = 0; 218 for (int i = 1; i < mNumRows; i++) { 219 if (mRows[i].high > mRows[maxHighRowIndex].high) { 220 maxHighRowIndex = i; 221 } 222 } 223 return maxHighRowIndex; 224 } 225 226 protected final int getMinHighRowIndex() { 227 int minHighRowIndex = 0; 228 for (int i = 1; i < mNumRows; i++) { 229 if (mRows[i].high < mRows[minHighRowIndex].high) { 230 minHighRowIndex = i; 231 } 232 } 233 return minHighRowIndex; 234 } 235 236 protected final Location appendItemToRow(int itemIndex, int rowIndex) { 237 Location loc = new Location(rowIndex); 238 if (mLocations.size() == 0) { 239 mFirstIndex = itemIndex; 240 } 241 mLocations.addLast(loc); 242 mProvider.createItem(itemIndex, rowIndex, true); 243 return loc; 244 } 245 246 /** 247 * Append items until the high edge reaches upTo. 248 */ 249 public abstract void appendItems(int upTo); 250 251 protected final int getMaxLowRowIndex() { 252 int maxLowRowIndex = 0; 253 for (int i = 1; i < mNumRows; i++) { 254 if (mRows[i].low > mRows[maxLowRowIndex].low) { 255 maxLowRowIndex = i; 256 } 257 } 258 return maxLowRowIndex; 259 } 260 261 protected final int getMinLowRowIndex() { 262 int minLowRowIndex = 0; 263 for (int i = 1; i < mNumRows; i++) { 264 if (mRows[i].low < mRows[minLowRowIndex].low) { 265 minLowRowIndex = i; 266 } 267 } 268 return minLowRowIndex; 269 } 270 271 protected final Location prependItemToRow(int itemIndex, int rowIndex) { 272 Location loc = new Location(rowIndex); 273 mFirstIndex = itemIndex; 274 mLocations.addFirst(loc); 275 mProvider.createItem(itemIndex, rowIndex, false); 276 return loc; 277 } 278 279 /** 280 * Return array of Lists for all rows, each List contains item positions 281 * on that row between startPos(included) and endPositions(included). 282 * Returned value is read only, do not change it. 283 */ 284 public final List<Integer>[] getItemPositionsInRows(int startPos, int endPos) { 285 for (int i = 0; i < mNumRows; i++) { 286 mTmpItemPositionsInRows[i].clear(); 287 } 288 if (startPos >= 0) { 289 for (int i = startPos; i <= endPos; i++) { 290 mTmpItemPositionsInRows[getLocation(i).row].add(i); 291 } 292 } 293 return mTmpItemPositionsInRows; 294 } 295 296 /** 297 * Prepend items until the low edge reaches downTo. 298 */ 299 public abstract void prependItems(int downTo); 300 301 /** 302 * Strip items, keep a contiguous subset of items; the subset should include 303 * at least one item on every row that currently has at least one item. 304 * 305 * <p> 306 * TODO: document this better 307 */ 308 public abstract void stripDownTo(int itemIndex); 309} 310