StaggeredGridDefault.java revision 89fac6fa5d32123cc79d1d4127a4a7bcf86c498a
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 int visitedRows = 1; 139 int visitRow = row; 140 if (mReversedFlow) { 141 value = edge; 142 for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) { 143 loc = getLocation(i); 144 edge += loc.offset; 145 if (loc.row != visitRow) { 146 visitRow = loc.row; 147 visitedRows++; 148 if (findLarge ? edge > value : edge < value) { 149 row = visitRow; 150 value = edge; 151 index = i; 152 } 153 } 154 } 155 } else { 156 value = edge + mProvider.getSize(indexLimit); 157 for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) { 158 edge -= loc.offset; 159 loc = getLocation(i); 160 if (loc.row != visitRow) { 161 visitRow = loc.row; 162 visitedRows++; 163 int newValue = edge + mProvider.getSize(i); 164 if (findLarge ? newValue > value : newValue < value) { 165 row = visitRow; 166 value = newValue; 167 index = i; 168 } 169 } 170 } 171 } 172 if (indices != null) { 173 indices[0] = row; 174 indices[1] = index; 175 } 176 return value; 177 } 178 179 /** 180 * Note this method has assumption that item is filled either in the same row 181 * next row of last item. Search until row index wrapped. 182 */ 183 @Override 184 public int findRowMin(boolean findLarge, int indexLimit, int[] indices) { 185 int value; 186 int edge = mProvider.getEdge(indexLimit); 187 Location loc = getLocation(indexLimit); 188 int row = loc.row; 189 int index = indexLimit; 190 int visitedRows = 1; 191 int visitRow = row; 192 if (mReversedFlow) { 193 value = edge - mProvider.getSize(indexLimit); 194 for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) { 195 edge -= loc.offset; 196 loc = getLocation(i); 197 if (loc.row != visitRow) { 198 visitRow = loc.row; 199 visitedRows++; 200 int newValue = edge - mProvider.getSize(i); 201 if (findLarge ? newValue > value : newValue < value) { 202 value = newValue; 203 row = visitRow; 204 index = i; 205 } 206 } 207 } 208 } else { 209 value = edge; 210 for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) { 211 loc = getLocation(i); 212 edge += loc.offset; 213 if (loc.row != visitRow) { 214 visitRow = loc.row; 215 visitedRows++; 216 if (findLarge ? edge > value : edge < value) { 217 value = edge; 218 row = visitRow; 219 index = i; 220 } 221 } 222 } 223 } 224 if (indices != null) { 225 indices[0] = row; 226 indices[1] = index; 227 } 228 return value; 229 } 230 231 private int findRowEdgeLimitSearchIndex(boolean append) { 232 boolean wrapped = false; 233 if (append) { 234 for (int index = mLastVisibleIndex; index >= mFirstVisibleIndex; index--) { 235 int row = getLocation(index).row; 236 if (row == 0) { 237 wrapped = true; 238 } else if (wrapped && row == mNumRows - 1) { 239 return index; 240 } 241 } 242 } else { 243 for (int index = mFirstVisibleIndex; index <= mLastVisibleIndex; index++) { 244 int row = getLocation(index).row; 245 if (row == mNumRows - 1) { 246 wrapped = true; 247 } else if (wrapped && row == 0) { 248 return index; 249 } 250 } 251 } 252 return -1; 253 } 254 255 @Override 256 protected boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) { 257 final int count = mProvider.getCount(); 258 int itemIndex; 259 int rowIndex; 260 int edgeLimit; 261 boolean edgeLimitIsValid; 262 if (mLastVisibleIndex >= 0) { 263 if (mLastVisibleIndex < getLastIndex()) { 264 // should fill using cache instead 265 return false; 266 } 267 itemIndex = mLastVisibleIndex + 1; 268 rowIndex = getLocation(mLastVisibleIndex).row; 269 // find start item index of "previous column" 270 int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(true); 271 if (edgeLimitSearchIndex < 0) { 272 // if "previous colummn" is not found, using edgeLimit of 273 // first row currently in grid 274 edgeLimit = Integer.MIN_VALUE; 275 for (int i = 0; i < mNumRows; i++) { 276 edgeLimit = mReversedFlow ? getRowMin(i) : getRowMax(i); 277 if (edgeLimit != Integer.MIN_VALUE) { 278 break; 279 } 280 } 281 } else { 282 edgeLimit = mReversedFlow ? findRowMin(false, edgeLimitSearchIndex, null) : 283 findRowMax(true, edgeLimitSearchIndex, null); 284 } 285 if (mReversedFlow ? getRowMin(rowIndex) <= edgeLimit 286 : getRowMax(rowIndex) >= edgeLimit) { 287 // if current row exceeds previous column, fill from next row 288 rowIndex = rowIndex + 1; 289 if (rowIndex == mNumRows) { 290 // start a new column and using edge limit of current column 291 rowIndex = 0; 292 edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null); 293 } 294 } 295 edgeLimitIsValid = true; 296 } else { 297 itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0; 298 rowIndex = itemIndex % mNumRows; 299 edgeLimit = 0; 300 edgeLimitIsValid = false; 301 } 302 303 boolean filledOne = false; 304 while (true) { 305 // find end-most row edge (.high is biggest, or .low is smallest in reversed flow) 306 // fill from current row till last row so that each row will grow longer than 307 // the previous highest row. 308 for (; rowIndex < mNumRows; rowIndex++) { 309 // fill one item to a row 310 if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) { 311 return filledOne; 312 } 313 int location = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex); 314 if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) { 315 // nothing on the row 316 if (rowIndex == 0) { 317 location = mReversedFlow ? getRowMin(mNumRows - 1) : getRowMax(mNumRows - 1); 318 if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) { 319 location = location + (mReversedFlow ? -mMargin : mMargin); 320 } 321 } else { 322 location = mReversedFlow ? getRowMax(rowIndex - 1) : getRowMin(rowIndex - 1); 323 } 324 } else { 325 location = location + (mReversedFlow ? -mMargin : mMargin); 326 } 327 int size = appendVisibleItemToRow(itemIndex++, rowIndex, location); 328 filledOne = true; 329 // fill more item to the row to make sure this row is longer than 330 // the previous highest row. 331 if (edgeLimitIsValid) { 332 while (mReversedFlow ? location - size > edgeLimit : 333 location + size < edgeLimit) { 334 if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) { 335 return filledOne; 336 } 337 location = location + (mReversedFlow ? - size - mMargin : size + mMargin); 338 size = appendVisibleItemToRow(itemIndex++, rowIndex, location); 339 } 340 } else { 341 edgeLimitIsValid = true; 342 edgeLimit = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex); 343 } 344 } 345 if (oneColumnMode) { 346 return filledOne; 347 } 348 edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null); 349 // start fill from row 0 again 350 rowIndex = 0; 351 } 352 } 353 354 @Override 355 protected boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) { 356 int itemIndex; 357 int rowIndex; 358 int edgeLimit; 359 boolean edgeLimitIsValid; 360 if (mFirstVisibleIndex >= 0) { 361 if (mFirstVisibleIndex > getFirstIndex()) { 362 // should fill using cache instead 363 return false; 364 } 365 itemIndex = mFirstVisibleIndex - 1; 366 rowIndex = getLocation(mFirstVisibleIndex).row; 367 // find start item index of "previous column" 368 int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(false); 369 if (edgeLimitSearchIndex < 0) { 370 // if "previous colummn" is not found, using edgeLimit of 371 // last row currently in grid and fill from upper row 372 rowIndex = rowIndex - 1; 373 edgeLimit = Integer.MAX_VALUE; 374 for (int i = mNumRows - 1; i >= 0; i--) { 375 edgeLimit = mReversedFlow ? getRowMax(i) : getRowMin(i); 376 if (edgeLimit != Integer.MAX_VALUE) { 377 break; 378 } 379 } 380 } else { 381 edgeLimit = mReversedFlow ? findRowMax(true, edgeLimitSearchIndex, null) : 382 findRowMin(false, edgeLimitSearchIndex, null); 383 } 384 if (mReversedFlow ? getRowMax(rowIndex) >= edgeLimit 385 : getRowMin(rowIndex) <= edgeLimit) { 386 // if current row exceeds previous column, fill from next row 387 rowIndex = rowIndex - 1; 388 if (rowIndex < 0) { 389 // start a new column and using edge limit of current column 390 rowIndex = mNumRows - 1; 391 edgeLimit = mReversedFlow ? findRowMax(true, null) : 392 findRowMin(false, null); 393 } 394 } 395 edgeLimitIsValid = true; 396 } else { 397 itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0; 398 rowIndex = itemIndex % mNumRows; 399 edgeLimit = 0; 400 edgeLimitIsValid = false; 401 } 402 boolean filledOne = false; 403 while (true) { 404 // find start-most row edge (.low is smallest, or .high is largest in reversed flow) 405 // fill from current row till first row so that each row will grow longer than 406 // the previous lowest row. 407 for (; rowIndex >= 0; rowIndex--) { 408 // fill one item to a row 409 if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) { 410 return filledOne; 411 } 412 int location = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex); 413 if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) { 414 // nothing on the row 415 if (rowIndex == mNumRows - 1) { 416 location = mReversedFlow ? getRowMax(0) : getRowMin(0); 417 if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) { 418 location = location + (mReversedFlow ? mMargin : -mMargin); 419 } 420 } else { 421 location = mReversedFlow ? getRowMin(rowIndex + 1) : getRowMax(rowIndex + 1); 422 } 423 } else { 424 location = location + (mReversedFlow ? mMargin : -mMargin); 425 } 426 int size = prependVisibleItemToRow(itemIndex--, rowIndex, location); 427 filledOne = true; 428 429 // fill more item to the row to make sure this row is longer than 430 // the previous highest row. 431 if (edgeLimitIsValid) { 432 while (mReversedFlow ? location + size < edgeLimit : 433 location - size > edgeLimit) { 434 if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) { 435 return filledOne; 436 } 437 location = location + (mReversedFlow ? size + mMargin : -size - mMargin); 438 size = prependVisibleItemToRow(itemIndex--, rowIndex, location); 439 } 440 } else { 441 edgeLimitIsValid = true; 442 edgeLimit = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex); 443 } 444 } 445 if (oneColumnMode) { 446 return filledOne; 447 } 448 edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null); 449 // start fill from last row again 450 rowIndex = mNumRows - 1; 451 } 452 } 453 454 455} 456