温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Android怎么实现点赞动画效果

发布时间:2022-02-07 09:52:59 来源:亿速云 阅读:228 作者:iii 栏目:开发技术

今天小编给大家分享一下Android怎么实现点赞动画效果的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

    一、前言

    对接下来功能实现的探索。

    二、需求拆分

    仔细观察点赞交互,看出大概以下几个步骤:

    1:点赞控件需要自定义,对其触摸事件进行处理。

    2:点赞动画的实现。

    3:要有一个存放动画的容器。

    三、实现方案

    1、点赞控件触摸事件处理

    点赞控件是区分长按和点击处理的,另外我们发现在手指按下以后包括手指的移动直到手指的抬起都在执行动画。因为点赞的点击区域可能包括点赞次数,所以这里就自定义了点赞控件,并处理onTouchEvent(event: MotionEvent)事件,区分长按和单击是使用了点击到手指抬起的间隔时间区分的,伪代码如下:

    override fun onTouchEvent(event: MotionEvent): Boolean {
        var onTouch: Boolean
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isRefreshing = false
                isDowning = true
                //点击
                lastDownTime = System.currentTimeMillis()
                postDelayed(autoPollTask, CLICK_INTERVAL_TIME)
                onTouch = true
            }
            MotionEvent.ACTION_UP -> {
                isDowning = false
                //抬起
                if (System.currentTimeMillis() - lastDownTime < CLICK_INTERVAL_TIME) {
                    //小于间隔时间按照单击处理
                    onFingerDowningListener?.onDown(this)
                } else {
                    //大于等于间隔时间按照长按抬起手指处理
                    onFingerDowningListener?.onUp()
                }
                removeCallbacks(autoPollTask)
                onTouch = true
            }
            MotionEvent.ACTION_CANCEL ->{
                isDowning = false
                removeCallbacks(autoPollTask)
                onTouch = false
            }
            else -> onTouch = false
        }
        return onTouch
    }

    长按时使用Runnable的postDelayed(Runnable action, long delayMillis)方法来进行不断的执行动画,伪代码:

    private inner class AutoPollTask : Runnable {
        override fun run() {
            onFingerDowningListener?.onLongPress(this@LikeView)
            if(!canLongPress){
                removeCallbacks(autoPollTask)
            }else{
                postDelayed(autoPollTask, CLICK_INTERVAL_TIME)
            }
        }
    
    }

    2、点赞动画的实现

    点赞效果元素分为:点赞表情图标、点赞次数数字以及点赞文案

    2.1、点赞效果图片的获取和存储管理

    这里参考了SuperLike的做法,对图片进行了缓存处理,代码如下:

    object BitmapProviderFactory {
        fun getProvider(context: Context): BitmapProvider.Provider {
            return BitmapProvider.Builder(context)
                .setDrawableArray(
                    intArrayOf(
                            R.mipmap.emoji_1, R.mipmap.emoji_2, R.mipmap.emoji_3,
                            R.mipmap.emoji_4, R.mipmap.emoji_5, R.mipmap.emoji_6,
                            R.mipmap.emoji_7, R.mipmap.emoji_8, R.mipmap.emoji_9, R.mipmap.emoji_10,
                            R.mipmap.emoji_11, R.mipmap.emoji_12, R.mipmap.emoji_13,
                            R.mipmap.emoji_14
                    )
                )
                .setNumberDrawableArray(
                    intArrayOf(
                            R.mipmap.multi_digg_num_0, R.mipmap.multi_digg_num_1,
                            R.mipmap.multi_digg_num_2, R.mipmap.multi_digg_num_3,
                            R.mipmap.multi_digg_num_4, R.mipmap.multi_digg_num_5,
                            R.mipmap.multi_digg_num_6, R.mipmap.multi_digg_num_7,
                            R.mipmap.multi_digg_num_8, R.mipmap.multi_digg_num_9
                    )
                )
                .setLevelDrawableArray(
                    intArrayOf(
                            R.mipmap.multi_digg_word_level_1, R.mipmap.multi_digg_word_level_2,
                            R.mipmap.multi_digg_word_level_3
                    )
                )
                .build()
        }
    }
    object BitmapProvider {
        class Default(
            private val context: Context,
            cacheSize: Int,
            @DrawableRes private val drawableArray: IntArray,
            @DrawableRes private val numberDrawableArray: IntArray?,
            @DrawableRes private val levelDrawableArray: IntArray?,
            private val levelStringArray: Array<String>?,
            private val textSize: Float
        ) : Provider {
            private val bitmapLruCache: LruCache<Int, Bitmap> = LruCache(cacheSize)
            private val NUMBER_PREFIX = 0x70000000
            private val LEVEL_PREFIX = -0x80000000
    
            /**
             * 获取数字图片
             * @param number
             * @return
             */
            override fun getNumberBitmap(number: Int): Bitmap? {
                var bitmap: Bitmap?
                if (numberDrawableArray != null && numberDrawableArray.isNotEmpty()) {
                    val index = number % numberDrawableArray.size
                    bitmap = bitmapLruCache[NUMBER_PREFIX or numberDrawableArray[index]]
                    if (bitmap == null) {
                        bitmap =
                            BitmapFactory.decodeResource(context.resources, numberDrawableArray[index])
                        bitmapLruCache.put(NUMBER_PREFIX or numberDrawableArray[index], bitmap)
                    }
                } else {
                    bitmap = bitmapLruCache[NUMBER_PREFIX or number]
                    if (bitmap == null) {
                        bitmap = createBitmapByText(textSize, number.toString())
                        bitmapLruCache.put(NUMBER_PREFIX or number, bitmap)
                    }
                }
                return bitmap
            }
    
            /**
             * 获取等级文案图片
             * @param level
             * @return
             */
            override fun getLevelBitmap(level: Int): Bitmap? {
                var bitmap: Bitmap?
                if (levelDrawableArray != null && levelDrawableArray.isNotEmpty()) {
                    val index = level.coerceAtMost(levelDrawableArray.size)
                    bitmap = bitmapLruCache[LEVEL_PREFIX or levelDrawableArray[index]]
                    if (bitmap == null) {
                        bitmap =
                            BitmapFactory.decodeResource(context.resources, levelDrawableArray[index])
                        bitmapLruCache.put(LEVEL_PREFIX or levelDrawableArray[index], bitmap)
                    }
                } else {
                    bitmap = bitmapLruCache[LEVEL_PREFIX or level]
                    if (bitmap == null && !levelStringArray.isNullOrEmpty()) {
                        val index = level.coerceAtMost(levelStringArray.size)
                        bitmap = createBitmapByText(textSize, levelStringArray[index])
                        bitmapLruCache.put(LEVEL_PREFIX or level, bitmap)
                    }
                }
                return bitmap
            }
    
            /**
             * 获取随机表情图片
             * @return
             */
            override val randomBitmap: Bitmap
                get() {
                    val index = (Math.random() * drawableArray.size).toInt()
                    var bitmap = bitmapLruCache[drawableArray[index]]
                    if (bitmap == null) {
                        bitmap = BitmapFactory.decodeResource(context.resources, drawableArray[index])
                        bitmapLruCache.put(drawableArray[index], bitmap)
                    }
                    return bitmap
                }
    
            private fun createBitmapByText(textSize: Float, text: String): Bitmap {
                val textPaint = TextPaint()
                textPaint.color = Color.BLACK
                textPaint.textSize = textSize
                val bitmap = Bitmap.createBitmap(
                    textPaint.measureText(text).toInt(),
                    textSize.toInt(), Bitmap.Config.ARGB_4444
                )
                val canvas = Canvas(bitmap)
                canvas.drawColor(Color.TRANSPARENT)
                canvas.drawText(text, 0f, textSize, textPaint)
                return bitmap
            }
    
        }
    
        class Builder(var context: Context) {
            private var cacheSize = 0
    
            @DrawableRes
            private var drawableArray: IntArray? = null
    
            @DrawableRes
            private var numberDrawableArray: IntArray? = null
    
            @DrawableRes
            private var levelDrawableArray: IntArray? = null
            private var levelStringArray: Array<String>? = null
            private var textSize = 0f
    
            fun setCacheSize(cacheSize: Int): Builder {
                this.cacheSize = cacheSize
                return this
            }
    
            /**
             * 设置表情图片
             * @param drawableArray
             * @return
             */
            fun setDrawableArray(@DrawableRes drawableArray: IntArray?): Builder {
                this.drawableArray = drawableArray
                return this
            }
    
            /**
             * 设置数字图片
             * @param numberDrawableArray
             * @return
             */
            fun setNumberDrawableArray(@DrawableRes numberDrawableArray: IntArray): Builder {
                this.numberDrawableArray = numberDrawableArray
                return this
            }
    
            /**
             * 设置等级文案图片
             * @param levelDrawableArray
             * @return
             */
            fun setLevelDrawableArray(@DrawableRes levelDrawableArray: IntArray?): Builder {
                this.levelDrawableArray = levelDrawableArray
                return this
            }
    
            fun setLevelStringArray(levelStringArray: Array<String>?): Builder {
                this.levelStringArray = levelStringArray
                return this
            }
    
            fun setTextSize(textSize: Float): Builder {
                this.textSize = textSize
                return this
            }
    
            fun build(): Provider {
                if (cacheSize == 0) {
                    cacheSize = 32
                }
                if (drawableArray == null || drawableArray?.isEmpty() == true) {
                    drawableArray = intArrayOf(R.mipmap.emoji_1)
                }
                if (levelDrawableArray == null && levelStringArray.isNullOrEmpty()) {
                    levelStringArray = arrayOf("次赞!", "太棒了!!", "超赞同!!!")
                }
                return Default(
                    context, cacheSize, drawableArray!!, numberDrawableArray,
                    levelDrawableArray, levelStringArray, textSize
                )
            }
        }
    
        interface Provider {
    
            /**
             * 获取随机表情图片
             */
            val randomBitmap: Bitmap
    
            /**
             * 获取数字图片
             * [number] 点击次数
             */
            fun getNumberBitmap(number: Int): Bitmap?
    
            /**
             * 获取等级文案图片
             * [level] 等级
             */
            fun getLevelBitmap(level: Int): Bitmap?
        }
    }
    2.2、点赞表情图标动画实现

    这里的实现参考了toutiaothumb,表情图标的动画大致分为:上升动画的同时执行图标大小变化动画和图标透明度变化,在上升动画完成时进行下降动画。代码如下:

    class EmojiAnimationView @JvmOverloads constructor(
        context: Context,
        private val provider: BitmapProvider.Provider?,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
        private var mThumbImage: Bitmap? = null
        private var mBitmapPaint: Paint? = null
        private var mAnimatorListener: AnimatorListener? = null
    
        /**
         * 表情图标的宽度
         */
        private var emojiWith = 0
    
        /**
         * 表情图标的高度
         */
        private var emojiHeight = 0
    
    
        private fun init() {
            //初始化图片,取出随机图标
            mThumbImage = provider?.randomBitmap
        }
    
        init {
            //初始化paint
            mBitmapPaint = Paint()
            mBitmapPaint?.isAntiAlias = true
        }
    
        /**
         * 设置动画
         */
        private fun showAnimation() {
            val imageWidth = mThumbImage?.width ?:0
            val imageHeight = mThumbImage?.height ?:0
            val topX = -1080 + (1400 * Math.random()).toFloat()
            val topY = -300 + (-700 * Math.random()).toFloat()
            //上升动画
            val translateAnimationX = ObjectAnimator.ofFloat(this, "translationX", 0f, topX)
            translateAnimationX.duration = DURATION.toLong()
            translateAnimationX.interpolator = LinearInterpolator()
            val translateAnimationY = ObjectAnimator.ofFloat(this, "translationY", 0f, topY)
            translateAnimationY.duration = DURATION.toLong()
            translateAnimationY.interpolator = DecelerateInterpolator()
            //表情图片的大小变化
            val translateAnimationRightLength = ObjectAnimator.ofInt(
                this, "emojiWith",
                0,imageWidth,imageWidth,imageWidth,imageWidth, imageWidth, imageWidth, imageWidth, imageWidth, imageWidth
            )
            translateAnimationRightLength.duration = DURATION.toLong()
            val translateAnimationBottomLength = ObjectAnimator.ofInt(
                this, "emojiHeight",
                0,imageHeight,imageHeight,imageHeight,imageHeight,imageHeight, imageHeight, imageHeight, imageHeight, imageHeight
            )
            translateAnimationBottomLength.duration = DURATION.toLong()
            translateAnimationRightLength.addUpdateListener {
                invalidate()
            }
            //透明度变化
            val alphaAnimation = ObjectAnimator.ofFloat(
                this,
                "alpha",
                0.8f,
                1.0f,
                1.0f,
                1.0f,
                0.9f,
                0.8f,
                0.8f,
                0.7f,
                0.6f,
                0f
            )
            alphaAnimation.duration = DURATION.toLong()
            //动画集合
            val animatorSet = AnimatorSet()
            animatorSet.play(translateAnimationX).with(translateAnimationY)
                .with(translateAnimationRightLength).with(translateAnimationBottomLength)
                .with(alphaAnimation)
    
            //下降动画
            val translateAnimationXDown =
                ObjectAnimator.ofFloat(this, "translationX", topX, topX * 1.2f)
            translateAnimationXDown.duration = (DURATION / 5).toLong()
            translateAnimationXDown.interpolator = LinearInterpolator()
            val translateAnimationYDown =
                ObjectAnimator.ofFloat(this, "translationY", topY, topY * 0.8f)
            translateAnimationYDown.duration = (DURATION / 5).toLong()
            translateAnimationYDown.interpolator = AccelerateInterpolator()
            //设置动画播放顺序
            val animatorSetDown = AnimatorSet()
            animatorSet.start()
            animatorSet.addListener(object : Animator.AnimatorListener {
                override fun onAnimationStart(animation: Animator) {}
                override fun onAnimationEnd(animation: Animator) {
                    animatorSetDown.play(translateAnimationXDown).with(translateAnimationYDown)
                    animatorSetDown.start()
                }
    
                override fun onAnimationCancel(animation: Animator) {}
                override fun onAnimationRepeat(animation: Animator) {}
            })
            animatorSetDown.addListener(object : Animator.AnimatorListener {
                override fun onAnimationStart(animation: Animator) {}
                override fun onAnimationEnd(animation: Animator) {
                    //动画完成后通知移除动画view
                    mAnimatorListener?.onAnimationEmojiEnd()
                }
    
                override fun onAnimationCancel(animation: Animator) {}
                override fun onAnimationRepeat(animation: Animator) {}
            })
        }
    
    
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            drawEmojiImage(canvas)
        }
    
        /**
         * 绘制表情图片
         */
        private fun drawEmojiImage(canvas: Canvas) {
            mThumbImage?.let{
                val dst = Rect()
                dst.left = 0
                dst.top = 0
                dst.right = emojiWith
                dst.bottom = emojiHeight
                canvas.drawBitmap(it, null, dst, mBitmapPaint)
            }
    
        }
    
        /**
         * 这些get\set方法用于表情图标的大小动画
         * 不能删除
         */
        fun getEmojiWith(): Int {
            return emojiWith
        }
    
        fun setEmojiWith(emojiWith: Int) {
            this.emojiWith = emojiWith
        }
    
        fun getEmojiHeight(): Int {
            return emojiHeight
        }
    
        fun setEmojiHeight(emojiHeight: Int) {
            this.emojiHeight = emojiHeight
        }
    
        fun setEmojiAnimation() {
            showAnimation()
        }
    
        fun setAnimatorListener(animatorListener: AnimatorListener?) {
            mAnimatorListener = animatorListener
        }
    
        interface AnimatorListener {
            /**
             *  动画结束
             */
            fun onAnimationEmojiEnd()
        }
    
    
        fun setEmoji() {
            init()
        }
    
        companion object {
            //动画时长
            const val DURATION = 500
        }
    }
    2.3、点赞次数和点赞文案的绘制

    这里的点赞次数处理了从1到999,并在不同的点赞次数区间显示不同的点赞文案。代码如下:

    class NumberLevelView @JvmOverloads constructor(
        context: Context,
        private val provider: BitmapProvider.Provider?,
        private val x: Int,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : View(context, attrs, defStyleAttr) {
        private var textPaint: Paint = Paint()
    
        /**
         * 点击次数
         */
        private var mNumber = 0
    
        /**
         * 等级文案图片
         */
        private var bitmapTalk: Bitmap? = null
    
        /**
         * 等级
         */
        private var level = 0
    
        /**
         * 数字图片宽度
         */
        private var numberImageWidth = 0
    
        /**
         * 数字图片的总宽度
         */
        private var offsetX = 0
    
        /**
         * x 初始位置
         */
        private var initialValue = 0
    
        /**
         * 默认数字和等级文案图片间距
         */
        private var spacing = 0
    
        init {
            textPaint.isAntiAlias = true
            initialValue = x - PublicMethod.dp2px(context, 120f)
            numberImageWidth = provider?.getNumberBitmap(1)?.width ?: 0
            spacing = PublicMethod.dp2px(context, 10f)
        }
        override fun onDraw(canvas: Canvas) {
            super.onDraw(canvas)
            val levelBitmap = provider?.getLevelBitmap(level) ?: return
            //等级图片的宽度
            val levelBitmapWidth = levelBitmap.width
    
            val dst = Rect()
            when (mNumber) {
                in 0..9 -> {
                    initialValue = x - levelBitmapWidth
                    dst.left =  initialValue
                    dst.right = initialValue + levelBitmapWidth
                }
                in 10..99 -> {
                    initialValue  = x - PublicMethod.dp2px(context, 100f)
                    dst.left =  initialValue + numberImageWidth + spacing
                    dst.right = initialValue+ numberImageWidth  + spacing+ levelBitmapWidth
                }
                else -> {
                    initialValue = x - PublicMethod.dp2px(context, 120f)
                    dst.left =  initialValue + 2*numberImageWidth + spacing
                    dst.right = initialValue+ 2*numberImageWidth + spacing + levelBitmapWidth
                }
            }
            dst.top = 0
            dst.bottom = levelBitmap.height
            //绘制等级文案图标
            canvas.drawBitmap(levelBitmap, null, dst, textPaint)
    
            while (mNumber > 0) {
                val number = mNumber % 10
                val bitmap = provider.getNumberBitmap(number)?:continue
                offsetX += bitmap.width
                //这里是数字
                val rect = Rect()
                rect.top = 0
                when {
                    mNumber/ 10 < 1 -> {
                        rect.left = initialValue - bitmap.width
                        rect.right = initialValue
                    }
                    mNumber/ 10 in 1..9 -> {
                        rect.left = initialValue
                        rect.right = initialValue + bitmap.width
                    }
                    else -> {
                        rect.left = initialValue +  bitmap.width
                        rect.right = initialValue +2* bitmap.width
                    }
                }
    
                rect.bottom = bitmap.height
                //绘制数字
                canvas.drawBitmap(bitmap, null, rect, textPaint)
                mNumber /= 10
            }
    
        }
    
        fun setNumber(number: Int) {
            this.mNumber = number
            if (mNumber >999){
                mNumber = 999
            }
            level = when (mNumber) {
                in 1..20 -> {
                    0
                }
                in 21..80 -> {
                    1
                }
                else -> {
                    2
                }
            }
            //根据等级取出等级文案图标
            bitmapTalk = provider?.getLevelBitmap(level)
            invalidate()
        }
    }

    3、存放点赞动画的容器

    我们需要自定义一个view来存放动画,以及提供开始动画以及回收动画view等工作。代码如下:

    class LikeAnimationLayout @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
    ) : FrameLayout(context, attrs, defStyleAttr) {
    
        private var lastClickTime: Long = 0
        private var currentNumber = 1
        private var mNumberLevelView: NumberLevelView? = null
    
        /**
         * 有无表情动画 暂时无用
         */
        private var hasEruptionAnimation = false
    
        /**
         * 有无等级文字 暂时无用
         */
        private var hasTextAnimation = false
    
        /**
         * 是否可以长按,暂时无用 目前用时间来管理
         */
        private var canLongPress = false
    
        /**
         * 最大和最小角度暂时无用
         */
        private var maxAngle = 0
        private var minAngle = 0
    
        private var pointX = 0
        private var pointY = 0
        var provider: BitmapProvider.Provider? = null
            get() {
                if (field == null) {
                    field = BitmapProvider.Builder(context)
                        .build()
                }
                return field
            }
    
    
        private fun init(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
            val typedArray = context.obtainStyledAttributes(
                attrs,
                    R.styleable.LikeAnimationLayout,
                defStyleAttr,
                0
            )
            maxAngle =
                typedArray.getInteger(R.styleable.LikeAnimationLayout_max_angle, MAX_ANGLE)
            minAngle =
                typedArray.getInteger(R.styleable.LikeAnimationLayout_min_angle, MIN_ANGLE)
            hasEruptionAnimation = typedArray.getBoolean(
                    R.styleable.LikeAnimationLayout_show_emoji,
                true
            )
            hasTextAnimation = typedArray.getBoolean(R.styleable.LikeAnimationLayout_show_text, true)
            typedArray.recycle()
    
        }
    
        /**
         * 点击表情动画view
         */
        private fun addEmojiView(
            context: Context?,
            x: Int,
            y: Int
        ) {
    
            for (i in 0 .. ERUPTION_ELEMENT_AMOUNT) {
                val layoutParams = RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
                layoutParams.setMargins(x, y, 0, 0)
                val articleThumb = context?.let {
                    EmojiAnimationView(
                        it, provider
                    )
                }
    
                articleThumb?.let {
                    it.setEmoji()
                    this.addView(it, -1, layoutParams)
                    it.setAnimatorListener(object : EmojiAnimationView.AnimatorListener {
                        override fun onAnimationEmojiEnd() {
                            removeView(it)
                            val handler = Handler()
                            handler.postDelayed({
                                if (mNumberLevelView != null && System.currentTimeMillis() - lastClickTime >= SPACING_TIME) {
                                    removeView(mNumberLevelView)
                                    mNumberLevelView = null
                                }
                            }, SPACING_TIME)
                        }
    
                    })
                    it.setEmojiAnimation()
    
                }
    
            }
        }
    
        /**
         * 开启动画
         */
        fun launch(x: Int, y: Int) {
            if (System.currentTimeMillis() - lastClickTime >= SPACING_TIME) {
                pointX = x
                pointY = y
                //单次点击
                addEmojiView(context, x, y-50)
                lastClickTime = System.currentTimeMillis()
                currentNumber = 1
                if (mNumberLevelView != null) {
                    removeView(mNumberLevelView)
                    mNumberLevelView = null
                }
            } else { //连续点击
                if (pointX != x || pointY != y){
                    return
                }
                lastClickTime = System.currentTimeMillis()
                Log.i(TAG, "当前动画化正在执行")
                addEmojiView(context, x, y)
                //添加数字连击view
                val layoutParams = RelativeLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
                )
               layoutParams.setMargins(0, y - PublicMethod.dp2px(context, 60f), 0, 0)
                if (mNumberLevelView == null) {
                    mNumberLevelView = NumberLevelView(context,provider,x)
                    addView(mNumberLevelView, layoutParams)
                }
                currentNumber++
                mNumberLevelView?.setNumber(currentNumber)
            }
        }
    
        companion object {
            private const val TAG = "LikeAnimationLayout"
    
            /**
             * 表情动画单次弹出个数,以后如果有不同需求可以改为配置
             */
            private const val ERUPTION_ELEMENT_AMOUNT = 8
            private const val MAX_ANGLE = 180
            private const val MIN_ANGLE = 70
            private const val SPACING_TIME = 400L
        }
    
        init {
            init(context, attrs, defStyleAttr)
        }
    }

    注意:动画完成之后一定要清除view。

    4、启动动画

    点赞控件的手势回调,伪代码如下:

    holder.likeView.setOnFingerDowningListener(object : OnFingerDowningListener {
        /**
         * 长按回调
         */
        override fun onLongPress(v: View) {
            if (!bean.hasLike) {
                //未点赞
                if (!fistLongPress) {
                    //这里同步点赞接口等数据交互
                    bean.likeNumber++
                    bean.hasLike = true
                    setLikeStatus(holder, bean)
                }
    
                //显示动画
                onLikeAnimationListener?.doLikeAnimation(v)
            } else {
                if (System.currentTimeMillis() - lastClickTime <= throttleTime && lastClickTime != 0L) {
                    //处理点击过后为点赞状态的情况
                    onLikeAnimationListener?.doLikeAnimation(v)
                    lastClickTime = System.currentTimeMillis()
                } else {
                    //处理长按为点赞状态后的情况
                    onLikeAnimationListener?.doLikeAnimation(v)
                }
            }
    
            fistLongPress = true
    
        }
    
        /**
         * 长按抬起手指回调处理
         */
        override fun onUp() {
            fistLongPress = false
        }
    
        /**
         * 单击事件回调
         */
        override fun onDown(v: View) {
            if (System.currentTimeMillis() - lastClickTime > throttleTime || lastClickTime == 0L) {
                if (!bean.hasLike) {
                    //未点赞情况下,点赞接口和数据交互处理
                    bean.hasLike = true
                    bean.likeNumber++
                    setLikeStatus(holder, bean)
                    throttleTime = 1000
                    onLikeAnimationListener?.doLikeAnimation(v)
                } else {
                    //点赞状态下,取消点赞接口和数据交互处理
                    bean.hasLike = false
                    bean.likeNumber--
                    setLikeStatus(holder, bean)
                    throttleTime = 30
                }
            } else if (lastClickTime != 0L && bean.hasLike) {
                //在时间范围内,连续点击点赞,显示动画
                onLikeAnimationListener?.doLikeAnimation(v)
            }
            lastClickTime = System.currentTimeMillis()
    
        }
    
    
    })

    在显示动画页面初始化工作时初始化动画资源:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_list)
        likeAnimationLayout?.provider = BitmapProviderFactory.getProvider(this)
    }

    在显示动画的回调中启动动画:

    override fun doLikeAnimation(v: View) {
        val itemPosition = IntArray(2)
        val superLikePosition = IntArray(2)
        v.getLocationOnScreen(itemPosition)
        likeAnimationLayout?.getLocationOnScreen(superLikePosition)
        val x = itemPosition[0] + v.width / 2
        val y = itemPosition[1] - superLikePosition[1] + v.height / 2
        likeAnimationLayout?.launch(x, y)
    }

    四、遇到的问题

    因为流列表中使用了SmartRefreshLayout下拉刷新控件,如果在列表前几条内容进行点赞动画当手指移动时触摸事件会被SmartRefreshLayout拦截去执行下拉刷新,那么手指抬起时点赞控件得不到响应会一直进行动画操作,目前想到的解决方案是点赞控件在手指按下时查看父布局有无SmartRefreshLayout,如果有通过反射先禁掉下拉刷新功能,手指抬起或者取消进行重置操作。代码如下:

    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        parent?.requestDisallowInterceptTouchEvent(true)
        return super.dispatchTouchEvent(event)
    }
    
    override fun onTouchEvent(event: MotionEvent): Boolean {
        var onTouch: Boolean
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isRefreshing = false
                isDowning = true
                //点击
                lastDownTime = System.currentTimeMillis()
                findSmartRefreshLayout(false)
                if (isRefreshing) {
                    //如果有下拉控件并且正在刷新直接不响应
                    return false
                }
                postDelayed(autoPollTask, CLICK_INTERVAL_TIME)
                onTouch = true
            }
            MotionEvent.ACTION_UP -> {
                isDowning = false
                //抬起
                if (System.currentTimeMillis() - lastDownTime < CLICK_INTERVAL_TIME) {
                    //小于间隔时间按照单击处理
                    onFingerDowningListener?.onDown(this)
                } else {
                    //大于等于间隔时间按照长按抬起手指处理
                    onFingerDowningListener?.onUp()
                }
                findSmartRefreshLayout(true)
                removeCallbacks(autoPollTask)
                onTouch = true
            }
            MotionEvent.ACTION_CANCEL ->{
                isDowning = false
                findSmartRefreshLayout(true)
                removeCallbacks(autoPollTask)
                onTouch = false
            }
            else -> onTouch = false
        }
        return onTouch
    }
    
    /**
     * 如果父布局有SmartRefreshLayout 控件,设置控件是否可用
     */
    private fun findSmartRefreshLayout(enable: Boolean) {
        var parent = parent
        while (parent != null && parent !is ContentFrameLayout) {
            if (parent is SmartRefreshLayout) {
                isRefreshing = parent.state == RefreshState.Refreshing
                if (isRefreshing){
                    //如果有下拉控件并且正在刷新直接结束
                    break
                }
                if (!enable && firstClick){
                    try {
                        firstClick = false
                        val field: Field = parent.javaClass.getDeclaredField("mEnableRefresh")
                        field.isAccessible = true
                        //通过反射获取是否可以先下拉刷新的初始值
                        enableRefresh = field.getBoolean(parent)
                    }catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
                if (enableRefresh){
                    //如果初始值不可以下拉刷新不要设置下拉刷新状态
                    parent.setEnableRefresh(enable)
                }
                parent.setEnableLoadMore(enable)
                break
            } else {
                parent = parent.parent
            }
        }
    }

    五、实现效果

    Android怎么实现点赞动画效果

    以上就是“Android怎么实现点赞动画效果”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI