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