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;
|
}
|
}
|