StaggeredGridDefault.java revision 3e534fdedcd360d1dd5bcc51661d93f71e57b31e
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 16/** 17 * A default implementation of {@link StaggeredGrid}. 18 * 19 * This implementation tries to fill items in consecutive row order. The next 20 * item is always in same row or in the next row. 21 */ 22final class StaggeredGridDefault extends StaggeredGrid { 23 24 @Override 25 protected int updateFirstVisibleOffset(int prependedItemSize) { 26 Location firstVisible = getLocation(mFirstVisibleIndex); 27 // Find a cached item in same row, if not found, just use last item. 28 int index = mFirstVisibleIndex - 1; 29 boolean foundCachedItemInSameRow = false; 30 while (index >= mFirstIndex) { 31 Location loc = getLocation(index); 32 if (loc.row == firstVisible.row) { 33 foundCachedItemInSameRow = true; 34 break; 35 } 36 index--; 37 } 38 if (!foundCachedItemInSameRow) { 39 index = mFirstVisibleIndex - 1; 40 } 41 // Assuming mFirstVisibleIndex is next to index on the same row, so the 42 // sum of offset of [index + 1, mFirstVisibleIndex] should be size of the item 43 // plus margin. 44 int offset = isReversedFlow() ? -getLocation(index).size - mMargin: 45 getLocation(index).size + mMargin; 46 for (int i = index + 1; i < mFirstVisibleIndex; i++) { 47 offset -= getLocation(index).offset; 48 } 49 firstVisible.offset = offset; 50 return offset; 51 } 52 53 /** 54 * Returns the max edge value of item (visible or cached) in a row. This 55 * will be the place to append or prepend item not in cache. 56 */ 57 int getRowMax(int rowIndex) { 58 if (mFirstVisibleIndex < 0) { 59 return Integer.MIN_VALUE; 60 } 61 if (mReversedFlow) { 62 int edge = mProvider.getEdge(mFirstVisibleIndex); 63 if (getLocation(mFirstVisibleIndex).row == rowIndex) { 64 return edge; 65 } 66 for (int i = mFirstVisibleIndex + 1; i <= getLastIndex(); i++) { 67 Location loc = getLocation(i); 68 edge += loc.offset; 69 if (loc.row == rowIndex) { 70 return edge; 71 } 72 } 73 } else { 74 int edge = mProvider.getEdge(mLastVisibleIndex); 75 Location loc = getLocation(mLastVisibleIndex); 76 if (loc.row == rowIndex) { 77 return edge + loc.size; 78 } 79 for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) { 80 edge -= loc.offset; 81 loc = getLocation(i); 82 if (loc.row == rowIndex) { 83 return edge + loc.size; 84 } 85 } 86 } 87 return Integer.MIN_VALUE; 88 } 89 90 /** 91 * Returns the min edge value of item (visible or cached) in a row. This 92 * will be the place to prepend or append item not in cache. 93 */ 94 int getRowMin(int rowIndex) { 95 if (mFirstVisibleIndex < 0) { 96 return Integer.MAX_VALUE; 97 } 98 if (mReversedFlow) { 99 int edge = mProvider.getEdge(mLastVisibleIndex); 100 Location loc = getLocation(mLastVisibleIndex); 101 if (loc.row == rowIndex) { 102 return edge - loc.size; 103 } 104 for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) { 105 edge -= loc.offset; 106 loc = getLocation(i); 107 if (loc.row == rowIndex) { 108 return edge - loc.size; 109 } 110 } 111 } else { 112 int edge = mProvider.getEdge(mFirstVisibleIndex); 113 if (getLocation(mFirstVisibleIndex).row == rowIndex) { 114 return edge; 115 } 116 for (int i = mFirstVisibleIndex + 1; i <= getLastIndex() ; i++) { 117 Location loc = getLocation(i); 118 edge += loc.offset; 119 if (loc.row == rowIndex) { 120 return edge; 121 } 122 } 123 } 124 return Integer.MAX_VALUE; 125 } 126 127 /** 128 * Note this method has assumption that item is filled either in the same row 129 * next row of last item. Search until row index wrapped. 130 */ 131 @Override 132 public int findRowMax(boolean findLarge, int indexLimit, int[] indices) { 133 int value; 134 int edge = mProvider.getEdge(indexLimit); 135 Location loc = getLocation(indexLimit); 136 int row = loc.row; 137 int index = indexLimit; 138 boolean breakWrapped = false; 139 if (mReversedFlow) { 140 value = edge; 141 if (mNumRows > 1) { 142 for (int i = indexLimit + 1; i <= mLastVisibleIndex; i++) { 143 loc = getLocation(i); 144 if (loc.row == mNumRows - 1) { 145 breakWrapped = true; 146 } else if (breakWrapped && loc.row != mNumRows - 1) { 147 break; 148 } 149 edge += loc.offset; 150 if (findLarge ? edge > value : edge < value) { 151 value = edge; 152 row = loc.row; 153 index = i; 154 } 155 } 156 } 157 } else { 158 value = edge + mProvider.getSize(indexLimit); 159 if (mNumRows > 1) { 160 for (int i = indexLimit - 1; i >= mFirstVisibleIndex; i--) { 161 edge -= loc.offset; 162 loc = getLocation(i); 163 if (loc.row == 0) { 164 breakWrapped = true; 165 } else if (breakWrapped && loc.row != 0) { 166 break; 167 } 168 int newValue = edge + mProvider.getSize(i); 169 if (findLarge ? newValue > value : newValue < value) { 170 value = newValue; 171 row = loc.row; 172 index = i; 173 } 174 } 175 } 176 } 177 if (indices != null) { 178 indices[0] = row; 179 indices[1] = index; 180 } 181 return value; 182 } 183 184 /** 185 * Note this method has assumption that item is filled either in the same row 186 * next row of last item. Search until row index wrapped. 187 */ 188 @Override 189 public int findRowMin(boolean findLarge, int indexLimit, int[] indices) { 190 int value; 191 int edge = mProvider.getEdge(indexLimit); 192 Location loc = getLocation(indexLimit); 193 int row = loc.row; 194 int index = indexLimit; 195 boolean breakWrapped = false; 196 if (mReversedFlow) { 197 value = edge - mProvider.getSize(indexLimit); 198 if (mNumRows > 1) { 199 for (int i = indexLimit - 1; i >= mFirstVisibleIndex; i--) { 200 edge -= loc.offset; 201 loc = getLocation(i); 202 if (loc.row == 0) { 203 breakWrapped = true; 204 } else if (breakWrapped && loc.row != 0) { 205 break; 206 } 207 value = findLarge ? Math.max(value, edge - mProvider.getSize(i)) 208 : Math.min(value, edge - mProvider.getSize(i)); 209 } 210 } 211 } else { 212 value = edge; 213 if (mNumRows > 1) { 214 for (int i = indexLimit + 1; i <= mLastVisibleIndex; i++) { 215 loc = getLocation(i); 216 if (loc.row == mNumRows - 1) { 217 breakWrapped = true; 218 } else if (breakWrapped && loc.row != mNumRows - 1) { 219 break; 220 } 221 edge += loc.offset; 222 value = findLarge ? Math.max(value, edge) : Math.min(value, edge); 223 index = i; 224 } 225 } 226 } 227 if (indices != null) { 228 indices[0] = row; 229 indices[1] = index; 230 } 231 return value; 232 } 233 234 private int findRowEdgeLimitSearchIndex(boolean append) { 235 boolean wrapped = false; 236 if (append) { 237 for (int index = mLastVisibleIndex; index >= mFirstVisibleIndex; index--) { 238 int row = getLocation(index).row; 239 if (row == 0) { 240 wrapped = true; 241 } else if (wrapped && row == mNumRows - 1) { 242 return index; 243 } 244 } 245 } else { 246 for (int index = mFirstVisibleIndex; index <= mLastVisibleIndex; index++) { 247 int row = getLocation(index).row; 248 if (row == mNumRows - 1) { 249 wrapped = true; 250 } else if (wrapped && row == 0) { 251 return index; 252 } 253 } 254 } 255 return -1; 256 } 257 258 @Override 259 protected boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) { 260 final int count = mProvider.getCount(); 261 int itemIndex; 262 int rowIndex; 263 int edgeLimit; 264 boolean edgeLimitIsValid; 265 if (mLastVisibleIndex >= 0) { 266 if (mLastVisibleIndex < getLastIndex()) { 267 // should fill using cache instead 268 return false; 269 } 270 itemIndex = mLastVisibleIndex + 1; 271 rowIndex = getLocation(mLastVisibleIndex).row; 272 // find start item index of "previous column" 273 int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(true); 274 if (edgeLimitSearchIndex < 0) { 275 // if "previous colummn" is not found, using edgeLimit of 276 // first row currently in grid 277 edgeLimit = Integer.MIN_VALUE; 278 for (int i = 0; i < mNumRows; i++) { 279 edgeLimit = mReversedFlow ? getRowMin(i) : getRowMax(i); 280 if (edgeLimit != Integer.MIN_VALUE) { 281 break; 282 } 283 } 284 } else { 285 edgeLimit = mReversedFlow ? findRowMin(false, edgeLimitSearchIndex, null) : 286 findRowMax(true, edgeLimitSearchIndex, null); 287 } 288 if (mReversedFlow ? getRowMin(rowIndex) <= edgeLimit 289 : getRowMax(rowIndex) >= edgeLimit) { 290 // if current row exceeds previous column, fill from next row 291 rowIndex = rowIndex + 1; 292 if (rowIndex == mNumRows) { 293 // start a new column and using edge limit of current column 294 rowIndex = 0; 295 edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null); 296 } 297 } 298 edgeLimitIsValid = true; 299 } else { 300 itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0; 301 rowIndex = itemIndex % mNumRows; 302 edgeLimit = 0; 303 edgeLimitIsValid = false; 304 } 305 306 boolean filledOne = false; 307 while (true) { 308 // find end-most row edge (.high is biggest, or .low is smallest in reversed flow) 309 // fill from current row till last row so that each row will grow longer than 310 // the previous highest row. 311 for (; rowIndex < mNumRows; rowIndex++) { 312 // fill one item to a row 313 if (itemIndex == count) { 314 return filledOne; 315 } 316 int location = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex); 317 if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) { 318 // nothing on the row 319 if (rowIndex == 0) { 320 location = mReversedFlow ? getRowMin(mNumRows - 1) : getRowMax(mNumRows - 1); 321 if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) { 322 location = location + (mReversedFlow ? -mMargin : mMargin); 323 } 324 } else { 325 location = mReversedFlow ? getRowMax(rowIndex - 1) : getRowMin(rowIndex - 1); 326 } 327 } else { 328 location = location + (mReversedFlow ? -mMargin : mMargin); 329 } 330 int size = appendVisibleItemToRow(itemIndex++, rowIndex, location); 331 filledOne = true; 332 // fill more item to the row to make sure this row is longer than 333 // the previous highest row. 334 if (edgeLimitIsValid) { 335 while (mReversedFlow ? location - size -mMargin > edgeLimit : 336 location + size + mMargin < edgeLimit) { 337 if (itemIndex == count) { 338 return filledOne; 339 } 340 location = location + (mReversedFlow ? - size - mMargin : size + mMargin); 341 size = appendVisibleItemToRow(itemIndex++, rowIndex, location); 342 } 343 } else { 344 edgeLimitIsValid = true; 345 edgeLimit = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex); 346 } 347 } 348 edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null); 349 if (oneColumnMode || (mReversedFlow ? edgeLimit <= toLimit : edgeLimit >= toLimit)) { 350 return filledOne; 351 } 352 // start fill from row 0 again 353 rowIndex = 0; 354 } 355 } 356 357 @Override 358 protected boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) { 359 int itemIndex; 360 int rowIndex; 361 int edgeLimit; 362 boolean edgeLimitIsValid; 363 if (mFirstVisibleIndex >= 0) { 364 if (mFirstVisibleIndex > getFirstIndex()) { 365 // should fill using cache instead 366 return false; 367 } 368 itemIndex = mFirstVisibleIndex - 1; 369 rowIndex = getLocation(mFirstVisibleIndex).row; 370 // find start item index of "previous column" 371 int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(false); 372 if (edgeLimitSearchIndex < 0) { 373 // if "previous colummn" is not found, using edgeLimit of 374 // last row currently in grid and fill from upper row 375 rowIndex = rowIndex - 1; 376 edgeLimit = Integer.MAX_VALUE; 377 for (int i = mNumRows - 1; i >= 0; i--) { 378 edgeLimit = mReversedFlow ? getRowMax(i) : getRowMin(i); 379 if (edgeLimit != Integer.MAX_VALUE) { 380 break; 381 } 382 } 383 } else { 384 edgeLimit = mReversedFlow ? findRowMax(true, edgeLimitSearchIndex, null) : 385 findRowMin(false, edgeLimitSearchIndex, null); 386 } 387 if (mReversedFlow ? getRowMax(rowIndex) >= edgeLimit 388 : getRowMin(rowIndex) <= edgeLimit) { 389 // if current row exceeds previous column, fill from next row 390 rowIndex = rowIndex - 1; 391 if (rowIndex < 0) { 392 // start a new column and using edge limit of current column 393 rowIndex = mNumRows - 1; 394 edgeLimit = mReversedFlow ? findRowMax(true, null) : 395 findRowMin(false, null); 396 } 397 } 398 edgeLimitIsValid = true; 399 } else { 400 itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0; 401 rowIndex = itemIndex % mNumRows; 402 edgeLimit = 0; 403 edgeLimitIsValid = false; 404 } 405 boolean filledOne = false; 406 while (true) { 407 // find start-most row edge (.low is smallest, or .high is largest in reversed flow) 408 // fill from current row till first row so that each row will grow longer than 409 // the previous lowest row. 410 for (; rowIndex >= 0; rowIndex--) { 411 // fill one item to a row 412 if (itemIndex < 0) { 413 return filledOne; 414 } 415 int location = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex); 416 if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) { 417 // nothing on the row 418 if (rowIndex == mNumRows - 1) { 419 location = mReversedFlow ? getRowMax(0) : getRowMin(0); 420 if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) { 421 location = location + (mReversedFlow ? mMargin : -mMargin); 422 } 423 } else { 424 location = mReversedFlow ? getRowMin(rowIndex + 1) : getRowMax(rowIndex + 1); 425 } 426 } else { 427 location = location + (mReversedFlow ? mMargin : -mMargin); 428 } 429 int size = prependVisibleItemToRow(itemIndex--, rowIndex, location); 430 filledOne = true; 431 432 // fill more item to the row to make sure this row is longer than 433 // the previous highest row. 434 if (edgeLimitIsValid) { 435 while (mReversedFlow ? location + size + mMargin < edgeLimit : 436 location - size - mMargin > edgeLimit) { 437 if (itemIndex < 0) { 438 return filledOne; 439 } 440 location = location + (mReversedFlow ? size + mMargin : -size - mMargin); 441 size = prependVisibleItemToRow(itemIndex--, rowIndex, location); 442 } 443 } else { 444 edgeLimitIsValid = true; 445 edgeLimit = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex); 446 } 447 } 448 edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null); 449 if (oneColumnMode || (mReversedFlow ? edgeLimit >= toLimit : edgeLimit <= toLimit)) { 450 return filledOne; 451 } 452 // start fill from last row again 453 rowIndex = mNumRows - 1; 454 } 455 } 456 457 458} 459