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