blob: cb196a3beb65ad977bec02649a82e790708ff7db [file] [log] [blame]
package com.android.camera.widget;
import android.widget.AbsListView;
import com.android.camera.debug.Log;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Responsible for controlling preloading logic. Intended usage is for ListViews that
* benefit from initiating a load before the row appear on screen.
* @param <T> The type of items this class preload.
* @param <Y> The type of load tokens that can be used to cancel loads for the items this class
* preloads.
*/
public class Preloader<T, Y> implements AbsListView.OnScrollListener {
private static final Log.Tag TAG = new Log.Tag("Preloader");
/**
* Implemented by the source for items that should be preloaded.
*/
public interface ItemSource<T> {
/**
* Returns the objects in the range [startPosition; endPosition).
*/
public List<T> getItemsInRange(int startPosition, int endPosition);
/**
* Returns the total number of items in the source.
*/
public int getCount();
}
/**
* Responsible for the loading of items.
*/
public interface ItemLoader<T, Y> {
/**
* Initiates a load for the specified items and returns a list of 0 or more load tokens that
* can be used to cancel the loads for the given items. Should preload the items in the list
* order,preloading the 0th item in the list fist.
*/
public List<Y> preloadItems(List<T> items);
/**
* Cancels all of the loads represented by the given load tokens.
*/
public void cancelItems(List<Y> loadTokens);
}
private final int mMaxConcurrentPreloads;
/**
* Keep track of the largest/smallest item we requested (depending on scroll direction) so
* we don't preload the same items repeatedly. Without this var, scrolling down we preload
* 0-5, then 1-6 etc. Using this we instead preload 0-5, then 5-6, 6-7 etc.
*/
private int mLastEnd = -1;
private int mLastStart;
private final int mLoadAheadItems;
private ItemSource<T> mItemSource;
private ItemLoader<T, Y> mItemLoader;
private Queue<List<Y>> mItemLoadTokens = new LinkedBlockingQueue<List<Y>>();
private int mLastVisibleItem;
private boolean mScrollingDown = false;
public Preloader(int loadAheadItems, ItemSource<T> itemSource, ItemLoader<T, Y> itemLoader) {
mItemSource = itemSource;
mItemLoader = itemLoader;
mLoadAheadItems = loadAheadItems;
// Add an additional item so we don't cancel a preload before we start a real load.
mMaxConcurrentPreloads = loadAheadItems + 1;
}
/**
* Initiates a pre load.
*
* @param first The source position to load from
* @param increasing The direction we're going in (increasing -> source positions are
* increasing -> we're scrolling down the list)
*/
private void preload(int first, boolean increasing) {
final int start;
final int end;
if (increasing) {
start = Math.max(first, mLastEnd);
end = Math.min(first + mLoadAheadItems, mItemSource.getCount());
} else {
start = Math.max(0, first - mLoadAheadItems);
end = Math.min(first, mLastStart);
}
Log.v(TAG, "preload first=" + first + " increasing=" + increasing + " start=" + start +
" end=" + end);
mLastEnd = end;
mLastStart = start;
if (start == 0 && end == 0) {
return;
}
final List<T> items = mItemSource.getItemsInRange(start, end);
if (!increasing) {
Collections.reverse(items);
}
registerLoadTokens(mItemLoader.preloadItems(items));
}
private void registerLoadTokens(List<Y> loadTokens) {
mItemLoadTokens.offer(loadTokens);
// We pretend that one batch of load tokens corresponds to one item in the list. This isn't
// strictly true because we may batch preload multiple items at once when we first start
// scrolling in the list or change the direction we're scrolling in. In those cases, we will
// have a single large batch of load tokens for multiple items, and then go back to getting
// one batch per item as we continue to scroll. This means we may not cancel as many
// preloads as we expect when we change direction, but we can at least be sure we won't
// cancel preloads for items we still care about. We can't be more precise here because
// there is no guarantee that there is a one to one relationship between load tokens
// and list items.
if (mItemLoadTokens.size() > mMaxConcurrentPreloads) {
final List<Y> loadTokensToCancel = mItemLoadTokens.poll();
mItemLoader.cancelItems(loadTokensToCancel);
}
}
public void cancelAllLoads() {
for (List<Y> loadTokens : mItemLoadTokens) {
mItemLoader.cancelItems(loadTokens);
}
mItemLoadTokens.clear();
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
// Do nothing.
}
@Override
public void onScroll(AbsListView absListView, int firstVisible, int visibleItemCount,
int totalItemCount) {
boolean wasScrollingDown = mScrollingDown;
int preloadStart = -1;
if (firstVisible > mLastVisibleItem) {
// Scrolling list down
mScrollingDown = true;
preloadStart = firstVisible + visibleItemCount;
} else if (firstVisible < mLastVisibleItem) {
// Scrolling list Up
mScrollingDown = false;
preloadStart = firstVisible;
}
if (wasScrollingDown != mScrollingDown) {
// If we've changed directions, we don't care about any of our old preloads, so cancel
// all of them.
cancelAllLoads();
}
// onScroll can be called multiple times with the same arguments, so we only want to preload
// if we've actually scrolled at least an item in either direction.
if (preloadStart != -1) {
preload(preloadStart, mScrollingDown);
}
mLastVisibleItem = firstVisible;
}
}