ViewTarget.java revision 5ba19a0e69ad3a651b8f13ba45de48a56b56ce36
1package com.bumptech.glide.request.target; 2 3import android.content.Context; 4import android.util.Log; 5import android.view.Display; 6import android.view.View; 7import android.view.ViewGroup; 8import android.view.ViewTreeObserver; 9import android.view.WindowManager; 10import com.bumptech.glide.request.Request; 11 12import java.lang.ref.WeakReference; 13import java.util.ArrayList; 14import java.util.List; 15 16/** 17 * A base {@link Target} for loading {@link android.graphics.Bitmap}s into {@link View}s that provides default 18 * implementations for most most methods and can determine the size of views using a 19 * {@link android.view.ViewTreeObserver.OnDrawListener}. 20 * 21 * <p> 22 * To detect {@link View} reuse in {@link android.widget.ListView} or any {@link ViewGroup} that reuses views, this 23 * class uses the {@link View#setTag(Object)} method to store some metadata so that if a view is reused, any 24 * previous loads or resources from previous loads can be cancelled or reused. 25 * </p> 26 * 27 * <p> 28 * Any calls to {@link View#setTag(Object)}} on a View given to this class will result in excessive allocations and 29 * and/or {@link IllegalArgumentException}s. If you must call {@link View#setTag(Object)} on a view, consider 30 * using {@link BaseTarget} or {@link SimpleTarget} instead. 31 * </p> 32 * 33 * @param <T> The specific subclass of view wrapped by this target. 34 * @param <Z> The resource type this target will receive. 35 */ 36public abstract class ViewTarget<T extends View, Z> extends BaseTarget<Z> { 37 private static final String TAG = "ViewTarget"; 38 39 protected final T view; 40 private final SizeDeterminer sizeDeterminer; 41 42 public ViewTarget(T view) { 43 if (view == null) { 44 throw new NullPointerException("View must not be null!"); 45 } 46 this.view = view; 47 sizeDeterminer = new SizeDeterminer(view); 48 } 49 50 /** 51 * Returns the wrapped {@link android.view.View}. 52 */ 53 public T getView() { 54 return view; 55 } 56 57 /** 58 * Determines the size of the view by first checking {@link android.view.View#getWidth()} and 59 * {@link android.view.View#getHeight()}. If one or both are zero, it then checks the view's 60 * {@link android.view.ViewGroup.LayoutParams}. If one or both of the params width and height are less than or 61 * equal to zero, it then adds an {@link android.view.ViewTreeObserver.OnPreDrawListener} which waits until the view 62 * has been measured before calling the callback with the view's drawn width and height. 63 * 64 * @param cb {@inheritDoc} 65 */ 66 @Override 67 public void getSize(SizeReadyCallback cb) { 68 sizeDeterminer.getSize(cb); 69 } 70 71 /** 72 * Stores the request using {@link View#setTag(Object)}. 73 * 74 * @param request {@inheritDoc} 75 */ 76 @Override 77 public void setRequest(Request request) { 78 view.setTag(request); 79 } 80 81 /** 82 * Returns any stored request using {@link android.view.View#getTag()}. 83 * 84 * <p> 85 * For Glide to function correctly, Glide must be the only thing that calls {@link View#setTag(Object)}. If the 86 * tag is cleared or set to another object type, Glide will not be able to retrieve and cancel previous loads 87 * which will not only prevent Glide from reusing resource, but will also result in incorrect images being 88 * loaded and lots of flashing of images in lists. As a result, this will throw an 89 * {@link java.lang.IllegalArgumentException} if {@link android.view.View#getTag()}} returns a non null object 90 * that is not an {@link com.bumptech.glide.request.Request}. 91 * </p> 92 */ 93 @Override 94 public Request getRequest() { 95 Object tag = view.getTag(); 96 Request request = null; 97 if (tag != null) { 98 if (tag instanceof Request) { 99 request = (Request) tag; 100 } else { 101 throw new IllegalArgumentException("You must not call setTag() on a view Glide is targeting"); 102 } 103 } 104 return request; 105 } 106 107 @Override 108 public String toString() { 109 return "Target for: " + view; 110 } 111 112 private static class SizeDeterminer { 113 private final View view; 114 private final List<SizeReadyCallback> cbs = new ArrayList<SizeReadyCallback>(); 115 private SizeDeterminerLayoutListener layoutListener; 116 117 public SizeDeterminer(View view) { 118 this.view = view; 119 } 120 121 private void notifyCbs(int width, int height) { 122 for (SizeReadyCallback cb : cbs) { 123 cb.onSizeReady(width, height); 124 } 125 cbs.clear(); 126 } 127 128 private void checkCurrentDimens() { 129 if (cbs.isEmpty()) { 130 return; 131 } 132 133 boolean calledCallback = true; 134 ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 135 if (isViewSizeValid()) { 136 notifyCbs(view.getWidth(), view.getHeight()); 137 } else if (isLayoutParamsSizeValid()) { 138 notifyCbs(layoutParams.width, layoutParams.height); 139 } else { 140 calledCallback = false; 141 } 142 143 if (calledCallback) { 144 // Keep a reference to the layout listener and remove it here 145 // rather than having the observer remove itself because the observer 146 // we add the listener to will be almost immediately merged into 147 // another observer and will therefore never be alive. If we instead 148 // keep a reference to the listener and remove it here, we get the 149 // current view tree observer and should succeed. 150 ViewTreeObserver observer = view.getViewTreeObserver(); 151 if (observer.isAlive()) { 152 observer.removeOnPreDrawListener(layoutListener); 153 } 154 } 155 } 156 157 public void getSize(SizeReadyCallback cb) { 158 ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 159 if (isViewSizeValid()) { 160 cb.onSizeReady(view.getWidth(), view.getHeight()); 161 } else if (isLayoutParamsSizeValid()) { 162 cb.onSizeReady(layoutParams.width, layoutParams.height); 163 } else if (isUsingWrapContent()) { 164 WindowManager windowManager = 165 (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE); 166 Display display = windowManager.getDefaultDisplay(); 167 @SuppressWarnings("deprecation") final int width = display.getWidth(), height = display.getHeight(); 168 if (Log.isLoggable(TAG, Log.WARN)) { 169 Log.w(TAG, "Trying to load image into ImageView using WRAP_CONTENT, defaulting to screen" 170 + " dimensions: [" + width + "x" + height + "]. Give the view an actual width and height " 171 + " for better performance."); 172 } 173 cb.onSizeReady(width, height); 174 } else { 175 if (cbs.contains(cb)) { 176 throw new IllegalArgumentException("Cannot add a callback twice"); 177 } 178 cbs.add(cb); 179 final ViewTreeObserver observer = view.getViewTreeObserver(); 180 layoutListener = new SizeDeterminerLayoutListener(this); 181 observer.addOnPreDrawListener(layoutListener); 182 } 183 } 184 185 private boolean isViewSizeValid() { 186 return view.getWidth() > 0 && view.getHeight() > 0; 187 } 188 189 private boolean isUsingWrapContent() { 190 final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 191 return layoutParams != null && (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT 192 || layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT); 193 } 194 195 private boolean isLayoutParamsSizeValid() { 196 final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 197 return layoutParams != null && layoutParams.width > 0 && layoutParams.height > 0; 198 } 199 200 private static class SizeDeterminerLayoutListener implements ViewTreeObserver.OnPreDrawListener { 201 private final WeakReference<SizeDeterminer> sizeDeterminerRef; 202 203 public SizeDeterminerLayoutListener(SizeDeterminer sizeDeterminer) { 204 sizeDeterminerRef = new WeakReference<SizeDeterminer>(sizeDeterminer); 205 } 206 207 @Override 208 public boolean onPreDraw() { 209 if (Log.isLoggable(TAG, Log.VERBOSE)) { 210 Log.v(TAG, "OnGlobalLayoutListener called listener=" + this); 211 } 212 SizeDeterminer sizeDeterminer = sizeDeterminerRef.get(); 213 if (sizeDeterminer != null) { 214 sizeDeterminer.checkCurrentDimens(); 215 } 216 return true; 217 } 218 } 219 } 220} 221