luxiaoyu
9/7/2015 - 12:53 PM

Helper Class allowing to load multiple images consecutively in the same view with Glide and animate between them.

Helper Class allowing to load multiple images consecutively in the same view with Glide and animate between them.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.animation.AlphaAnimation;
import android.widget.ImageView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.request.Request;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.ViewTarget;

import java.util.Arrays;



/**
 * Helper class to use when you  want to load several images consecutively without
 * displaying the default placeholder between them.<br>
 * To initiate a load, instead of the usual {@code Glide.with(ctx).load(model).into(view);}, you need to call
 * {@link DiaporamaAdapter#loadNextImage(Object)} or {@link DiaporamaAdapter#loadNextImage(Object, BitmapTransformation...)}.
 *
 * @author François
 */
public class DiaporamaAdapter {

    @Nullable
    private Drawable mPlaceholder;

    @NonNull
    private final ImageView mImageView;

    private final Context mContext;

    /**
     * In order to do not conflict with Glide's clear() calls, we need to use two targets and to
     * switch between them each time an image is loaded.
     */
    @NonNull
    private DiaporamaViewTarget mCurrentTarget;
    @NonNull
    private DiaporamaViewTarget mNextTarget;

    private int mAnimationDuration;
    private int mLastLoadHash;

    /**
     * Default constructor, initiate a Diaporama Adapter with the terminal's {@code config_mediumAnimTime}
     * for animation duration and no placeholder.
     *
     * @param imageView the imageView where the images will be loaded
     */
    public DiaporamaAdapter(final @NonNull ImageView imageView) {
        this(imageView, -1, -1);
    }

    /**
     * @param imageView         the imageView where the images will be loaded
     * @param animationDuration the animationDuration to use or {@code -1} to use the platform's {@code config_mediumAnimTime}
     * @param placeholderResId  the drawableResId to use or {@code -1} if you don't want to display one.
     */
    public DiaporamaAdapter(final @NonNull ImageView imageView,
                            final int animationDuration,
                            final @DrawableRes int placeholderResId) {
        mImageView = imageView;
        mContext = imageView.getContext();

        mCurrentTarget = new DiaporamaViewTarget(mImageView);
        mNextTarget = new DiaporamaViewTarget(mImageView);
        mCurrentTarget.setPreviousTarget(mNextTarget);
        mNextTarget.setPreviousTarget(mCurrentTarget);


        if (animationDuration != -1) {
            mAnimationDuration = animationDuration;
        } else {
            mAnimationDuration = mImageView.getResources().getInteger(
                    android.R.integer.config_mediumAnimTime);
        }

        if (placeholderResId != -1) setPlaceholder(placeholderResId);
    }


    //////////////////////////////////////////////////////////////////////////////////////
    // public methods
    //////////////////////////////////////////////////////////////////////////////////////

    public <T> void loadNextImage(@NonNull T model,
                                  @NonNull BitmapTransformation... transformations) {
        //noinspection MagicNumber
        int hash = model.hashCode() + 31 * Arrays.hashCode(transformations);
        if (mLastLoadHash == hash) return;
        Glide.with(mContext).load(model).asBitmap().transform(transformations).into(mCurrentTarget);
        mLastLoadHash = hash;
    }

    public <T> void loadNextImage(@NonNull T model) {
        int hash = model.hashCode();
        if (mLastLoadHash == hash) return;
        Glide.with(mContext).load(model).asBitmap().into(mCurrentTarget);
        mLastLoadHash = hash;
    }

    public void showPlaceholderIfSet() {
        if (mPlaceholder == null || mPlaceholder == mImageView.getDrawable()) return;
        mImageView.setImageDrawable(mPlaceholder);
        Glide.clear(mNextTarget);
        Glide.clear(mCurrentTarget);
        mNextTarget.mLoadedDrawable = null;
        mCurrentTarget.mLoadedDrawable = null;
        mLastLoadHash = 0;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // Setters
    //////////////////////////////////////////////////////////////////////////////////////

    public void setAnimationDuration(int animationDuration) {
        mAnimationDuration = animationDuration;
    }

    public void setPlaceholder(@DrawableRes int resId) {
        Bitmap bitmap = BitmapFactory.decodeResource(mImageView.getResources(), resId);
        mPlaceholder = new BitmapDrawable(mImageView.getResources(), bitmap);
        mImageView.setImageDrawable(mPlaceholder);
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // ViewTarget
    //////////////////////////////////////////////////////////////////////////////////////

    private class DiaporamaViewTarget extends ViewTarget<ImageView, Bitmap> {

        @NonNull
        private DiaporamaViewTarget mPreviousTarget;

        @Nullable
        private BitmapDrawable mLoadedDrawable;

        @Nullable
        private Request mRequest;

        public DiaporamaViewTarget(ImageView view) {
            super(view);
        }

        public void setPreviousTarget(@NonNull DiaporamaViewTarget previousTarget) {
            mPreviousTarget = previousTarget;
        }

        @Nullable
        public BitmapDrawable getLoadedDrawable() {
            return mLoadedDrawable;
        }


        @Override
        public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
            Drawable previousDrawable = getPreviousImageForTransition();
            mLoadedDrawable = new BitmapDrawable(mImageView.getResources(), resource);

            // we null the other Target loaded drawable so it can eventually be reclaimed.
            mPreviousTarget.mLoadedDrawable = null;

            DiaporamaAdapter.this.mCurrentTarget = mPreviousTarget;
            DiaporamaAdapter.this.mNextTarget = this;


            if (previousDrawable != null) {
                final Drawable[] layers = new Drawable[2];
                // Prevent cascade of TransitionDrawables.
                if (previousDrawable instanceof TransitionDrawable) {
                    final TransitionDrawable previousTransitionDrawable =
                            (TransitionDrawable) previousDrawable;
                    layers[0] = previousTransitionDrawable.getDrawable(
                            previousTransitionDrawable.getNumberOfLayers() - 1);
                } else {
                    layers[0] = previousDrawable;
                }
                layers[1] = mLoadedDrawable;
                TransitionDrawable drawable = new TransitionDrawable(layers);
                view.setImageDrawable(drawable);
                drawable.startTransition(mAnimationDuration);
            } else {
                AlphaAnimation alphaAnimation = new AlphaAnimation(0f, 1f);
                alphaAnimation.setDuration(mAnimationDuration);
                mImageView.setAnimation(alphaAnimation);
                alphaAnimation.start();
                view.setImageDrawable(mLoadedDrawable);
            }
        }

        @Override
        public void onLoadFailed(Exception e, Drawable errorDrawable) {
            mLastLoadHash = 0;
        }

        @Override
        public void setRequest(@Nullable Request request) {
            mRequest = request;
        }

        @Override
        @Nullable
        public Request getRequest() {
            return mRequest;
        }

        @Override
        public void onLoadCleared(Drawable placeholder) {
            super.onLoadCleared(placeholder);
            // we need to null the drawable in order to make sure that a recycled bitmap won't be reused.
            mLoadedDrawable = null;
        }


        @Nullable
        private Drawable getPreviousImageForTransition() {
            if (mPreviousTarget.getLoadedDrawable() != null) {
                return mPreviousTarget.getLoadedDrawable();
            }
            if (mPlaceholder != null) return mPlaceholder;

            return null;
        }
    }


}