Grid.java revision ef4e1b371215f9fffa9245159250740b611dfb4d
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.CircularIntArray; 17 18import java.io.PrintWriter; 19 20/** 21 * A grid is representation of single or multiple rows layout data structure and algorithm. 22 * Grid is the base class for single row, non-staggered grid and staggered grid. 23 * <p> 24 * To use the Grid, user must implement a Provider to create or remove visible item. 25 * Grid maintains a list of visible items. Visible items are created when 26 * user calls appendVisibleItems() or prependVisibleItems() with certain limitation 27 * (e.g. a max edge that append up to). Visible items are deleted when user calls 28 * removeInvisibleItemsAtEnd() or removeInvisibleItemsAtFront(). Grid's algorithm 29 * uses size of visible item returned from Provider.createItem() to decide which row 30 * to add a new visible item and may cache the algorithm results. User must call 31 * invalidateItemsAfter() when it detects item size changed to ask Grid to remove cached 32 * results. 33 */ 34abstract class Grid { 35 36 /** 37 * A constant representing a default starting index, indicating that the 38 * developer did not provide a start index. 39 */ 40 public static final int START_DEFAULT = -1; 41 42 /** 43 * When user uses Grid, he should provide count of items and 44 * the method to create item and remove item. 45 */ 46 public static interface Provider { 47 48 /** 49 * Return how many items (are in the adapter). 50 */ 51 public abstract int getCount(); 52 53 /** 54 * Create visible item and where the provider should measure it. 55 * The call is always followed by addItem(). 56 * @param index 0-based index of the item in provider 57 * @param append True if new item is after last visible item, false if new item is 58 * before first visible item. 59 * @param item item[0] returns created item that will be passed in addItem() call. 60 * @return length of the item. 61 */ 62 public abstract int createItem(int index, boolean append, Object[] item); 63 64 /** 65 * add item to given row and given edge. The call is always after createItem(). 66 * @param item The object returned by createItem() 67 * @param index 0-based index of the item in provider 68 * @param length The size of the object 69 * @param rowIndex Row index to put the item 70 * @param edge min_edge if not reversed or max_edge if reversed. 71 */ 72 public abstract void addItem(Object item, int index, int length, int rowIndex, int edge); 73 74 /** 75 * Remove visible item at index. 76 * @param index 0-based index of the item in provider 77 */ 78 public abstract void removeItem(int index); 79 80 /** 81 * Get edge of an existing visible item. edge will be the min_edge 82 * if not reversed or the max_edge if reversed. 83 * @param index 0-based index of the item in provider 84 */ 85 public abstract int getEdge(int index); 86 87 /** 88 * Get size of an existing visible item. 89 * @param index 0-based index of the item in provider 90 */ 91 public abstract int getSize(int index); 92 } 93 94 /** 95 * Cached representation of an item in Grid. May be subclassed. 96 */ 97 public static class Location { 98 /** 99 * The index of the row for this Location. 100 */ 101 public int row; 102 103 public Location(int row) { 104 this.row = row; 105 } 106 } 107 108 protected Provider mProvider; 109 protected boolean mReversedFlow; 110 protected int mSpacing; 111 protected int mNumRows; 112 protected int mFirstVisibleIndex = -1; 113 protected int mLastVisibleIndex = -1; 114 115 protected CircularIntArray[] mTmpItemPositionsInRows; 116 117 // the first index that grid will layout 118 protected int mStartIndex = START_DEFAULT; 119 120 /** 121 * Creates a single or multiple rows (can be staggered or not staggered) grid 122 */ 123 public static Grid createGrid(int rows) { 124 Grid grid; 125 if (rows == 1) { 126 grid = new SingleRow(); 127 } else { 128 // TODO support non staggered multiple rows grid 129 grid = new StaggeredGridDefault(); 130 grid.setNumRows(rows); 131 } 132 return grid; 133 } 134 135 /** 136 * Sets the space between items in a row 137 */ 138 public final void setSpacing(int spacing) { 139 mSpacing = spacing; 140 } 141 142 /** 143 * Sets if reversed flow (rtl) 144 */ 145 public final void setReversedFlow(boolean reversedFlow) { 146 mReversedFlow = reversedFlow; 147 } 148 149 /** 150 * Returns true if reversed flow (rtl) 151 */ 152 public boolean isReversedFlow() { 153 return mReversedFlow; 154 } 155 156 /** 157 * Sets the {@link Provider} for this grid. 158 * 159 * @param provider The provider for this grid. 160 */ 161 public void setProvider(Provider provider) { 162 mProvider = provider; 163 } 164 165 /** 166 * Sets the first item index to create when there are no items. 167 * 168 * @param startIndex the index of the first item 169 */ 170 public void setStart(int startIndex) { 171 mStartIndex = startIndex; 172 } 173 174 /** 175 * Returns the number of rows in the grid. 176 */ 177 public int getNumRows() { 178 return mNumRows; 179 } 180 181 /** 182 * Sets number of rows to fill into. For views that represent a 183 * horizontal list, this will be the rows of the view. For views that 184 * represent a vertical list, this will be the columns. 185 * 186 * @param numRows numberOfRows 187 */ 188 void setNumRows(int numRows) { 189 if (numRows <= 0) { 190 throw new IllegalArgumentException(); 191 } 192 if (mNumRows == numRows) { 193 return; 194 } 195 mNumRows = numRows; 196 mTmpItemPositionsInRows = new CircularIntArray[mNumRows]; 197 for (int i = 0; i < mNumRows; i++) { 198 mTmpItemPositionsInRows[i] = new CircularIntArray(); 199 } 200 } 201 202 /** 203 * Returns index of first visible item in the staggered grid. Returns negative value 204 * if no visible item. 205 */ 206 public final int getFirstVisibleIndex() { 207 return mFirstVisibleIndex; 208 } 209 210 /** 211 * Returns index of last visible item in the staggered grid. Returns negative value 212 * if no visible item. 213 */ 214 public final int getLastVisibleIndex() { 215 return mLastVisibleIndex; 216 } 217 218 /** 219 * Reset visible indices and keep cache (if exists) 220 */ 221 public void resetVisibleIndex() { 222 mFirstVisibleIndex = mLastVisibleIndex = -1; 223 } 224 225 /** 226 * Invalidate items after or equal to index. This will remove visible items 227 * after that and invalidate cache of layout results after that. 228 */ 229 public void invalidateItemsAfter(int index) { 230 if (index < 0) { 231 return; 232 } 233 if (mLastVisibleIndex < 0) { 234 return; 235 } 236 while (mLastVisibleIndex >= index) { 237 mProvider.removeItem(mLastVisibleIndex); 238 mLastVisibleIndex--; 239 } 240 resetVisibleIndexIfEmpty(); 241 if (getFirstVisibleIndex() < 0) { 242 setStart(index); 243 } 244 } 245 246 /** 247 * Gets the row index of item at given index. 248 */ 249 public final int getRowIndex(int index) { 250 return getLocation(index).row; 251 } 252 253 /** 254 * Gets {@link Location} of item. The return object is read only and temporarily. 255 */ 256 public abstract Location getLocation(int index); 257 258 /** 259 * Finds the largest or smallest row min edge of visible items, 260 * the row index is returned in indices[0], the item index is returned in indices[1]. 261 */ 262 public final int findRowMin(boolean findLarge, int[] indices) { 263 return findRowMin(findLarge, mReversedFlow ? mLastVisibleIndex : mFirstVisibleIndex, 264 indices); 265 } 266 267 /** 268 * Finds the largest or smallest row min edge of visible items, starts searching from 269 * indexLimit, the row index is returned in indices[0], the item index is returned in indices[1]. 270 */ 271 protected abstract int findRowMin(boolean findLarge, int indexLimit, int[] rowIndex); 272 273 /** 274 * Finds the largest or smallest row max edge of visible items, the row index is returned in 275 * indices[0], the item index is returned in indices[1]. 276 */ 277 public final int findRowMax(boolean findLarge, int[] indices) { 278 return findRowMax(findLarge, mReversedFlow ? mFirstVisibleIndex : mLastVisibleIndex, 279 indices); 280 } 281 282 /** 283 * Find largest or smallest row max edge of visible items, starts searching from indexLimit, 284 * the row index is returned in indices[0], the item index is returned in indices[1]. 285 */ 286 protected abstract int findRowMax(boolean findLarge, int indexLimit, int[] indices); 287 288 /** 289 * Returns true if appending item has reached "toLimit" 290 */ 291 protected final boolean checkAppendOverLimit(int toLimit) { 292 if (mLastVisibleIndex < 0) { 293 return false; 294 } 295 return mReversedFlow ? findRowMin(true, null) <= toLimit + mSpacing : 296 findRowMax(false, null) >= toLimit - mSpacing; 297 } 298 299 /** 300 * Returns true if prepending item has reached "toLimit" 301 */ 302 protected final boolean checkPrependOverLimit(int toLimit) { 303 if (mLastVisibleIndex < 0) { 304 return false; 305 } 306 return mReversedFlow ? findRowMax(false, null) >= toLimit - mSpacing : 307 findRowMin(true, null) <= toLimit + mSpacing; 308 } 309 310 /** 311 * Return array of int array for all rows, each int array contains visible item positions 312 * in pair on that row between startPos(included) and endPositions(included). 313 * Returned value is read only, do not change it. 314 * <p> 315 * E.g. First row has 3,7,8, second row has 4,5,6. 316 * getItemPositionsInRows(3, 8) returns { {3,3,7,8}, {4,6} } 317 */ 318 public abstract CircularIntArray[] getItemPositionsInRows(int startPos, int endPos); 319 320 /** 321 * Return array of int array for all rows, each int array contains visible item positions 322 * in pair on that row. 323 * Returned value is read only, do not change it. 324 * <p> 325 * E.g. First row has 3,7,8, second row has 4,5,6 { {3,3,7,8}, {4,6} } 326 */ 327 public final CircularIntArray[] getItemPositionsInRows() { 328 return getItemPositionsInRows(getFirstVisibleIndex(), getLastVisibleIndex()); 329 } 330 331 /** 332 * Prepends items and stops after one column is filled. 333 * (i.e. filled items from row 0 to row mNumRows - 1) 334 * @return true if at least one item is filled. 335 */ 336 public final boolean prependOneColumnVisibleItems() { 337 return prependVisibleItems(mReversedFlow ? Integer.MIN_VALUE : Integer.MAX_VALUE, true); 338 } 339 340 /** 341 * Prepends items until first item or reaches toLimit (min edge when not reversed or 342 * max edge when reversed) 343 */ 344 public final void prependVisibleItems(int toLimit) { 345 prependVisibleItems(toLimit, false); 346 } 347 348 /** 349 * Prepends items until first item or reaches toLimit (min edge when not reversed or 350 * max edge when reversed). 351 * @param oneColumnMode true when fills one column and stops, false 352 * when checks if condition matches before filling first column. 353 * @return true if at least one item is filled. 354 */ 355 protected abstract boolean prependVisibleItems(int toLimit, boolean oneColumnMode); 356 357 /** 358 * Appends items and stops after one column is filled. 359 * (i.e. filled items from row 0 to row mNumRows - 1) 360 * @return true if at least one item is filled. 361 */ 362 public boolean appendOneColumnVisibleItems() { 363 return appendVisibleItems(mReversedFlow ? Integer.MAX_VALUE : Integer.MIN_VALUE, true); 364 } 365 366 /** 367 * Append items until last item or reaches toLimit (max edge when not 368 * reversed or min edge when reversed) 369 */ 370 public final void appendVisibleItems(int toLimit) { 371 appendVisibleItems(toLimit, false); 372 } 373 374 /** 375 * Appends items until last or reaches toLimit (high edge when not 376 * reversed or low edge when reversed). 377 * @param oneColumnMode True when fills one column and stops, false 378 * when checks if condition matches before filling first column. 379 * @return true if filled at least one item 380 */ 381 protected abstract boolean appendVisibleItems(int toLimit, boolean oneColumnMode); 382 383 /** 384 * Removes invisible items from end until reaches item at aboveIndex or toLimit. 385 */ 386 public void removeInvisibleItemsAtEnd(int aboveIndex, int toLimit) { 387 while(mLastVisibleIndex >= mFirstVisibleIndex && mLastVisibleIndex > aboveIndex) { 388 boolean offEnd = !mReversedFlow ? mProvider.getEdge(mLastVisibleIndex) >= toLimit 389 : mProvider.getEdge(mLastVisibleIndex) <= toLimit; 390 if (offEnd) { 391 mProvider.removeItem(mLastVisibleIndex); 392 mLastVisibleIndex--; 393 } else { 394 break; 395 } 396 } 397 resetVisibleIndexIfEmpty(); 398 } 399 400 /** 401 * Removes invisible items from front until reaches item at belowIndex or toLimit. 402 */ 403 public void removeInvisibleItemsAtFront(int belowIndex, int toLimit) { 404 while(mLastVisibleIndex >= mFirstVisibleIndex && mFirstVisibleIndex < belowIndex) { 405 boolean offFront = !mReversedFlow ? mProvider.getEdge(mFirstVisibleIndex) 406 + mProvider.getSize(mFirstVisibleIndex) <= toLimit 407 : mProvider.getEdge(mFirstVisibleIndex) 408 - mProvider.getSize(mFirstVisibleIndex) >= toLimit; 409 if (offFront) { 410 mProvider.removeItem(mFirstVisibleIndex); 411 mFirstVisibleIndex++; 412 } else { 413 break; 414 } 415 } 416 resetVisibleIndexIfEmpty(); 417 } 418 419 private void resetVisibleIndexIfEmpty() { 420 if (mLastVisibleIndex < mFirstVisibleIndex) { 421 resetVisibleIndex(); 422 } 423 } 424 425 public abstract void debugPrint(PrintWriter pw); 426} 427