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