Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a Lottie animation file as a placeholder with glide

I am using Glide to load images to my activity (inside a recycler view) and I want to use a Lottie loading animation file as a place holder.

so this is my glide code:

Glide.with(mContext)
                .load(PATH_TO_FILE)
                .placeholder(createLottieDrawable("loading_animation.json"))
                .into(holder.img_view);

and this is the method createLottieDrawable which I use to create a drawable from the lottie file:

private LottieDrawable createLottieDrawable(String filename) {
final LottieDrawable lottieDrawable = new LottieDrawable();

LottieComposition.Factory.fromAssetFileName(mContext, filename,
        composition -> {
            lottieDrawable.setComposition(composition);
            lottieDrawable.loop(true);
            lottieDrawable.playAnimation();
        });

return lottieDrawable;
}

but it doesn't work and I don't see anything as a placeholder.

Any idea why?

like image 446
TheAndoOne Avatar asked Jan 16 '20 22:01

TheAndoOne


People also ask

How do I embed a Lottie file?

Just enter the URL to your Lottie file. You can play around with the options to customize the player or animation. Then copy the code and paste it into your website's HTML. You can also click Use this animation in <html> on any animation page to automatically generate the code.

What is place holder in Glide?

Placeholder. Placeholders are Drawables that are shown while a request is in progress. When a request completes successfully, the placeholder is replaced with the requested resource. If the requested resource is loaded from memory, the placeholder may never be shown.

How do you set up Lottie animation?

Step 1: Add this dependency into the App level gradle module of the project and then sync the gradle with the project. This library enables us to use Lottie's animations: Java.


Video Answer


2 Answers

This answer provides an alternative approach solution to your problem and doesn't directly answer it.

As I mentioned before in my comment above I would suggest you use a LottieAnimationView for loading animations instead of the placeholder approach. It is more robust and Lottie docs suggest it for loading animations. The way I did it is I placed the LottieAnimationView on top of the image I'm loading and play the animation by default. I attach a RequestListener with addListener() to the builder methods, then stop the animation and set the LottieAnimationView's visibility to GONE inside onResourceReady() callback of the listener.

Inside your list_item.xml put your LottieAnimationView on top of your Imageview by nesting them inside a FrameLayout like so:

<FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/list_item_image"
            android:layout_width="match_parent"
            android:layout_height="@dimen/list_item_cover_image_height"
            android:layout_gravity="center"
            android:adjustViewBounds="true"
            android:scaleType="centerCrop" />

        <com.airbnb.lottie.LottieAnimationView
            android:id="@+id/image_pending_animation"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            app:lottie_autoPlay="true"
            app:lottie_fileName="image-loading.json"
            app:lottie_loop="true"
            app:lottie_scale="2" />
</FrameLayout>

Then in your adapter class's onBindViewHolder() method you can load the image and attach a RequestListener to know when it's ready like this:

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
    ://other code omitted
    ://holder.cover is the ImageView
    ://holder.pendingImage is the LottieAnimationView
    GlideApp.with(context)
                .load(item.getImageUri())
                .addListener(imageLoadingListener(holder.pendingImage))//this line
                .into(holder.cover);
}

The following is an implementation of the RequestListener, I made it public because I use it somewhere else:

public static RequestListener<Drawable> imageLoadingListener(final LottieAnimationView pendingImage) {
    return new RequestListener<Drawable>() {
        @Override
        public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
            return false;
        }

        @Override
        public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
            //hide the animation
            pendingImage.pauseAnimation();
            pendingImage.setVisibility(View.GONE);
            return false;//let Glide handle everything else
        }
    };
}

NB: The Type on the RequestListener can also be Bitmap, like RequestListener<Bitmap> just make sure to change the parameters accordingly.

like image 124
Edgar Avatar answered Oct 09 '22 05:10

Edgar


Based on Edgar's response above, I translated this using DataBinding and it's really quick to implement and use.

First off, wherever you have your image being loaded from a URL, add a LottieAnimationView over it.

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="match_parent"
        android:layout_height="140dp"
        app:loader="@{lavLoader}"
        app:url="@{horizontalCardsItemVM.imageUrl}" />

    <com.airbnb.lottie.LottieAnimationView
        android:id="@+id/lav_loader"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignStart="@id/iv_image"
        android:layout_alignTop="@id/iv_image"
        android:layout_alignEnd="@id/iv_image"
        android:layout_alignBottom="@id/iv_image"
        app:lottie_autoPlay="true"
        app:lottie_loop="true"
        app:lottie_rawRes="@raw/image_loader" />
</FrameLayout>

As you can see above, I'm passing a reference of the LottieAnimationView to the ImageView using app:loader="@{lavLoader}"

This reference is used in a BindingAdapter. Now in a BindingAdapter (defined statically anywhere you want), use the addListener method as mentioned by Edgar:

@BindingAdapter("url", "loader")
fun setImageSrcFromUrlWithLoader(view: ImageView, url: String?, loader: LottieAnimationView) {
    url?.let {
        Glide.with(view)
            .load(it)
            .addListener(imageLoadingListener(loader))
            .centerCrop()
            .into(view)
    }
}

And the imageLoadingListener function is the same as the one written by Edgar:

fun imageLoadingListener(pendingImage: LottieAnimationView): RequestListener<Drawable?>? {
    return object : RequestListener<Drawable?> {
        override fun onLoadFailed(e: GlideException?, model: Any?, target: com.bumptech.glide.request.target.Target<Drawable?>?, isFirstResource: Boolean): Boolean {
            return false
        }

        override fun onResourceReady(
            resource: Drawable?,
            model: Any?,
            target: com.bumptech.glide.request.target.Target<Drawable?>?,
            dataSource: DataSource?,
            isFirstResource: Boolean
        ): Boolean {
            pendingImage.pauseAnimation()
            pendingImage.visibility = View.GONE
            return false
        }
    }
}

The benefit of using this approach is now you don't need to do much in any of your actual layouts, except add the LottieAnimationView and link it to the ImageView. Once the setup is done, it's a matter of seconds to include this logic everywhere without making any tweaks to Java/Kotlin code.

like image 36
Rachit Avatar answered Oct 09 '22 05:10

Rachit