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