/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package android.support.v17.leanback.widget; /** * A default implementation of {@link StaggeredGrid}. * * This implementation tries to fill items in consecutive row order. The next * item is always in same row or in the next row. */ final class StaggeredGridDefault extends StaggeredGrid { /** * Returns the max edge value of item (visible or cached) in a row. This * will be the place to append or prepend item not in cache. */ int getRowMax(int rowIndex) { if (mFirstVisibleIndex < 0) { return Integer.MIN_VALUE; } if (mReversedFlow) { int edge = mProvider.getEdge(mFirstVisibleIndex); if (getLocation(mFirstVisibleIndex).row == rowIndex) { return edge; } for (int i = mFirstVisibleIndex + 1; i <= getLastIndex(); i++) { Location loc = getLocation(i); edge += loc.offset; if (loc.row == rowIndex) { return edge; } } } else { int edge = mProvider.getEdge(mLastVisibleIndex); Location loc = getLocation(mLastVisibleIndex); if (loc.row == rowIndex) { return edge + loc.size; } for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) { edge -= loc.offset; loc = getLocation(i); if (loc.row == rowIndex) { return edge + loc.size; } } } return Integer.MIN_VALUE; } /** * Returns the min edge value of item (visible or cached) in a row. This * will be the place to prepend or append item not in cache. */ int getRowMin(int rowIndex) { if (mFirstVisibleIndex < 0) { return Integer.MAX_VALUE; } if (mReversedFlow) { int edge = mProvider.getEdge(mLastVisibleIndex); Location loc = getLocation(mLastVisibleIndex); if (loc.row == rowIndex) { return edge - loc.size; } for (int i = mLastVisibleIndex - 1; i >= getFirstIndex(); i--) { edge -= loc.offset; loc = getLocation(i); if (loc.row == rowIndex) { return edge - loc.size; } } } else { int edge = mProvider.getEdge(mFirstVisibleIndex); if (getLocation(mFirstVisibleIndex).row == rowIndex) { return edge; } for (int i = mFirstVisibleIndex + 1; i <= getLastIndex() ; i++) { Location loc = getLocation(i); edge += loc.offset; if (loc.row == rowIndex) { return edge; } } } return Integer.MAX_VALUE; } /** * Note this method has assumption that item is filled either in the same row * next row of last item. Search until row index wrapped. */ @Override public int findRowMax(boolean findLarge, int indexLimit, int[] indices) { int value; int edge = mProvider.getEdge(indexLimit); Location loc = getLocation(indexLimit); int row = loc.row; int index = indexLimit; int visitedRows = 1; int visitRow = row; if (mReversedFlow) { value = edge; for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) { loc = getLocation(i); edge += loc.offset; if (loc.row != visitRow) { visitRow = loc.row; visitedRows++; if (findLarge ? edge > value : edge < value) { row = visitRow; value = edge; index = i; } } } } else { value = edge + mProvider.getSize(indexLimit); for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) { edge -= loc.offset; loc = getLocation(i); if (loc.row != visitRow) { visitRow = loc.row; visitedRows++; int newValue = edge + mProvider.getSize(i); if (findLarge ? newValue > value : newValue < value) { row = visitRow; value = newValue; index = i; } } } } if (indices != null) { indices[0] = row; indices[1] = index; } return value; } /** * Note this method has assumption that item is filled either in the same row * next row of last item. Search until row index wrapped. */ @Override public int findRowMin(boolean findLarge, int indexLimit, int[] indices) { int value; int edge = mProvider.getEdge(indexLimit); Location loc = getLocation(indexLimit); int row = loc.row; int index = indexLimit; int visitedRows = 1; int visitRow = row; if (mReversedFlow) { value = edge - mProvider.getSize(indexLimit); for (int i = indexLimit - 1; visitedRows < mNumRows && i >= mFirstVisibleIndex; i--) { edge -= loc.offset; loc = getLocation(i); if (loc.row != visitRow) { visitRow = loc.row; visitedRows++; int newValue = edge - mProvider.getSize(i); if (findLarge ? newValue > value : newValue < value) { value = newValue; row = visitRow; index = i; } } } } else { value = edge; for (int i = indexLimit + 1; visitedRows < mNumRows && i <= mLastVisibleIndex; i++) { loc = getLocation(i); edge += loc.offset; if (loc.row != visitRow) { visitRow = loc.row; visitedRows++; if (findLarge ? edge > value : edge < value) { value = edge; row = visitRow; index = i; } } } } if (indices != null) { indices[0] = row; indices[1] = index; } return value; } private int findRowEdgeLimitSearchIndex(boolean append) { boolean wrapped = false; if (append) { for (int index = mLastVisibleIndex; index >= mFirstVisibleIndex; index--) { int row = getLocation(index).row; if (row == 0) { wrapped = true; } else if (wrapped && row == mNumRows - 1) { return index; } } } else { for (int index = mFirstVisibleIndex; index <= mLastVisibleIndex; index++) { int row = getLocation(index).row; if (row == mNumRows - 1) { wrapped = true; } else if (wrapped && row == 0) { return index; } } } return -1; } @Override protected boolean appendVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) { final int count = mProvider.getCount(); int itemIndex; int rowIndex; int edgeLimit; boolean edgeLimitIsValid; if (mLastVisibleIndex >= 0) { if (mLastVisibleIndex < getLastIndex()) { // should fill using cache instead return false; } itemIndex = mLastVisibleIndex + 1; rowIndex = getLocation(mLastVisibleIndex).row; // find start item index of "previous column" int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(true); if (edgeLimitSearchIndex < 0) { // if "previous colummn" is not found, using edgeLimit of // first row currently in grid edgeLimit = Integer.MIN_VALUE; for (int i = 0; i < mNumRows; i++) { edgeLimit = mReversedFlow ? getRowMin(i) : getRowMax(i); if (edgeLimit != Integer.MIN_VALUE) { break; } } } else { edgeLimit = mReversedFlow ? findRowMin(false, edgeLimitSearchIndex, null) : findRowMax(true, edgeLimitSearchIndex, null); } if (mReversedFlow ? getRowMin(rowIndex) <= edgeLimit : getRowMax(rowIndex) >= edgeLimit) { // if current row exceeds previous column, fill from next row rowIndex = rowIndex + 1; if (rowIndex == mNumRows) { // start a new column and using edge limit of current column rowIndex = 0; edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null); } } edgeLimitIsValid = true; } else { itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0; // if there are cached items, put on next row of last cached item. rowIndex = (mLocations.size() > 0 ? getLocation(getLastIndex()).row + 1 : itemIndex) % mNumRows; edgeLimit = 0; edgeLimitIsValid = false; } boolean filledOne = false; while (true) { // find end-most row edge (.high is biggest, or .low is smallest in reversed flow) // fill from current row till last row so that each row will grow longer than // the previous highest row. for (; rowIndex < mNumRows; rowIndex++) { // fill one item to a row if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) { return filledOne; } int location = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex); if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) { // nothing on the row if (rowIndex == 0) { location = mReversedFlow ? getRowMin(mNumRows - 1) : getRowMax(mNumRows - 1); if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) { location = location + (mReversedFlow ? -mMargin : mMargin); } } else { location = mReversedFlow ? getRowMax(rowIndex - 1) : getRowMin(rowIndex - 1); } } else { location = location + (mReversedFlow ? -mMargin : mMargin); } int size = appendVisibleItemToRow(itemIndex++, rowIndex, location); filledOne = true; // fill more item to the row to make sure this row is longer than // the previous highest row. if (edgeLimitIsValid) { while (mReversedFlow ? location - size > edgeLimit : location + size < edgeLimit) { if (itemIndex == count || (!oneColumnMode && checkAppendOverLimit(toLimit))) { return filledOne; } location = location + (mReversedFlow ? - size - mMargin : size + mMargin); size = appendVisibleItemToRow(itemIndex++, rowIndex, location); } } else { edgeLimitIsValid = true; edgeLimit = mReversedFlow ? getRowMin(rowIndex) : getRowMax(rowIndex); } } if (oneColumnMode) { return filledOne; } edgeLimit = mReversedFlow ? findRowMin(false, null) : findRowMax(true, null); // start fill from row 0 again rowIndex = 0; } } @Override protected boolean prependVisibleItemsWithoutCache(int toLimit, boolean oneColumnMode) { int itemIndex; int rowIndex; int edgeLimit; boolean edgeLimitIsValid; if (mFirstVisibleIndex >= 0) { if (mFirstVisibleIndex > getFirstIndex()) { // should fill using cache instead return false; } itemIndex = mFirstVisibleIndex - 1; rowIndex = getLocation(mFirstVisibleIndex).row; // find start item index of "previous column" int edgeLimitSearchIndex = findRowEdgeLimitSearchIndex(false); if (edgeLimitSearchIndex < 0) { // if "previous colummn" is not found, using edgeLimit of // last row currently in grid and fill from upper row rowIndex = rowIndex - 1; edgeLimit = Integer.MAX_VALUE; for (int i = mNumRows - 1; i >= 0; i--) { edgeLimit = mReversedFlow ? getRowMax(i) : getRowMin(i); if (edgeLimit != Integer.MAX_VALUE) { break; } } } else { edgeLimit = mReversedFlow ? findRowMax(true, edgeLimitSearchIndex, null) : findRowMin(false, edgeLimitSearchIndex, null); } if (mReversedFlow ? getRowMax(rowIndex) >= edgeLimit : getRowMin(rowIndex) <= edgeLimit) { // if current row exceeds previous column, fill from next row rowIndex = rowIndex - 1; if (rowIndex < 0) { // start a new column and using edge limit of current column rowIndex = mNumRows - 1; edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null); } } edgeLimitIsValid = true; } else { itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0; // if there are cached items, put on previous row of first cached item. rowIndex = (mLocations.size() >= 0 ? getLocation(getFirstIndex()).row + mNumRows - 1 : itemIndex) % mNumRows; edgeLimit = 0; edgeLimitIsValid = false; } boolean filledOne = false; while (true) { // find start-most row edge (.low is smallest, or .high is largest in reversed flow) // fill from current row till first row so that each row will grow longer than // the previous lowest row. for (; rowIndex >= 0; rowIndex--) { // fill one item to a row if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) { return filledOne; } int location = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex); if (location == Integer.MAX_VALUE || location == Integer.MIN_VALUE) { // nothing on the row if (rowIndex == mNumRows - 1) { location = mReversedFlow ? getRowMax(0) : getRowMin(0); if (location != Integer.MAX_VALUE && location != Integer.MIN_VALUE) { location = location + (mReversedFlow ? mMargin : -mMargin); } } else { location = mReversedFlow ? getRowMin(rowIndex + 1) : getRowMax(rowIndex + 1); } } else { location = location + (mReversedFlow ? mMargin : -mMargin); } int size = prependVisibleItemToRow(itemIndex--, rowIndex, location); filledOne = true; // fill more item to the row to make sure this row is longer than // the previous highest row. if (edgeLimitIsValid) { while (mReversedFlow ? location + size < edgeLimit : location - size > edgeLimit) { if (itemIndex < 0 || (!oneColumnMode && checkPrependOverLimit(toLimit))) { return filledOne; } location = location + (mReversedFlow ? size + mMargin : -size - mMargin); size = prependVisibleItemToRow(itemIndex--, rowIndex, location); } } else { edgeLimitIsValid = true; edgeLimit = mReversedFlow ? getRowMax(rowIndex) : getRowMin(rowIndex); } } if (oneColumnMode) { return filledOne; } edgeLimit = mReversedFlow ? findRowMax(true, null) : findRowMin(false, null); // start fill from last row again rowIndex = mNumRows - 1; } } }