欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android實(shí)現(xiàn)仿今日頭條點(diǎn)贊動畫效果實(shí)例

 更新時(shí)間:2022年02月06日 12:08:01   作者:笑慢  
我想看到今日頭條的點(diǎn)贊效果,應(yīng)該都覺得很絢麗吧,下面這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)仿今日頭條點(diǎn)贊動畫效果的相關(guān)資料,文中通過示例代價(jià)介紹的非常詳細(xì),需要的朋友可以參考下

一、前言

我們在今日頭條APP上會看到點(diǎn)贊動畫效果,感覺非常不錯,正好公司有點(diǎn)贊動畫的需求,所以有了接下來的對此功能的實(shí)現(xiàn)的探索。

二、需求拆分

仔細(xì)觀察點(diǎn)贊交互,看出大概以下幾個(gè)步驟:

1:點(diǎn)贊控件需要自定義,對其觸摸事件進(jìn)行處理。

2:點(diǎn)贊動畫的實(shí)現(xiàn)。

3:要有一個(gè)存放動畫的容器。

三、實(shí)現(xiàn)方案

1、點(diǎn)贊控件觸摸事件處理

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

override fun onTouchEvent(event: MotionEvent): Boolean {
    var onTouch: Boolean
    when (event.action) {
        MotionEvent.ACTION_DOWN -> {
            isRefreshing = false
            isDowning = true
            //點(diǎn)擊
            lastDownTime = System.currentTimeMillis()
            postDelayed(autoPollTask, CLICK_INTERVAL_TIME)
            onTouch = true
        }
        MotionEvent.ACTION_UP -> {
            isDowning = false
            //抬起
            if (System.currentTimeMillis() - lastDownTime < CLICK_INTERVAL_TIME) {
                //小于間隔時(shí)間按照單擊處理
                onFingerDowningListener?.onDown(this)
            } else {
                //大于等于間隔時(shí)間按照長按抬起手指處理
                onFingerDowningListener?.onUp()
            }
            removeCallbacks(autoPollTask)
            onTouch = true
        }
        MotionEvent.ACTION_CANCEL ->{
            isDowning = false
            removeCallbacks(autoPollTask)
            onTouch = false
        }
        else -> onTouch = false
    }
    return onTouch
}

長按時(shí)使用Runnable的postDelayed(Runnable action, long delayMillis)方法來進(jìn)行不斷的執(zhí)行動畫,偽代碼:

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

}

2、點(diǎn)贊動畫的實(shí)現(xiàn)

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

2.1、點(diǎn)贊效果圖片的獲取和存儲管理

這里參考了SuperLike的做法,對圖片進(jìn)行了緩存處理,代碼如下:

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
        }

        /**
         * 獲取隨機(jī)表情圖片
         * @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 {

        /**
         * 獲取隨機(jī)表情圖片
         */
        val randomBitmap: Bitmap

        /**
         * 獲取數(shù)字圖片
         * [number] 點(diǎn)擊次數(shù)
         */
        fun getNumberBitmap(number: Int): Bitmap?

        /**
         * 獲取等級文案圖片
         * [level] 等級
         */
        fun getLevelBitmap(level: Int): Bitmap?
    }
}

2.2、點(diǎn)贊表情圖標(biāo)動畫實(shí)現(xiàn)

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

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() {
        //初始化圖片,取出隨機(jī)圖標(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 {
        //動畫時(shí)長
        const val DURATION = 500
    }
}

2.3、點(diǎn)贊次數(shù)和點(diǎn)贊文案的繪制

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

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()

    /**
     * 點(diǎn)擊次數(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

    /**
     * 默認(rèn)數(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、存放點(diǎn)贊動畫的容器

我們需要自定義一個(gè)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

    /**
     * 有無表情動畫 暫時(shí)無用
     */
    private var hasEruptionAnimation = false

    /**
     * 有無等級文字 暫時(shí)無用
     */
    private var hasTextAnimation = false

    /**
     * 是否可以長按,暫時(shí)無用 目前用時(shí)間來管理
     */
    private var canLongPress = false

    /**
     * 最大和最小角度暫時(shí)無用
     */
    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()

    }

    /**
     * 點(diǎn)擊表情動畫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
            //單次點(diǎn)擊
            addEmojiView(context, x, y-50)
            lastClickTime = System.currentTimeMillis()
            currentNumber = 1
            if (mNumberLevelView != null) {
                removeView(mNumberLevelView)
                mNumberLevelView = null
            }
        } else { //連續(xù)點(diǎn)擊
            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"

        /**
         * 表情動畫單次彈出個(gè)數(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ǎn)贊控件的手勢回調(diào),偽代碼如下:

holder.likeView.setOnFingerDowningListener(object : OnFingerDowningListener {
    /**
     * 長按回調(diào)
     */
    override fun onLongPress(v: View) {
        if (!bean.hasLike) {
            //未點(diǎn)贊
            if (!fistLongPress) {
                //這里同步點(diǎn)贊接口等數(shù)據(jù)交互
                bean.likeNumber++
                bean.hasLike = true
                setLikeStatus(holder, bean)
            }

            //顯示動畫
            onLikeAnimationListener?.doLikeAnimation(v)
        } else {
            if (System.currentTimeMillis() - lastClickTime <= throttleTime && lastClickTime != 0L) {
                //處理點(diǎn)擊過后為點(diǎn)贊狀態(tài)的情況
                onLikeAnimationListener?.doLikeAnimation(v)
                lastClickTime = System.currentTimeMillis()
            } else {
                //處理長按為點(diǎn)贊狀態(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) {
                //未點(diǎn)贊情況下,點(diǎn)贊接口和數(shù)據(jù)交互處理
                bean.hasLike = true
                bean.likeNumber++
                setLikeStatus(holder, bean)
                throttleTime = 1000
                onLikeAnimationListener?.doLikeAnimation(v)
            } else {
                //點(diǎn)贊狀態(tài)下,取消點(diǎn)贊接口和數(shù)據(jù)交互處理
                bean.hasLike = false
                bean.likeNumber--
                setLikeStatus(holder, bean)
                throttleTime = 30
            }
        } else if (lastClickTime != 0L && bean.hasLike) {
            //在時(shí)間范圍內(nèi),連續(xù)點(diǎn)擊點(diǎn)贊,顯示動畫
            onLikeAnimationListener?.doLikeAnimation(v)
        }
        lastClickTime = System.currentTimeMillis()

    }


})

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

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)
}

四、遇到的問題

因?yàn)榱髁斜碇惺褂昧薙martRefreshLayout下拉刷新控件,如果在列表前幾條內(nèi)容進(jìn)行點(diǎn)贊動畫當(dāng)手指移動時(shí)觸摸事件會被SmartRefreshLayout攔截去執(zhí)行下拉刷新,那么手指抬起時(shí)點(diǎn)贊控件得不到響應(yīng)會一直進(jìn)行動畫操作,目前想到的解決方案是點(diǎn)贊控件在手指按下時(shí)查看父布局有無SmartRefreshLayout,如果有通過反射先禁掉下拉刷新功能,手指抬起或者取消進(jìn)行重置操作。代碼如下:

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
            //點(diǎn)擊
            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) {
                //小于間隔時(shí)間按照單擊處理
                onFingerDowningListener?.onDown(this)
            } else {
                //大于等于間隔時(shí)間按照長按抬起手指處理
                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
        }
    }
}

五、實(shí)現(xiàn)效果

六、完整代碼獲取

點(diǎn)擊獲取源碼

七、參考和感謝

再次感謝

1、SuperLike

2、toutiaothumb

總結(jié)

到此這篇關(guān)于Android實(shí)現(xiàn)仿今日頭條點(diǎn)贊動畫效果的文章就介紹到這了,更多相關(guān)Android今日頭條點(diǎn)贊動畫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論