package com.bumptech.glide.request.target; import android.content.Context; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.util.Log; import android.view.Display; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.WindowManager; import com.bumptech.glide.request.Request; import com.bumptech.glide.util.Preconditions; import com.bumptech.glide.util.Synthetic; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * A base {@link Target} for loading {@link android.graphics.Bitmap}s into {@link View}s that * provides default implementations for most most methods and can determine the size of views using * a {@link android.view.ViewTreeObserver.OnDrawListener}. * *
To detect {@link View} reuse in {@link android.widget.ListView} or any {@link * android.view.ViewGroup} that reuses views, this class uses the {@link View#setTag(Object)} method * to store some metadata so that if a view is reused, any previous loads or resources from previous * loads can be cancelled or reused. * *
Any calls to {@link View#setTag(Object)}} on a View given to this class will result in * excessive allocations and and/or {@link IllegalArgumentException}s. If you must call {@link * View#setTag(Object)} on a view, use {@link #setTagId(int)} to specify a custom tag for Glide to * use. * *
Subclasses must call super in {@link #onLoadCleared(Drawable)}
*
* @param This is an experimental API that may be removed in a future version.
*
* Using this method can save memory by allowing Glide to more eagerly clear resources when
* transitioning screens or swapping adapters in scrolling views. However it also substantially
* increases the odds that images will not be in memory if users subsequently return to a screen
* where images were previously loaded. Whether or not this happens will depend on the number
* of images loaded in the new screen and the size of the memory cache. Increasing the size of
* the memory cache can improve this behavior but it largely negates the memory benefits of using
* this method.
*
* Use this method with caution and measure your memory usage to ensure that it's actually
* improving your memory usage in the cases you care about.
*/
// Public API.
@NonNull
@SuppressWarnings({"UnusedReturnValue", "WeakerAccess"})
public final ViewTarget By default, Glide will only wait for a pending layout pass if it's unable to resolve the
* size from the {@link LayoutParams} or valid non-zero values for {@link View#getWidth()} and
* {@link View#getHeight()}.
*
* Because calling this method forces Glide to wait for the layout pass to occur before
* starting loads, setting this parameter to {@code true} can cause Glide to asynchronous load
* an image even if it's in the memory cache. The load will happen asynchronously because Glide
* has to wait for a layout pass to occur, which won't necessarily happen in the same frame as
* when the image is requested. As a result, using this method can resulting in flashing in some
* cases and should be used sparingly.
*
* If the {@link LayoutParams} of the wrapped {@link View} are set to fixed sizes, they will
* still be used instead of the {@link View}'s dimensions even if this method is called. This
* parameter is a fallback only.
*/
@SuppressWarnings("WeakerAccess") // Public API
@NonNull
public final ViewTarget For Glide to function correctly, Glide must be the only thing that calls {@link
* View#setTag(Object)}. If the tag is cleared or put to another object type, Glide will not be
* able to retrieve and cancel previous loads which will not only prevent Glide from reusing
* resource, but will also result in incorrect images being loaded and lots of flashing of images
* in lists. As a result, this will throw an {@link java.lang.IllegalArgumentException} if {@link
* android.view.View#getTag()}} returns a non null object that is not an {@link
* com.bumptech.glide.request.Request}.
* If no tag id is set, Glide will use {@link View#setTag(Object)}.
*
* Warning: prior to Android 4.0 tags were stored in a static map. Using this method prior
* to Android 4.0 may cause memory leaks and isn't recommended. If you do use this method
* on older versions, be sure to call {@link com.bumptech.glide.RequestManager#clear(View)} on
* any view you start a load into to ensure that the static state is removed.
* See #2237.
*/
void removeCallback(@NonNull SizeReadyCallback cb) {
cbs.remove(cb);
}
void clearCallbacksAndListener() {
// Keep a reference to the layout attachStateListener and remove it here
// rather than having the observer remove itself because the observer
// we add the attachStateListener to will be almost immediately merged into
// another observer and will therefore never be alive. If we instead
// keep a reference to the attachStateListener and remove it here, we get the
// current view tree observer and should succeed.
ViewTreeObserver observer = view.getViewTreeObserver();
if (observer.isAlive()) {
observer.removeOnPreDrawListener(layoutListener);
}
layoutListener = null;
cbs.clear();
}
private boolean isViewStateAndSizeValid(int width, int height) {
return isDimensionValid(width) && isDimensionValid(height);
}
private int getTargetHeight() {
int verticalPadding = view.getPaddingTop() + view.getPaddingBottom();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.height : PENDING_SIZE;
return getTargetDimen(view.getHeight(), layoutParamSize, verticalPadding);
}
private int getTargetWidth() {
int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;
return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);
}
private int getTargetDimen(int viewSize, int paramSize, int paddingSize) {
// We consider the View state as valid if the View has non-null layout params and a non-zero
// layout params width and height. This is imperfect. We're making an assumption that View
// parents will obey their child's layout parameters, which isn't always the case.
int adjustedParamSize = paramSize - paddingSize;
if (adjustedParamSize > 0) {
return adjustedParamSize;
}
// Since we always prefer layout parameters with fixed sizes, even if waitForLayout is true,
// we might as well ignore it and just return the layout parameters above if we have them.
// Otherwise we should wait for a layout pass before checking the View's dimensions.
if (waitForLayout && view.isLayoutRequested()) {
return PENDING_SIZE;
}
// We also consider the View state valid if the View has a non-zero width and height. This
// means that the View has gone through at least one layout pass. It does not mean the Views
// width and height are from the current layout pass. For example, if a View is re-used in
// RecyclerView or ListView, this width/height may be from an old position. In some cases
// the dimensions of the View at the old position may be different than the dimensions of the
// View in the new position because the LayoutManager/ViewParent can arbitrarily decide to
// change them. Nevertheless, in most cases this should be a reasonable choice.
int adjustedViewSize = viewSize - paddingSize;
if (adjustedViewSize > 0) {
return adjustedViewSize;
}
// Finally we consider the view valid if the layout parameter size is set to wrap_content.
// It's difficult for Glide to figure out what to do here. Although Target.SIZE_ORIGINAL is a
// coherent choice, it's extremely dangerous because original images may be much too large to
// fit in memory or so large that only a couple can fit in memory, causing OOMs. If users want
// the original image, they can always use .override(Target.SIZE_ORIGINAL). Since wrap_content
// may never resolve to a real size unless we load something, we aim for a square whose length
// is the largest screen size. That way we're loading something and that something has some
// hope of being downsampled to a size that the device can support. We also log a warning that
// tries to explain what Glide is doing and why some alternatives are preferable.
// Since WRAP_CONTENT is sometimes used as a default layout parameter, we always wait for
// layout to complete before using this fallback parameter (ConstraintLayout among others).
if (!view.isLayoutRequested() && paramSize == LayoutParams.WRAP_CONTENT) {
if (Log.isLoggable(TAG, Log.INFO)) {
Log.i(TAG, "Glide treats LayoutParams.WRAP_CONTENT as a request for an image the size of"
+ " this device's screen dimensions. If you want to load the original image and are"
+ " ok with the corresponding memory cost and OOMs (depending on the input size), use"
+ " .override(Target.SIZE_ORIGINAL). Otherwise, use LayoutParams.MATCH_PARENT, set"
+ " layout_width and layout_height to fixed dimension, or use .override() with fixed"
+ " dimensions.");
}
return getMaxDisplayLength(view.getContext());
}
// If the layout parameters are < padding, the view size is < padding, or the layout
// parameters are set to match_parent or wrap_content and no layout has occurred, we should
// wait for layout and repeat.
return PENDING_SIZE;
}
private boolean isDimensionValid(int size) {
return size > 0 || size == SIZE_ORIGINAL;
}
private static final class SizeDeterminerLayoutListener
implements ViewTreeObserver.OnPreDrawListener {
private final WeakReference