Search code examples
androidandroid-animationandroid-imageviewandroid-glide

Fading animation not working when an image is loaded with .asBitmap()


I have this code

Glide
            .with(this)
            .load(mUrl)
            .asBitmap()
            .thumbnail(Glide
                    .with(this)
                    .load(mPreviewUrl)
                    .asBitmap()
                    .animate(R.anim.fade_in))
            .listener(new RequestListener<String, Bitmap>() {
                @Override
                public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {
                    mProgressBar.setVisibility(View.GONE);
                    return false;
                }

                @Override
                public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {
                    mProgressBar.setVisibility(View.GONE);
                    return false;
                }
            })
            .into(new SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
                @Override
                public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
                    mWallpaperImageView.setImageBitmap(resource);
                    createPaletteAsync(resource);
                }
            });

This downloads an image and returns a Bitmap object, which then I use to generate a palette of colour with the Palette library. I also want to load the bitmap in an ImageView, and I do that with mWallpaperImageView.setImageBitmap(resource) which loads the image without any fading or animation to smooth out the loading.

If I use glide like this:

Glide.with(this)
            .load(mUrl)
            .crossFade(500)
            .into(mImageView)

then the image appears with fading but then I don't have a Bitmap object.


Solution

  • According to this answer,

    Unfortunately, there isn't a built in way to cross fade Bitmaps. You can however, use a custom BitmapImageViewTarget, and use a TransitionDrawable in onResourceReady() to cross fade.

    Fortunately, there's a trick to enable fading effect with bitmap in Glide.

    For this you will need two classes,

    FadingDrawable

    final public class FadingDrawable extends BitmapDrawable {
        // Only accessed from main thread.
        private static final float FADE_DURATION = 200; //ms
        private final float density;
        Drawable placeholder;
        long startTimeMillis;
        boolean animating;
        int alpha = 0xFF;
    
        FadingDrawable(Context context, Bitmap bitmap, Drawable placeholder) {
            super(context.getResources(), bitmap);
    
            this.density = context.getResources().getDisplayMetrics().density;
    
            this.placeholder = placeholder;
            animating = true;
            startTimeMillis = SystemClock.uptimeMillis();
        }
    
        /**
         * Create or update the drawable on the target {@link android.widget.ImageView} to display the supplied bitmap
         * image.
         */
        static public void setBitmap(ImageView target, Context context, Bitmap bitmap) {
            if (bitmap != null && !bitmap.isRecycled()) {
                Drawable placeholder = target.getDrawable();
                if (placeholder instanceof AnimationDrawable) {
                    ((AnimationDrawable) placeholder).stop();
                }
                FadingDrawable drawable = new FadingDrawable(context, bitmap, placeholder);
    
                //this will avoid OverDraw
                //target.setBackgroundDrawable(null);
                //target.setBackgroundColor(0);
    
                target.setImageDrawable(drawable);
    
            }
        }
    
        /**
         * Create or update the drawable on the target {@link android.widget.ImageView} to display the supplied
         * placeholder image.
         */
        static void setPlaceholder(ImageView target, Drawable placeholderDrawable) {
            target.setImageDrawable(placeholderDrawable);
            if (target.getDrawable() instanceof AnimationDrawable) {
                ((AnimationDrawable) target.getDrawable()).start();
            }
        }
    
        @Override
        public void draw(Canvas canvas) {
            if (!animating) {
                super.draw(canvas);
            } else {
                float normalized = (SystemClock.uptimeMillis() - startTimeMillis) / FADE_DURATION;
                if (normalized >= 1f) {
                    animating = false;
                    placeholder = null;
                    super.draw(canvas);
                } else {
                    if (placeholder != null) {
                        placeholder.draw(canvas);
                    }
    
                    int partialAlpha = (int) (alpha * normalized);
                    super.setAlpha(partialAlpha);
                    super.draw(canvas);
                    super.setAlpha(alpha);
                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
                        invalidateSelf();
                    }
                }
            }
    
    
        }
    
        @Override
        public void setAlpha(int alpha) {
            this.alpha = alpha;
            if (placeholder != null) {
                placeholder.setAlpha(alpha);
            }
            super.setAlpha(alpha);
        }
    
        @Override
        public void setColorFilter(ColorFilter cf) {
            if (placeholder != null) {
                placeholder.setColorFilter(cf);
            }
            super.setColorFilter(cf);
        }
    
        @Override
        protected void onBoundsChange(Rect bounds) {
            if (placeholder != null) {
                placeholder.setBounds(bounds);
            }
            super.onBoundsChange(bounds);
        }
    }
    

    and GlideImageView

    public class GlideImageView extends AppCompatImageView {
        public GlideImageView(Context context) {
            this(context, null);
        }
    
        public GlideImageView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public GlideImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            Drawable placeholder = getDrawable();
            if (placeholder instanceof AnimationDrawable) {
                ((AnimationDrawable) placeholder).stop();
                Glide.clear(this);
            }
        }
    
        @Override
        public void setImageBitmap(Bitmap bitmap) {
            if (bitmap != null) FadingDrawable.setBitmap(this, getContext(), bitmap);
        }
    
        public void setImageBitmapWithoutAnimation(Bitmap bitmap) {
            super.setImageBitmap(bitmap);
        }
    }
    

    Now change your mWallpaperImageView from ImageView to

    <com.yourpackage.GlideImageView
        android:id="@+id/mWallpaperImageView" 
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    

    And finally remove all external animation effect from Glide configuration,

    Glide
         .with(this)
         .load(mUrl)
         .asBitmap()
         .thumbnail(Glide
                        .with(this)
                        .load(mPreviewUrl)
                        .asBitmap()
                .listener(new RequestListener<String, Bitmap>() {
                    @Override
                    public boolean onException(Exception e, String model, Target<Bitmap> target, boolean isFirstResource) {
                        mProgressBar.setVisibility(View.GONE);
                        return false;
                    }
    
                    @Override
                    public boolean onResourceReady(Bitmap resource, String model, Target<Bitmap> target, boolean isFromMemoryCache, boolean isFirstResource) {
                        mProgressBar.setVisibility(View.GONE);
                        return false;
                    }
                })
                .into(new SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
                    @Override
                    public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
                        mWallpaperImageView.setImageBitmap(resource);
                        createPaletteAsync(resource);
                    }
                }); 
    

    Tested and working properly!