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