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