仿抖音-视频及直播点赞效果

admin2024-04-26 12:16:0942资源视频直播坐标效果仓库

先上GIF

需求梳理

抖音APP中,视频的点赞和直播中点赞效果是不同的,先找寻两者的共同点提取接口:

类型图片动画初始旋转角度Y轴偏移量

视频点赞

单张红心

组合动画

随机旋转角度

-50

直播点赞

多张图片随机

组合动画

-20

Y轴偏移量是多次调试的,看各位同学需求,都可以调整。

interface ILikeFollowData {
    /**
     * 获取图标列表
     */
    fun getIconList(): List
    /**
     * 获取图标大小
     */
    fun getIconSize(): Int
    /**
     * 获取旋转角度范围
     */
    fun getRotationRange(): IntRange
    /**
     * 获取Y轴偏移量
     */
    fun getYOffset(): Int
    /**
     * 获取动画集合
     */
    fun getAnimatorSet(view: View): AnimatorSet
}

实现视频点赞数据类

class VideoLikeFollowData : ILikeFollowData {
    private val iconList = arrayListOf(R.drawable.img_like_follow)
    override fun getIconList() = iconList
    override fun getIconSize() = 240
    override fun getRotationRange() = -30..30
    override fun getYOffset() = -50
    override fun getAnimatorSet(view: View): AnimatorSet {
        val set = AnimatorSet()
        //放大
        set.playSequentially(
            AnimatorSet().apply {
                playTogether(
                    ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.4f).apply {
                        duration = 50
                    },
                    ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.4f).apply {
                        duration = 50
                    },
                )
            },
            //缩小
            AnimatorSet().apply {
                playTogether(
                    ObjectAnimator.ofFloat(view, "scaleX", 1.4f, 1f).apply {
                        duration = 200
                    },
                    ObjectAnimator.ofFloat(view, "scaleY", 1.4f, 1f).apply {
                        duration = 200
                    },
                )
            },
            ValueAnimator.ofInt(0, 100).apply {
                duration = 200
            },
            //缩放/透明并位移
            AnimatorSet().apply {
                playTogether(
                    ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.8f).apply {
                        duration = 500
                    },
                    ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.8f).apply {
                        duration = 500
                    },
                    ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
                        duration = 500
                    },
                    ObjectAnimator.ofFloat(
                        view,
                        "translationY",
                        view.y,
                        view.y - 300f
                    ).apply {
                        duration = 500
                    },
                )
            })
        return set
    }
}

直播点赞数据类

class LiveLikeFollowData : ILikeFollowData {
    private val iconList = arrayListOf(
        R.drawable.img_like_follow_1,
        R.drawable.img_like_follow_2,
        R.drawable.img_like_follow_3,
        R.drawable.img_like_follow_4,
        R.drawable.img_like_follow_5,
        R.drawable.img_like_follow_6,
        R.drawable.img_like_follow_7,
    )
    override fun getIconList() = iconList
    override fun getIconSize() = 100
    override fun getRotationRange() = 0..0
    override fun getYOffset() = -20
    override fun getAnimatorSet(view: View): AnimatorSet {
        val set = AnimatorSet()
        //放大
        set.playSequentially(
            AnimatorSet().apply {
                // 同时播放
                playTogether(
                    ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.1f).apply {
                        duration = 50
                    },
                    ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.1f).apply {
                        duration = 50
                    },
                    ObjectAnimator.ofFloat(view, "rotation", -20f, -6f).apply {
                        duration = 50
                    }
                )
            },
            //缩小
            AnimatorSet().apply {
                playTogether(
                    ObjectAnimator.ofFloat(view, "scaleX", 1.1f, 1f).apply {
                        duration = 200
                    },
                    ObjectAnimator.ofFloat(view, "scaleY", 1.1f, 1f).apply {
                        duration = 200
                    },
                    ObjectAnimator.ofFloat(view, "rotation", -6f, 4f, 0f).apply {
                        duration = 200
                    }
                )
            },
            ValueAnimator.ofInt(0, 100).apply {
                duration = 200
            },
            //缩放/透明并位移
            AnimatorSet().apply {
                playTogether(
                    ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.8f).apply {
                        duration = 500
                    },
                    ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.8f).apply {
                        duration = 500
                    },
                    ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply {
                        duration = 500
                    }
                )
            })
        return set
    }
}

上述代码中的图片,各位同学可以让UI自行设计切图,如果需要,可以在末尾代码链接处下载获取。

功能model

object LikeFollowModel {
    //短视频展示数据
    val videoLikeFollowData: ILikeFollowData by lazy {
        VideoLikeFollowData()
    }
    //直播展示数据
    val liveLikeFollowData: ILikeFollowData by lazy {
        LiveLikeFollowData()
    }
    
    /**
     * 展示点赞效果
     * @param x: 点击的x坐标
     * @param y: 点击的y坐标
     * @param viewGroup: 父布局
     * @param data: 展示数据
     */
    fun show(x: Int, y: Int, viewGroup: ViewGroup, data: ILikeFollowData = videoLikeFollowData) {
        //添加爱心
        val likeView = AppCompatImageView(viewGroup.context)
        //加载图片,封装的Glide方法,同学可自行处理
        loadImage(likeView, data.getIconList().random())
        val size = data.getIconSize()
        val layoutParams = ViewGroup.LayoutParams(size, size)
        likeView.layoutParams = layoutParams
        //设置爱心位置
        likeView.x = (x - (size / 2)).toFloat()
        likeView.y = (y - size + data.getYOffset()).toFloat()
        //设置随机旋转角度
        likeView.rotation = (data.getRotationRange().random()).toFloat()
        viewGroup.addView(likeView)
         //获取动画集合
        val animatorSet = data.getAnimatorSet(likeView)
        //添加动画结束监听
        animatorSet.addListener(onEnd = {
            tryCatch({
                //清理动画
                likeView.clearAnimation()
                //隐藏图片
                likeView.visibility = View.GONE
                //移除图片,延迟500毫秒移除,防止动画还没结束就移除
                HandlerUtils.postRunnable({
                    viewGroup.removeView(likeView)
                }, 500)
            })
        })
        animatorSet.start()
    }
}

以上四个类就是所有的功能代码。

实现思路还是蛮简单的:

6c36c981bc8d4fe8b1ea884dff21fce0.png

获取点击事件坐标

避免同学获取点击坐标有疑问,这里再补充上相关代码:

    /**
     * 设置点击监听
     * 返回坐标
     */
    @SuppressLint("ClickableViewAccessibility")
    private fun setViewOnClickListener(view: View, clickCallBack: (x: Int, y: Int) -> Unit) {
        var x = 0f
        var y = 0f
        view.setOnTouchListener { _, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    x = event.x
                    y = event.y
                }
                MotionEvent.ACTION_UP -> {
                    if (x == event.x && y == event.y) {
                        clickCallBack(event.x.toInt(), event.y.toInt())
                    }
                }
            }
            true
        }
    }

代码仓库

仓库地址