• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.bumptech.glide;
2 
3 import android.widget.AbsListView;
4 
5 import com.bumptech.glide.request.animation.GlideAnimation;
6 import com.bumptech.glide.request.target.BaseTarget;
7 import com.bumptech.glide.request.target.SizeReadyCallback;
8 import com.bumptech.glide.util.Util;
9 
10 import java.util.List;
11 import java.util.Queue;
12 
13 /**
14  * Loads a few resources ahead in the direction of scrolling in any {@link AbsListView} so that images are in the memory
15  * cache just before the corresponding view in created in the list. Gives the appearance of an infinitely large image
16  * cache, depending on scrolling speed, cpu speed, and cache size.
17  *
18  * <p>
19  *  Must be set using {@link AbsListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}, or have its
20  *  corresponding methods called from another {@link android.widget.AbsListView.OnScrollListener} to function.
21  * </p>
22  *
23  * @param <T> The type of the model being displayed in the list.
24  */
25 public abstract class ListPreloader<T> implements AbsListView.OnScrollListener {
26     private final int maxPreload;
27     private final PreloadTargetQueue preloadTargetQueue;
28 
29     private int lastEnd;
30     private int lastStart;
31     private int lastFirstVisible;
32     private int totalItemCount;
33 
34     private boolean isIncreasing = true;
35 
36     /**
37      * Constructor for the preloader.
38      *
39      * @param maxPreload The maximum number of items in the list to load ahead (corresponds to adapter positions).
40      */
ListPreloader(int maxPreload)41     public ListPreloader(int maxPreload) {
42         this.maxPreload = maxPreload;
43         preloadTargetQueue = new PreloadTargetQueue(maxPreload + 1);
44     }
45 
46     @Override
onScrollStateChanged(AbsListView absListView, int scrollState)47     public void onScrollStateChanged(AbsListView absListView, int scrollState) {
48         // Do nothing.
49     }
50 
51     @Override
onScroll(AbsListView absListView, int firstVisible, int visibleCount, int totalCount)52     public void onScroll(AbsListView absListView, int firstVisible, int visibleCount, int totalCount) {
53         totalItemCount = totalCount;
54         if (firstVisible > lastFirstVisible) {
55             preload(firstVisible + visibleCount, true);
56         } else if (firstVisible < lastFirstVisible) {
57             preload(firstVisible, false);
58         }
59         lastFirstVisible = firstVisible;
60     }
61 
62     /**
63      * Returns the dimensions of the view in the list where the resources will be displayed.
64      * <p>
65      *     Note - The dimensions returned here must precisely match those of the view in the list.
66      * </p>
67      * @param item A model
68      * @return The dimensions of the view where the item will be displayed
69      */
getDimensions(T item)70     protected abstract int[] getDimensions(T item);
71 
72     /**
73      * Returns a list of all models that need to be loaded for the list to display adapter items {@code start - end}.
74      * A list of any size can be returned so there can be multiple models per adapter position.
75      *
76      * @param start The smallest adapter position. Will be {@code >= 0 && < adapter.getCount() && <= end}
77      * @param end The largest adapter position. Will be {@code >= 0 && < adapter.getCount && >= start}
78      * @return A non null list of all models for adapter positions between {@code start} and {@code end}.
79      */
getItems(int start, int end)80     protected abstract List<T> getItems(int start, int end);
81 
82     /**
83      * Returns a glide request for a given item. Must exactly match the request used to load the resource in the list.
84      * The target and context will be provided by the preloader.
85      *
86      * @param item The model to load.
87      * @return A non null {@link BitmapRequestBuilder}.
88      */
89     @SuppressWarnings("rawtypes")
getRequestBuilder(T item)90     protected abstract GenericRequestBuilder getRequestBuilder(T item);
91 
preload(int start, boolean increasing)92     private void preload(int start, boolean increasing) {
93         if (isIncreasing != increasing) {
94             isIncreasing = increasing;
95             cancelAll();
96         }
97         preload(start, start + (increasing ? maxPreload : -maxPreload));
98     }
99 
preload(int from, int to)100     private void preload(int from, int to) {
101         int start;
102         int end;
103         if (from < to) {
104             start = Math.max(lastEnd, from);
105             end = to;
106         } else {
107             start = to;
108             end = Math.min(lastStart, from);
109         }
110         end = Math.min(totalItemCount, end);
111         start = Math.min(totalItemCount, Math.max(0, start));
112         List<T> items = getItems(start, end);
113 
114         if (from < to) {
115             // Increasing
116             final int numItems = items.size();
117             for (int i = 0; i < numItems; i++) {
118                 preloadItem(items, i);
119             }
120         } else {
121             // Decreasing
122             for (int i = items.size() - 1; i >= 0; i--) {
123                 preloadItem(items, i);
124             }
125         }
126 
127         lastStart = start;
128         lastEnd = end;
129     }
130 
131     @SuppressWarnings("unchecked")
preloadItem(List<T> items, int position)132     private void preloadItem(List<T> items, int position) {
133         final T item = items.get(position);
134         final int[] dimensions = getDimensions(item);
135         if (dimensions != null) {
136             getRequestBuilder(item).into(preloadTargetQueue.next(dimensions[0], dimensions[1]));
137         }
138     }
139 
cancelAll()140     private void cancelAll() {
141         for (int i = 0; i < maxPreload; i++) {
142             Glide.clear(preloadTargetQueue.next(0, 0));
143         }
144     }
145 
146     private static final class PreloadTargetQueue {
147         private final Queue<PreloadTarget> queue;
148 
PreloadTargetQueue(int size)149         public PreloadTargetQueue(int size) {
150             queue = Util.createQueue(size);
151 
152             for (int i = 0; i < size; i++) {
153                 queue.offer(new PreloadTarget());
154             }
155         }
156 
next(int width, int height)157         public PreloadTarget next(int width, int height) {
158             final PreloadTarget result = queue.poll();
159             queue.offer(result);
160             result.photoWidth = width;
161             result.photoHeight = height;
162             return result;
163         }
164     }
165 
166     private static class PreloadTarget extends BaseTarget<Object> {
167         private int photoHeight;
168         private int photoWidth;
169 
170         @Override
onResourceReady(Object resource, GlideAnimation<? super Object> glideAnimation)171         public void onResourceReady(Object resource, GlideAnimation<? super Object> glideAnimation) {
172             // Do nothing.
173         }
174 
175         @Override
getSize(SizeReadyCallback cb)176         public void getSize(SizeReadyCallback cb) {
177             cb.onSizeReady(photoWidth, photoHeight);
178         }
179     }
180 }
181