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