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 requestsTransitionNot 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:

  1. When I first loaded the image,previousplaceholder(If set)
  2. 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 copyBitmap, so pay attention to the memory footprint
  • Demo
  • There is a better way to hope to share