Glide: Replace image on an imageview without showing white background?

Created on 7 Jul 2015  路  13Comments  路  Source: bumptech/glide

I have one ImageView and a button which, when pressed, loads new image (from disk, not from network) into the same ImageView. Unfortunately it causes the first image to disappear for a second (showing white background) and then it loads the new image. How can I avoid that?
I tried:

Glide.with(this).load(photoToLoad.getPath()).dontAnimate().dontTransform().into(imageView);

But the "blinking" still exists.
The only solution I can think of is using two ImageViews with load listeners and showing/hiding them once the new image is loaded.

Is there any better way to do this?

question

Most helpful comment

@TWiStErRob's second solution works perfectly, here's the code for anyone wondering:

 Glide.with(this).load(photoToLoad.getPath()).placeholder(imageView.getDrawable()).into(imageView);

All 13 comments

Basically this isn't supported directly, but you can do it with either two different Views or, if you're careful, with one View and two different targets. I believe #132 has some more information.

I can think of two ways. Both theoretical.

  1. .preload() the second image right after the first one finished (listener): get the just-finished target size and use that for preload size. It should match what the second normal load will be and will use memory cache which means immediate load.
  2. Start the second load like this: Glide....placeholder(iv.getDrawable()).into(iv);
    Edit: This is not safe, your app will crash if you use it, see 2-3 comments below.
  3. Edit: after much time I found a better way: https://github.com/bumptech/glide/issues/527#issuecomment-148840717, it can even crossfade GIFs.

@TWiStErRob's second solution works perfectly, here's the code for anyone wondering:

 Glide.with(this).load(photoToLoad.getPath()).placeholder(imageView.getDrawable()).into(imageView);

@minimalviking Unfortunately that's not safe. As soon as you start your second load, the Bitmap/Drawable from the first load may be re-used or recycled. Most of the time it will work fine, but sometimes you will get unlucky and the wrong image will be displayed briefly, or your application will throw because it tries to draw a recycled Bitmap.

@minimalviking Sam's right, please for the sake of your users, don't go with that solution.
Here's how my first suggestion would go down. I know it's a little more convoluted, but it's much much safer and highly likely works. You can hide its uglyness in a factory method or class.

final Context context = this; // or Fragment context = this;
final ImageView imageView = ...;
 Glide
        .with(context)
        .load(photoToLoad.getPath())
        .listener(new RequestListener<String, GlideDrawable>() {
            @Override public boolean onException(
                    Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
                return false;
            }
            @Override public boolean onResourceReady(
                    GlideDrawable resource, String model, Target<GlideDrawable> target,
                    boolean isFromMemoryCache, boolean isFirstResource) {  // execution order: 2
                target.getSize(new SizeReadyCallback() {
                    @Override public void onSizeReady(int width, int height) {  // execution order: 3
                        Glide.with(context).load(photoToLoad2.getPath()).preload(width, height);
                    }
                });
                return false;
            }
        })
        .into(imageView); // execution order: 1
findViewById(R.id.button).setOnClickListener(new OnClickListener() {
    @Override public void onClick(View v) { // execution order: 4
        Glide.with(context).load(photoToLoad2.getPath()).into(imageView);
    }
});

I also thought of another way while discussing the unsafeness with Sam. It mimics the second solution in a safer way, but it's still a big hack as those Drawables may have other parameters that affects the visuals.

Glide.with(context).load(photoToLoad.getPath()).asBitmap().into(imageView);
findViewById(R.id.button).setOnClickListener(new OnClickListener() {
    @Override public void onClick(View v) {
        Drawable continuityPlaceholder = copy(imageView.getDrawable());
        Glide.with(context).load(photoToLoad2.getPath()).placeholder(continuityPlaceholder).into(imageView);
    }
    private Drawable copy(Drawable drawable) { // notice the .asBitmap() on the first load
        Bitmap bitmap = null;
        if (drawable instanceof BitmapDrawable) {
            bitmap = ((BitmapDrawable)drawable).getBitmap();
        } else if (drawable instanceof GlideBitmapDrawable) {
            bitmap = ((GlideBitmapDrawable)drawable).getBitmap();
        }
        if (bitmap != null) {
            bitmap = bitmap.copy(bitmap.getConfig(), bitmap.isMutable());
            return new BitmapDrawable(getResources(), bitmap);
        } else {
            return null;
        }
    }
});

@TWiStErRob thanks for such a detailed response!
I'll go with your second solution as it seems cleaner to me.

@minimalviking I found a much simpler and no-hack way:

Glide
    .with(context)
    .load(imageUrl)
    .thumbnail(Glide // this thumbnail request has to have the same RESULT cache key
            .with(context) // as the outer request, which usually simply means
            .load(oldImage) // same size/transformation(e.g. centerCrop)/format(e.g. asBitmap)
            .fitCenter() // have to be explicit here to match outer load exactly
    )
    .listener(new RequestListener<String, GlideDrawable>() {
        @Override public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
            if (isFirstResource) {
                return false; // thumbnail was not shown, do as usual
            }
            return new DrawableCrossFadeFactory<Drawable>(/* customize animation here */)
                    .build(false, false) // force crossFade() even if coming from memory cache
                    .animate(resource, (ViewAdapter)target);
        }
    })
    //.fitCenter() // this is implicitly added when .into() is called if there's no scaleType in xml or the value is fitCenter there
    .into(imageView)
;
oldImage = imageUrl;

How does it work? you ask. When an image is loaded into a target (ImageView) it's an active resource. When a new load is started (into) the target is cleared, pushing the active resource in it into memory cache. Thumbnail has a higher priority than the load it's attached to. Starting a load with thumbnail finds the just-memory-cached because the thumbnail's params match the outer load's. This happens immediately with memory cached resources which means that the clear that put it into memory cache and the load that puts it back into target are both run immediately one after the other on the main thread which leaves no time for the ImageView to re-draw itself empty, hence no white flash.

I found this way to solve the problem:

Glide.with(AppAdapter.context())
                     .load(<your_url_here>)
                    .centerCrop()
                    .crossFade()
                    .placeholder(holder.mLogo.getDrawable() != null ? holder.mLogo.getDrawable() : null)
                    .error(R.drawable.error_logo)
                    .into(holder.mLogo);

@mikhail709 this was addressed before in this very issue and it is not safe (may crash).
Please read the 4 comments starting with https://github.com/bumptech/glide/issues/527#issuecomment-119229629

@TWiStErRob is there an updated version of your solution for v4? GlideDrawable will not resolve in the new version.

@wezley98 read the compile errors, it lists the expected types.

Had a use case to refresh webcam images from a static URI. The problem for static URI is that is you want the image to refresh you have to either skip the cache or make up your own key for the resource. Following @TWiStErRob approach with a little adaptation works perfectly. I'll leave here if anyone is interested in future.
First you should do an initial fetch and store the timestamp (used as cache key):

oldPhotoKey = new ObjectKey(String.valueOf(System.currentTimeMillis()));
        GlideApp.with(getActivity())
                .load(static_uri)
                .signature(oldPhotoKey)
                .into(imageView);

Then the refresh code, maybe called from a CoutDownTimer:

ObjectKey newPhotoKey = new ObjectKey(String.valueOf(System.currentTimeMillis()));
GlideApp.with(getActivity())
                .load(static_uri)
                .signature(newPhotoKey)
                .thumbnail(GlideApp.with(getActivity())
                        .load(static_uri)
                        .signature(oldPhotoKey)
                        .fitCenter()
                )
                .into(imageView);
oldPhotoKey = newPhotoKey;

What happens here is that we assign the key for the cache when we store the image and than reuse that previous image for thumbnail just like mentioned in the suggested implementation by @TWiStErRob, but instead of only using URI as caching key we append a timestamp to it. In my case I'm fetching images every four seconds and the memory looks stable.

@TWiStErRob Hi, it doesn't load anything in my case:

        GlideApp
            .with(this)
            .load(image.url)
            .thumbnail(
                Glide // this thumbnail request has to have the same RESULT cache key
                    .with(this) // as the outer request, which usually simply means
                    .load(image.url) // same size/transformation(e.g. centerCrop)/format(e.g. asBitmap)
                    .fitCenter() // have to be explicit here to match outer load exactly
            )
            //.fitCenter() // this is implicitly added when .into() is called if there's no scaleType in xml or the value is fitCenter there
            .into(detailImage)

---- Turns out it's the TouchImageView's problem -----

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kooeasy picture kooeasy  路  3Comments

Tryking picture Tryking  路  3Comments

sergeyfitis picture sergeyfitis  路  3Comments

Anton111111 picture Anton111111  路  3Comments

MrFuFuFu picture MrFuFuFu  路  3Comments