使用Glide V4 实现GIF点赞动画

作者: zhuguohui | 来源:发表于2018-06-14 14:04 被阅读210次

    序言

    最近的项目中,客户提出一个点赞动画。给了一个gif图。如下

    这里写图片描述

    最终的效果是这样的。

    这里写图片描述

    这其中也有一些知识点分享给大家:

    1. 怎么动态的添加一个动画到指定的View
    2. 怎么实现GIF只播放一次
    3. 这么监听GIF播放完毕的时间(因为需要在结束时播放消失动画)

    实现

    我的思路是通过需要显示的View的getLocationInWindow 方法,拿到其相对于屏幕的坐标。然后拿到Activity 的最外层布局。即decorView。由于decorView 基本上是FrameLayout。这样就可以动态的添加一个ImageView用于显示动画。而GIF的播放我使用的是Glide V4.0 。网上有许多V3.0 播放GIF的方法。但是4.0 的还比较少。在获取GIF时长的方法中由于4.0中GifDecoder 没有提供API直接从GifDrawable 中获取。所以使用了反射的方式获取。最重要的代码我封装到了LikeUtil中。

    package com.zgh.likedemo.util;
    
    import android.annotation.SuppressLint;
    import android.app.Activity;
    import android.content.Context;
    import android.support.annotation.Nullable;
    import android.view.Gravity;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.animation.AlphaAnimation;
    import android.view.animation.Animation;
    import android.widget.FrameLayout;
    import android.widget.ImageView;
    
    import com.bumptech.glide.Glide;
    import com.bumptech.glide.load.DataSource;
    import com.bumptech.glide.load.engine.DiskCacheStrategy;
    import com.bumptech.glide.load.engine.GlideException;
    import com.bumptech.glide.load.resource.gif.GifDrawable;
    import com.bumptech.glide.request.RequestListener;
    import com.bumptech.glide.request.RequestOptions;
    import com.bumptech.glide.request.target.Target;
    import com.zgh.likedemo.R;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * Created by zhuguohui on 2018/6/13.
     */
    
    public class LikeUtil {
    
        /**
         * 显示点赞动画
         *
         * @param locationView 需要定位的view,动画将显示在其左下方
         * @param xOffset      x轴的偏移量
         * @param yOffset      y轴的偏移量
         */
        public static void showLike(View locationView, int xOffset, int yOffset) {
            if (locationView == null) {
                return;
            }
            Context context = locationView.getContext();
            if (!(context instanceof Activity)) {
                return;
            }
            //1.获取Activity最外层的DecorView
            Activity activity = (Activity) context;
            View decorView = activity.getWindow().getDecorView();
            FrameLayout frameLayout = null;
            if (decorView != null && decorView instanceof FrameLayout) {
                frameLayout = (FrameLayout) decorView;
            }
            if (frameLayout == null) {
                return;
            }
            //2.通过getLocationInWindow 获取需要显示的位置
            ImageView likeView = new ImageView(context);
            //注意不能使用warp_content 在实际的代码运行中。高度会比GIF的高度高。因此这里直接使用GIF的大小作为ImageView的大小
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(172, 219);
            int[] outLocation = new int[2];
            locationView.getLocationInWindow(outLocation);
            // 80 和 100 是一点点测试出来的。不同的需求可以自己测出需要的偏移量
            layoutParams.leftMargin = outLocation[0] + xOffset - 80;
            layoutParams.topMargin = outLocation[1] + yOffset - 100;
            layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
            likeView.setLayoutParams(layoutParams);
            frameLayout.addView(likeView);
            
            //3.创建消失动画
            Animation dismissAnimation = new AlphaAnimation(1.0f, 0.0f);
            dismissAnimation.setDuration(500);
            dismissAnimation.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }
    
                @Override
                public void onAnimationEnd(Animation animation) {
                    ViewGroup parent = (ViewGroup) likeView.getParent();
                    if (parent != null) {
                        parent.removeView(likeView);
                    }
                }
    
                @Override
                public void onAnimationRepeat(Animation animation) {
    
                }
            });
            
            //4.监听GIF消失事件
            loadOneTimeGif(context, R.drawable.ic_like_flower, likeView, new GifListener() {
                @Override
                public void gifPlayComplete() {
                    likeView.post(() -> {
                        int[] location = new int[2];
                        likeView.getLocationOnScreen(location);
    
                        likeView.startAnimation(dismissAnimation);
                    });
                }
            });
    
    
        }
    
        
        @SuppressLint("CheckResult")
        public static void loadOneTimeGif(Context context, Object model, final ImageView imageView, final GifListener gifListener) {
            RequestOptions option = new RequestOptions();
            //关闭缓存,在连续播放多个相同的Gif时,会出现第二个Gif是从最后一帧开始播放的。
            option.diskCacheStrategy(DiskCacheStrategy.NONE);
            option.skipMemoryCache(true);
            Glide.with(context).asGif().load(model).apply(option).listener(new RequestListener<GifDrawable>() {
                @Override
                public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<GifDrawable> target, boolean isFirstResource) {
                    return false;
                }
    
                @Override
                public boolean onResourceReady(GifDrawable resource, Object model, Target<GifDrawable> target, DataSource dataSource, boolean isFirstResource) {
                    try {
                        Field gifStateField = GifDrawable.class.getDeclaredField("state");
                        gifStateField.setAccessible(true);
                        Class gifStateClass = Class.forName("com.bumptech.glide.load.resource.gif.GifDrawable$GifState");
                        Field gifFrameLoaderField = gifStateClass.getDeclaredField("frameLoader");
                        gifFrameLoaderField.setAccessible(true);
                        Class gifFrameLoaderClass = Class.forName("com.bumptech.glide.load.resource.gif.GifFrameLoader");
                        Field gifDecoderField = gifFrameLoaderClass.getDeclaredField("gifDecoder");
                        gifDecoderField.setAccessible(true);
                        Class gifDecoderClass = Class.forName("com.bumptech.glide.gifdecoder.GifDecoder");
                        Object gifDecoder = gifDecoderField.get(gifFrameLoaderField.get(gifStateField.get(resource)));
                        Method getDelayMethod = gifDecoderClass.getDeclaredMethod("getDelay", int.class);
                        getDelayMethod.setAccessible(true);
                        //设置只播放一次
                        resource.setLoopCount(1);
                        //获得总帧数
                        int count = resource.getFrameCount();
                        int delay = 0;
                        for (int i = 0; i < count; i++) {
                            //计算每一帧所需要的时间进行累加
                            delay += (int) getDelayMethod.invoke(gifDecoder, i);
                        }
                        imageView.postDelayed(() -> {
                            if (gifListener != null) {
                                gifListener.gifPlayComplete();
                            }
                        }, delay);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return false;
                }
            }).into(imageView);
        }
    
        /**
         * Gif播放完毕回调
         */
        public interface GifListener {
            void gifPlayComplete();
        }
    }
    
    

    使用起来也很简单。

      tv_like.setOnClickListener(v -> {
                boolean newLikeState = !data.get(i).isLiked();
                tv_like.setSelected(newLikeState);
                data.get(i).setLiked(newLikeState);
                if (newLikeState) {
                    LikeUtil.showLike( tv_like, 0, 0);
                }
            });
    

    源码

    有兴趣的可以下载看一下
    使用Glide4.0 实现点赞动画的demo

    相关文章

      本文标题:使用Glide V4 实现GIF点赞动画

      本文链接:https://www.haomeiwen.com/subject/odkyeftx.html