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