android – Glide: load drawable but don't scale placeholder-ThrowExceptions

Exception or error:

Is there a way to use Glide to assign a placeholder but keep this image to its original scale?
I have an ImageView with variable size (depending on the incoming image) which I set before calling Glide.with().load().into() and I want to use a placeholder for it but don’t want the placeholder to be resized to the size of the ImageView, I want it to keep its original size.

So far I couldn’t find a way to do this.

How to solve:

Re: Gregs comment:
I gave it another shot (with almost a year of extra XP) and came up with this:

<ImageView android:scaleType="center" ... />

similar to my other solution and the following animation wrapper:

...
    .fitCenter()
    .animate(new PaddingAnimationFactory<>(new DrawableCrossFadeFactory<GlideDrawable>(2000)))
    .into(imageView)
;

class PaddingAnimationFactory<T extends Drawable> implements GlideAnimationFactory<T> {
    private final DrawableCrossFadeFactory<T> realFactory;
    @Override public GlideAnimation<T> build(boolean isFromMemoryCache, boolean isFirstResource) {
        return new PaddingAnimation<>(realFactory.build(isFromMemoryCache, isFirstResource));
    }
}

class PaddingAnimation<T extends Drawable> implements GlideAnimation<T> {
    private final GlideAnimation<? super T> realAnimation;
    @Override public boolean animate(T current, final ViewAdapter adapter) {
        int width = current.getIntrinsicWidth();
        int height = current.getIntrinsicHeight();
        return realAnimation.animate(current, new PaddingViewAdapter(adapter, width, height));
    }
}

class PaddingViewAdapter implements ViewAdapter {
    @Override public Drawable getCurrentDrawable() {
        Drawable drawable = realAdapter.getCurrentDrawable();
        if (drawable != null) {
            int padX = Math.max(0, targetWidth - drawable.getIntrinsicWidth()) / 2;
            int padY = Math.max(0, targetHeight - drawable.getIntrinsicHeight()) / 2;
            if (padX != 0 || padY != 0) {
                drawable = new InsetDrawable(drawable, padX, padY, padX, padY);
            }
        }
        return drawable;
    }
    @Override public void setDrawable(Drawable drawable) {
        if (VERSION.SDK_INT >= VERSION_CODES.M && drawable instanceof TransitionDrawable) {
            // For some reason padding is taken into account differently on M than before in LayerDrawable
            // PaddingMode was introduced in 21 and gravity in 23, I think NO_GRAVITY default may play
            // a role in this, but didn't have time to dig deeper than this.
            ((TransitionDrawable)drawable).setPaddingMode(TransitionDrawable.PADDING_MODE_STACK);
        }
        realAdapter.setDrawable(drawable);
    }
}

Trivial parts of the implementations are omitted, each class’s constructor initializes the fields from arguments. Full code available on GitHub in TWiStErRob/glide-support.

crossFaded placeholder


If you’re stuck on an older version of Glide (before 3.8.0), the same effect can be achieved by:

.fitCenter()
.placeholder(R.drawable.glide_placeholder)
.crossFade(2000)
.into(new GlideDrawableImageViewTarget(imageView) {
    @Override public void onResourceReady(GlideDrawable resource,
            GlideAnimation<? super GlideDrawable> animation) {
        super.onResourceReady(resource, new PaddingAnimation<>(animation));
    }
})

Note how the two solutions require the same amount of classes, but the post-3.8.0 solution has better separation of concerns and it can be cached in a variable to prevent allocations.

###

There’s a known Glide issue of placeholders distorting loaded images and vice versa. However I think you are not affected by that.

It sounds like what you want is to show the placeholder drawable with scaleType="center" and you want the loaded image to be scaleType="fitCenter". Here’s how you express that in Glide. Add android:scaleType="center" in the XML and use the following Glide load line:

Glide.with(...).load(...).placeholder(R.drawable....).fitCenter().into(imageView);

The trick is that the placeholder is set via setImageDrawable() so the ImageView will just display it as usual, but you tell Glide to use the FitCenter explicitly which will fit the loaded image nicely within the ImageView’s laid out size via a Transformation and then set it via setImageDrawable(). Since the fitted image is a perfect fit, center will just draw the image covering the whole area of the view.

You can also use .centerCrop() the same way.

If something is wrong you can try .asBitmap() and .dontAnimate(), they help most of the time in one way or another.

###

I do it like mentioned below:

The idea is to set the scale type to as required by the place holder initially & attach listener to change the scale type again to as required by the downloaded image after the image is downloaded.

//ivBuilderLogo = Target ImageView
//Set the scale type to as required by your place holder
//ScaleType.CENTER_INSIDE will maintain aspect ration and fit the placeholder inside the image view
holder.ivBuilderLogo.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 

//AnimationDrawable is required when you are using transition drawables
//You can directly send resource id to glide if your placeholder is static
//However if you are using GIFs, it is better to create a transition drawable in xml 
//& use it as shown in this example
AnimationDrawable animationDrawable;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
     animationDrawable=(AnimationDrawable)context.getDrawable(R.drawable.anim_image_placeholder);
else
     animationDrawable=(AnimationDrawable)context.getResources().getDrawable(R.drawable.anim_image_placeholder);
animationDrawable.start();

Glide.with(context).load(logo_url)
                   .placeholder(animationDrawable)
                   .listener(new RequestListener<String, GlideDrawable>() {
                        @Override
                        public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource)
                        {
                           return false;
                        }

                        //This is invoked when your image is downloaded and is ready 
                        //to be loaded to the image view
                        @Override
                        public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource)
                        {   
                        //This is used to remove the placeholder image from your ImageView 
                        //and load the downloaded image with desired scale-type(FIT_XY in this case)
                        //Changing the scale type from 'CENTER_INSIDE' to 'FIT_XY' 
                        //will stretch the placeholder for a (very) short duration,
                        //till the downloaded image is loaded
                        //setImageResource(0) removes the placeholder from the image-view 
                        //before setting the scale type to FIT_XY and ensures that the UX 
                        //is not spoiled, even for a (very) short duration
                            holder.ivBuilderLogo.setImageResource(0);
                            holder.ivBuilderLogo.setScaleType(ImageView.ScaleType.FIT_XY);
                            return false;
                        }
                    })
                    .into( holder.ivBuilderLogo);

My transition drawable (R.drawable.anim_image_placeholder) :

(not required if using a static image)

<?xml version="1.0" encoding="utf-8"?>
<animation-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/loading_frame1" android:duration="100" />
    <!--<item android:drawable="@drawable/loading_frame2" android:duration="100" />-->
    <item android:drawable="@drawable/loading_frame3" android:duration="100" />
    <!--<item android:drawable="@drawable/loading_frame4" android:duration="100" />-->
    <item android:drawable="@drawable/loading_frame5" android:duration="100" />
    <!--<item android:drawable="@drawable/loading_frame6" android:duration="100" />-->
    <item android:drawable="@drawable/loading_frame7" android:duration="100" />
    <!--<item android:drawable="@drawable/loading_frame8" android:duration="100" />-->
    <item android:drawable="@drawable/loading_frame9" android:duration="100" />
    <!--<item android:drawable="@drawable/loading_frame10" android:duration="100" />-->
</animation-list>

Leave a Reply

Your email address will not be published. Required fields are marked *