blob: cb196a3beb65ad977bec02649a82e790708ff7db [file] [log] [blame]
Sam Juddde3e9ab2014-03-17 13:07:22 -07001package com.android.camera.widget;
2
Sam Juddde3e9ab2014-03-17 13:07:22 -07003import android.widget.AbsListView;
4
Angus Kong2bca2102014-03-11 16:27:30 -07005import com.android.camera.debug.Log;
6
Sam Juddde3e9ab2014-03-17 13:07:22 -07007import java.util.Collections;
8import java.util.List;
9import java.util.Queue;
10import java.util.concurrent.LinkedBlockingQueue;
11
12/**
13 * Responsible for controlling preloading logic. Intended usage is for ListViews that
14 * benefit from initiating a load before the row appear on screen.
15 * @param <T> The type of items this class preload.
16 * @param <Y> The type of load tokens that can be used to cancel loads for the items this class
17 * preloads.
18 */
19public class Preloader<T, Y> implements AbsListView.OnScrollListener {
Angus Kong2bca2102014-03-11 16:27:30 -070020 private static final Log.Tag TAG = new Log.Tag("Preloader");
Sam Juddde3e9ab2014-03-17 13:07:22 -070021
22 /**
23 * Implemented by the source for items that should be preloaded.
24 */
25 public interface ItemSource<T> {
26 /**
27 * Returns the objects in the range [startPosition; endPosition).
28 */
29 public List<T> getItemsInRange(int startPosition, int endPosition);
30
31 /**
32 * Returns the total number of items in the source.
33 */
34 public int getCount();
35 }
36
37 /**
38 * Responsible for the loading of items.
39 */
40 public interface ItemLoader<T, Y> {
41 /**
42 * Initiates a load for the specified items and returns a list of 0 or more load tokens that
43 * can be used to cancel the loads for the given items. Should preload the items in the list
44 * order,preloading the 0th item in the list fist.
45 */
46 public List<Y> preloadItems(List<T> items);
47
48 /**
49 * Cancels all of the loads represented by the given load tokens.
50 */
51 public void cancelItems(List<Y> loadTokens);
52 }
53
54 private final int mMaxConcurrentPreloads;
55
56 /**
57 * Keep track of the largest/smallest item we requested (depending on scroll direction) so
58 * we don't preload the same items repeatedly. Without this var, scrolling down we preload
59 * 0-5, then 1-6 etc. Using this we instead preload 0-5, then 5-6, 6-7 etc.
60 */
61 private int mLastEnd = -1;
62 private int mLastStart;
63
64 private final int mLoadAheadItems;
65 private ItemSource<T> mItemSource;
66 private ItemLoader<T, Y> mItemLoader;
67 private Queue<List<Y>> mItemLoadTokens = new LinkedBlockingQueue<List<Y>>();
68
69 private int mLastVisibleItem;
70 private boolean mScrollingDown = false;
71
72 public Preloader(int loadAheadItems, ItemSource<T> itemSource, ItemLoader<T, Y> itemLoader) {
73 mItemSource = itemSource;
74 mItemLoader = itemLoader;
75 mLoadAheadItems = loadAheadItems;
76 // Add an additional item so we don't cancel a preload before we start a real load.
77 mMaxConcurrentPreloads = loadAheadItems + 1;
78 }
79
80 /**
81 * Initiates a pre load.
82 *
83 * @param first The source position to load from
84 * @param increasing The direction we're going in (increasing -> source positions are
85 * increasing -> we're scrolling down the list)
86 */
87 private void preload(int first, boolean increasing) {
88 final int start;
89 final int end;
90 if (increasing) {
91 start = Math.max(first, mLastEnd);
92 end = Math.min(first + mLoadAheadItems, mItemSource.getCount());
93 } else {
94 start = Math.max(0, first - mLoadAheadItems);
95 end = Math.min(first, mLastStart);
96 }
97
Angus Kong2bca2102014-03-11 16:27:30 -070098 Log.v(TAG, "preload first=" + first + " increasing=" + increasing + " start=" + start +
99 " end=" + end);
Sam Juddde3e9ab2014-03-17 13:07:22 -0700100
101 mLastEnd = end;
102 mLastStart = start;
103
104 if (start == 0 && end == 0) {
105 return;
106 }
107
108 final List<T> items = mItemSource.getItemsInRange(start, end);
109 if (!increasing) {
110 Collections.reverse(items);
111 }
112 registerLoadTokens(mItemLoader.preloadItems(items));
113 }
114
115 private void registerLoadTokens(List<Y> loadTokens) {
116 mItemLoadTokens.offer(loadTokens);
117 // We pretend that one batch of load tokens corresponds to one item in the list. This isn't
118 // strictly true because we may batch preload multiple items at once when we first start
119 // scrolling in the list or change the direction we're scrolling in. In those cases, we will
120 // have a single large batch of load tokens for multiple items, and then go back to getting
121 // one batch per item as we continue to scroll. This means we may not cancel as many
122 // preloads as we expect when we change direction, but we can at least be sure we won't
123 // cancel preloads for items we still care about. We can't be more precise here because
124 // there is no guarantee that there is a one to one relationship between load tokens
125 // and list items.
126 if (mItemLoadTokens.size() > mMaxConcurrentPreloads) {
127 final List<Y> loadTokensToCancel = mItemLoadTokens.poll();
128 mItemLoader.cancelItems(loadTokensToCancel);
129 }
130 }
131
132 public void cancelAllLoads() {
133 for (List<Y> loadTokens : mItemLoadTokens) {
134 mItemLoader.cancelItems(loadTokens);
135 }
136 mItemLoadTokens.clear();
137 }
138
139 @Override
140 public void onScrollStateChanged(AbsListView absListView, int i) {
141 // Do nothing.
142 }
143
144 @Override
145 public void onScroll(AbsListView absListView, int firstVisible, int visibleItemCount,
146 int totalItemCount) {
147 boolean wasScrollingDown = mScrollingDown;
148 int preloadStart = -1;
149 if (firstVisible > mLastVisibleItem) {
150 // Scrolling list down
151 mScrollingDown = true;
152 preloadStart = firstVisible + visibleItemCount;
153 } else if (firstVisible < mLastVisibleItem) {
154 // Scrolling list Up
155 mScrollingDown = false;
156 preloadStart = firstVisible;
157 }
158
159 if (wasScrollingDown != mScrollingDown) {
160 // If we've changed directions, we don't care about any of our old preloads, so cancel
161 // all of them.
162 cancelAllLoads();
163 }
164
165 // onScroll can be called multiple times with the same arguments, so we only want to preload
166 // if we've actually scrolled at least an item in either direction.
167 if (preloadStart != -1) {
168 preload(preloadStart, mScrollingDown);
169 }
170
171 mLastVisibleItem = firstVisible;
172 }
173}