blob: 4675743ce28bee3f4a58ec7008ed198b14e45085 [file] [log] [blame]
/*
* 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 {
@Override
protected int updateFirstVisibleOffset(int prependedItemSize) {
Location firstVisible = getLocation(mFirstVisibleIndex);
// Find a cached item in same row, if not found, just use last item.
int index = mFirstVisibleIndex - 1;
boolean foundCachedItemInSameRow = false;
while (index >= mFirstIndex) {
Location loc = getLocation(index);
if (loc.row == firstVisible.row) {
foundCachedItemInSameRow = true;
break;
}
index--;
}
if (!foundCachedItemInSameRow) {
index = mFirstVisibleIndex - 1;
}
// Assuming mFirstVisibleIndex is next to index on the same row, so the
// sum of offset of [index + 1, mFirstVisibleIndex] should be size of the item
// plus margin.
int offset = isReversedFlow() ? -getLocation(index).size - mMargin:
getLocation(index).size + mMargin;
for (int i = index + 1; i < mFirstVisibleIndex; i++) {
offset -= getLocation(index).offset;
}
firstVisible.offset = offset;
return offset;
}
/**
* 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;
rowIndex = 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;
rowIndex = 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;
}
}
}