溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android怎么實現(xiàn)點贊動畫效果

發(fā)布時間:2022-02-07 09:52:59 來源:億速云 閱讀:289 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下Android怎么實現(xiàn)點贊動畫效果的相關(guān)知識點,內(nèi)容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    一、前言

    對接下來功能實現(xiàn)的探索。

    二、需求拆分

    仔細觀察點贊交互,看出大概以下幾個步驟:

    1:點贊控件需要自定義,對其觸摸事件進行處理。

    2:點贊動畫的實現(xiàn)。

    3:要有一個存放動畫的容器。

    三、實現(xiàn)方案

    1、點贊控件觸摸事件處理

    點贊控件是區(qū)分長按和點擊處理的,另外我們發(fā)現(xiàn)在手指按下以后包括手指的移動直到手指的抬起都在執(zhí)行動畫。因為點贊的點擊區(qū)域可能包括點贊次數(shù),所以這里就自定義了點贊控件,并處理onTouchEvent(event: MotionEvent)事件,區(qū)分長按和單擊是使用了點擊到手指抬起的間隔時間區(qū)分的,偽代碼如下:

    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)方法來進行不斷的執(zhí)行動畫,偽代碼:

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

    2、點贊動畫的實現(xiàn)

    點贊效果元素分為:點贊表情圖標(biāo)、點贊次數(shù)數(shù)字以及點贊文案

    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
    
            /**
             * 獲取數(shù)字圖片
             * @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
            }
    
            /**
             * 設(shè)置表情圖片
             * @param drawableArray
             * @return
             */
            fun setDrawableArray(@DrawableRes drawableArray: IntArray?): Builder {
                this.drawableArray = drawableArray
                return this
            }
    
            /**
             * 設(shè)置數(shù)字圖片
             * @param numberDrawableArray
             * @return
             */
            fun setNumberDrawableArray(@DrawableRes numberDrawableArray: IntArray): Builder {
                this.numberDrawableArray = numberDrawableArray
                return this
            }
    
            /**
             * 設(shè)置等級文案圖片
             * @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
    
            /**
             * 獲取數(shù)字圖片
             * [number] 點擊次數(shù)
             */
            fun getNumberBitmap(number: Int): Bitmap?
    
            /**
             * 獲取等級文案圖片
             * [level] 等級
             */
            fun getLevelBitmap(level: Int): Bitmap?
        }
    }
    2.2、點贊表情圖標(biāo)動畫實現(xiàn)

    這里的實現(xiàn)參考了toutiaothumb,表情圖標(biāo)的動畫大致分為:上升動畫的同時執(zhí)行圖標(biāo)大小變化動畫和圖標(biāo)透明度變化,在上升動畫完成時進行下降動畫。代碼如下:

    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
    
        /**
         * 表情圖標(biāo)的寬度
         */
        private var emojiWith = 0
    
        /**
         * 表情圖標(biāo)的高度
         */
        private var emojiHeight = 0
    
    
        private fun init() {
            //初始化圖片,取出隨機圖標(biāo)
            mThumbImage = provider?.randomBitmap
        }
    
        init {
            //初始化paint
            mBitmapPaint = Paint()
            mBitmapPaint?.isAntiAlias = true
        }
    
        /**
         * 設(shè)置動畫
         */
        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()
            //設(shè)置動畫播放順序
            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方法用于表情圖標(biāo)的大小動畫
         * 不能刪除
         */
        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 {
            /**
             *  動畫結(jié)束
             */
            fun onAnimationEmojiEnd()
        }
    
    
        fun setEmoji() {
            init()
        }
    
        companion object {
            //動畫時長
            const val DURATION = 500
        }
    }
    2.3、點贊次數(shù)和點贊文案的繪制

    這里的點贊次數(shù)處理了從1到999,并在不同的點贊次數(shù)區(qū)間顯示不同的點贊文案。代碼如下:

    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()
    
        /**
         * 點擊次數(shù)
         */
        private var mNumber = 0
    
        /**
         * 等級文案圖片
         */
        private var bitmapTalk: Bitmap? = null
    
        /**
         * 等級
         */
        private var level = 0
    
        /**
         * 數(shù)字圖片寬度
         */
        private var numberImageWidth = 0
    
        /**
         * 數(shù)字圖片的總寬度
         */
        private var offsetX = 0
    
        /**
         * x 初始位置
         */
        private var initialValue = 0
    
        /**
         * 默認數(shù)字和等級文案圖片間距
         */
        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
            //繪制等級文案圖標(biāo)
            canvas.drawBitmap(levelBitmap, null, dst, textPaint)
    
            while (mNumber > 0) {
                val number = mNumber % 10
                val bitmap = provider.getNumberBitmap(number)?:continue
                offsetX += bitmap.width
                //這里是數(shù)字
                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
                //繪制數(shù)字
                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
                }
            }
            //根據(jù)等級取出等級文案圖標(biāo)
            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 { //連續(xù)點擊
                if (pointX != x || pointY != y){
                    return
                }
                lastClickTime = System.currentTimeMillis()
                Log.i(TAG, "當(dāng)前動畫化正在執(zhí)行")
                addEmojiView(context, x, y)
                //添加數(shù)字連擊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"
    
            /**
             * 表情動畫單次彈出個數(shù),以后如果有不同需求可以改為配置
             */
            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、啟動動畫

    點贊控件的手勢回調(diào),偽代碼如下:

    holder.likeView.setOnFingerDowningListener(object : OnFingerDowningListener {
        /**
         * 長按回調(diào)
         */
        override fun onLongPress(v: View) {
            if (!bean.hasLike) {
                //未點贊
                if (!fistLongPress) {
                    //這里同步點贊接口等數(shù)據(jù)交互
                    bean.likeNumber++
                    bean.hasLike = true
                    setLikeStatus(holder, bean)
                }
    
                //顯示動畫
                onLikeAnimationListener?.doLikeAnimation(v)
            } else {
                if (System.currentTimeMillis() - lastClickTime <= throttleTime && lastClickTime != 0L) {
                    //處理點擊過后為點贊狀態(tài)的情況
                    onLikeAnimationListener?.doLikeAnimation(v)
                    lastClickTime = System.currentTimeMillis()
                } else {
                    //處理長按為點贊狀態(tài)后的情況
                    onLikeAnimationListener?.doLikeAnimation(v)
                }
            }
    
            fistLongPress = true
    
        }
    
        /**
         * 長按抬起手指回調(diào)處理
         */
        override fun onUp() {
            fistLongPress = false
        }
    
        /**
         * 單擊事件回調(diào)
         */
        override fun onDown(v: View) {
            if (System.currentTimeMillis() - lastClickTime > throttleTime || lastClickTime == 0L) {
                if (!bean.hasLike) {
                    //未點贊情況下,點贊接口和數(shù)據(jù)交互處理
                    bean.hasLike = true
                    bean.likeNumber++
                    setLikeStatus(holder, bean)
                    throttleTime = 1000
                    onLikeAnimationListener?.doLikeAnimation(v)
                } else {
                    //點贊狀態(tài)下,取消點贊接口和數(shù)據(jù)交互處理
                    bean.hasLike = false
                    bean.likeNumber--
                    setLikeStatus(holder, bean)
                    throttleTime = 30
                }
            } else if (lastClickTime != 0L && bean.hasLike) {
                //在時間范圍內(nèi),連續(xù)點擊點贊,顯示動畫
                onLikeAnimationListener?.doLikeAnimation(v)
            }
            lastClickTime = System.currentTimeMillis()
    
        }
    
    
    })

    在顯示動畫頁面初始化工作時初始化動畫資源:

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

    在顯示動畫的回調(diào)中啟動動畫:

    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下拉刷新控件,如果在列表前幾條內(nèi)容進行點贊動畫當(dāng)手指移動時觸摸事件會被SmartRefreshLayout攔截去執(zhí)行下拉刷新,那么手指抬起時點贊控件得不到響應(yīng)會一直進行動畫操作,目前想到的解決方案是點贊控件在手指按下時查看父布局有無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) {
                    //如果有下拉控件并且正在刷新直接不響應(yīng)
                    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 控件,設(shè)置控件是否可用
     */
    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){
                    //如果有下拉控件并且正在刷新直接結(jié)束
                    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){
                    //如果初始值不可以下拉刷新不要設(shè)置下拉刷新狀態(tài)
                    parent.setEnableRefresh(enable)
                }
                parent.setEnableLoadMore(enable)
                break
            } else {
                parent = parent.parent
            }
        }
    }

    五、實現(xiàn)效果

    Android怎么實現(xiàn)點贊動畫效果

    以上就是“Android怎么實現(xiàn)點贊動畫效果”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI