| package com.bumptech.glide; |
| |
| import android.annotation.TargetApi; |
| import android.os.Build; |
| import android.widget.AbsListView; |
| import com.bumptech.glide.request.GlideAnimation; |
| import com.bumptech.glide.request.target.BaseTarget; |
| |
| import java.util.ArrayDeque; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Queue; |
| |
| /** |
| * Loads a few images ahead in the direction of scrolling in any {@link AbsListView} so that images are in the memory |
| * cache just before the corresponding view in created in the list. Gives the appearance of an infinitely large image |
| * cache, depending on scrolling speed, cpu speed, and cache size. |
| * |
| * <p> |
| * Must be set using {@link AbsListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}, or have its |
| * corresponding methods called from another {@link android.widget.AbsListView.OnScrollListener} to function. |
| * </p> |
| * |
| * @param <T> The type of the model being displayed in the list. |
| */ |
| public abstract class ListPreloader<T> implements AbsListView.OnScrollListener { |
| private final int maxPreload; |
| private final PreloadTargetQueue preloadTargetQueue; |
| |
| private int lastEnd; |
| private int lastStart; |
| private int lastFirstVisible; |
| private int totalItemCount; |
| |
| private boolean isIncreasing = true; |
| |
| /** |
| * Constructor for the preloader. |
| * |
| * @param maxPreload The maximum number of items in the list to load ahead (corresponds to adapter positions). |
| */ |
| public ListPreloader(int maxPreload) { |
| this.maxPreload = maxPreload; |
| preloadTargetQueue = new PreloadTargetQueue(maxPreload + 1); |
| } |
| |
| @Override |
| public void onScrollStateChanged(AbsListView absListView, int i) { } |
| |
| @Override |
| public void onScroll(AbsListView absListView, int firstVisible, int visibleCount, int totalCount) { |
| totalItemCount = totalCount; |
| if (firstVisible > lastFirstVisible) { |
| preload(firstVisible + visibleCount, true); |
| } else if (firstVisible < lastFirstVisible) { |
| preload(firstVisible, false); |
| } |
| lastFirstVisible = firstVisible; |
| } |
| |
| /** |
| * Returns the dimensions of the view in the list where the images will be displayed. |
| * <p> |
| * Note - The dimensions returned here must precisely match those of the view in the list. |
| * </p> |
| * @param item A model |
| * @return The dimensions of the view where the item will be displayed |
| */ |
| protected abstract int[] getDimensions(T item); |
| |
| /** |
| * Returns a list of all models that need to be loaded for the list to display adapter items start - end. A list of |
| * any size can be returned so there can be multiple models per adapter position. |
| * |
| * @param start The smallest adapter position. Will be >= 0 && < adapter.getCount() && <= end |
| * @param end The largest adapter position. Will be >= 0 && < adapter.getCount && >= start |
| * @return A non null list of all models for adapter positions between start and end. |
| */ |
| protected abstract List<T> getItems(int start, int end); |
| |
| /** |
| * Returns a glide request for a given item. Must exactly match the request used to load the image in the list. The |
| * target and context will be provided by the preloader. |
| * |
| * @param item The model to load. |
| * @return A non null {@link BitmapRequestBuilder}. |
| */ |
| protected abstract GenericRequestBuilder getRequestBuilder(T item); |
| |
| private void preload(int start, boolean increasing) { |
| if (isIncreasing != increasing) { |
| isIncreasing = increasing; |
| cancelAll(); |
| } |
| preload(start, start + (increasing ? maxPreload : -maxPreload)); |
| } |
| |
| private void preload(int from, int to) { |
| int start; |
| int end; |
| if (from < to) { |
| start = Math.max(lastEnd, from); |
| end = to; |
| } else { |
| start = to; |
| end = Math.min(lastStart, from); |
| } |
| end = Math.min(totalItemCount, end); |
| start = Math.min(totalItemCount, Math.max(0, start)); |
| List<T> items = getItems(start, end); |
| |
| if (from < to) { |
| // Increasing |
| final int numItems = items.size(); |
| for (int i = 0; i < numItems; i++) { |
| preloadItem(items, i); |
| } |
| } else { |
| // Decreasing |
| for (int i = items.size() - 1; i >= 0; i--) { |
| preloadItem(items, i); |
| } |
| } |
| |
| lastStart = start; |
| lastEnd = end; |
| } |
| |
| private void preloadItem(List<T> items, int position) { |
| final T item = items.get(position); |
| final int[] dimensions = getDimensions(item); |
| if (dimensions != null) { |
| getRequestBuilder(item).into(preloadTargetQueue.next(dimensions[0], dimensions[1])); |
| } |
| } |
| |
| private void cancelAll() { |
| for (int i = 0; i < maxPreload; i++) { |
| Glide.clear(preloadTargetQueue.next(0, 0)); |
| } |
| } |
| |
| private static class PreloadTargetQueue { |
| private final Queue<PreloadTarget> queue; |
| |
| @TargetApi(9) |
| private PreloadTargetQueue(int size) { |
| if (Build.VERSION.SDK_INT >= 9) { |
| queue = new ArrayDeque<PreloadTarget>(size); |
| } else { |
| queue = new LinkedList<PreloadTarget>(); |
| } |
| |
| for (int i = 0; i < size; i++) { |
| queue.offer(new PreloadTarget()); |
| } |
| } |
| |
| public PreloadTarget next(int width, int height) { |
| final PreloadTarget result = queue.poll(); |
| queue.offer(result); |
| result.photoWidth = width; |
| result.photoHeight = height; |
| return result; |
| } |
| } |
| |
| private static class PreloadTarget extends BaseTarget { |
| private int photoHeight; |
| private int photoWidth; |
| |
| @Override |
| public void onResourceReady(Object resource, GlideAnimation glideAnimation) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void getSize(SizeReadyCallback cb) { |
| cb.onSizeReady(photoWidth, photoHeight); |
| } |
| |
| } |
| } |