Glide transition effect between different requests
Base On Gradle 4.11.0
Glide allows us to set placeholders through Transitions, where thumbnails transition to the animation of the requested image.
DrawableCrossFadeFactory factory =
new DrawableCrossFadeFactory.Builder().build();
GlideApp.with(context)
.load(url)
.transition(withCrossFade(factory))
.placeholder(R.color.placeholder)
.into(imageView);
Copy the code
The loaded image becomes progressively more transparent, covering the placeholder.
However, between different requests, each time it becomes Placehodler (or transparent) and gradually displays the new image.
Why different requestsTransition
Not smooth transition
To solve this problem, we need to understand why the Transition isn’t smooth between requests.
First of all, how does the Transition animation work inside
DrawableCrossFadeTransition, for example, also call DrawableCrossFadeTransition realize BitmapCrossFadeTransition internal, can go to check
@Override
public boolean transition(Drawable current, ViewAdapter adapter) {
Drawable previous = adapter.getCurrentDrawable();
if (previous == null) {
previous = new ColorDrawable(Color.TRANSPARENT);
}
TransitionDrawable transitionDrawable =
new TransitionDrawable(new Drawable[] {previous, current});
transitionDrawable.setCrossFadeEnabled(isCrossFadeEnabled);
transitionDrawable.startTransition(duration);
adapter.setDrawable(transitionDrawable);
return true;
}
Copy the code
Glide’s switch animation is a TransitionDrawable
So, previous and current are important here, so let’s debug to see what their values are.
After debugging, we find:
- When I first loaded the image,
previous
为placeholder
(If set) - And when I switch urls,
adapter.getCurrentDrawable()
It is empty
So, each time the request is toggled, the image is transitioned from transparent to the requested image.
whyadapter.getCurrentDrawable()
Will be empty
When we debug, we can see that the ViewAdapter here is actually our DrawableImageViewTarget, inherited from ImageViewTraget
The code is a little long, so I won’t put it out, but I’ll check it out
The getCurrentDrawable() method returns imageView.getDrawable () directly. So, let’s look at where Drawable is set. Let’s focus on the following methods.
/* Call placeholder */ before loading
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
/* Toggle requests and push calls 1. Stop animation 2. Set placeholder */
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
if(animatable ! =null) {
animatable.stop();
}
setResourceInternal(null);
setDrawable(placeholder);
}
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
// After loading successfully, switch to display pictures and play animation
if (transition == null| |! transition.transition(resource,this)) {
setResourceInternal(resource);
} else{ maybeUpdateAnimatable(resource); }}private void setResourceInternal(@Nullable Z resource) {
setResource(resource);
maybeUpdateAnimatable(resource);
}
Copy the code
Code from
ImageViewTraget
We can see that the problem with the onLoadCleared method is that the placeholder is reset every time we switch requests. If you don’t set the placeholder, and you put in the Transition code above, you know you’re going to go from transparent to the next image.
The customTarget
Now that you know what the problem is, let’s rewrite Target to remove the onLadCleared method from ImaegViewTarget.
Rewriting, run should appear after the Execption: Java. Lang. RuntimeException: Canvas: trying to use a recycled bitmap android. Graphics. Bitmap @ XXXXXX
This is because Glide recycles the Bitmap on onLoadCleared, so TransitionDrawable will raise this issue when the next image is displayed. To resolve this issue, use onResourceReady, It is used to copy a Bitmap to display, but it is not used to retrieve the Bitmap for you. Note that this is why It is used to display animation between requests without default.
Final code:
class TransitionImageTarget(val imageView: ImageView) :
CustomViewTarget<ImageView, Drawable>(imageView), ViewAdapter {
private var animatable: Animatable? = null
override fun onLoadFailed(errorDrawable: Drawable?). {
setResourceInternal(null)
setDrawable(errorDrawable)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>? {
var dst: Drawable = resource
// Avoid Glide recovery
//java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@xxxxxx
if (resource is BitmapDrawable) {
val bmp = resource.bitmap
dst = BitmapDrawable(imageView.resources, bmp.copy(bmp.config, true))}if (transition == null| |! transition.transition(dst,this)) {
setResourceInternal(dst)
} else {
maybeUpdateAnimatable(dst)
}
}
override fun onResourceCleared(placeholder: Drawable?).{ animatable? .stop() }private fun setResourceInternal(resource: Drawable?). {
setDrawable(resource)
maybeUpdateAnimatable(resource)
}
private fun maybeUpdateAnimatable(resource: Drawable?). {
if (resource is Animatable) {
animatable = resource asAnimatable? animatable? .start() }else {
animatable = null}}override fun getCurrentDrawable(a): Drawable? {
return imageView.drawable
}
override fun setDrawable(drawable: Drawable?). {
imageView.setImageDrawable(drawable)
}
}
Copy the code
At this point, you should see a noticeable transition effect, but the animation disappears when you reload the cached image, because DrawableCrossFadeFactory will use NoTransition when the image comes from the cache
@Override
public Transition<Drawable> build(DataSource dataSource, boolean isFirstResource) {
return dataSource == DataSource.MEMORY_CACHE
? NoTransition.<Drawable>get()
: getResourceTransition();
}
Copy the code
code from
DrawableCrossFadeFactory
To display each switch, you can customize the TransitionFactory
private var factory = TransitionFactory<Drawable> { dataSource, isFirstResource ->
DrawableCrossFadeTransition(1000.false)
}
Glide.with(this)
.load(images[(index++ % images.size)])
.placeholder(ColorDrawable(Color.GRAY))
.error(ColorDrawable(Color.DKGRAY))
.transition(DrawableTransitionOptions.with(factory))
.into(TransitionImageTarget(binding.ivResult))
Copy the code
The last
- Because the display picture has a copy
Bitmap
, so pay attention to the memory footprint - Demo
- There is a better way to hope to share