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