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