当前位置:首页 > 谈天说地

Android实现仿今日头条点赞动画效果实例

34资源网2022-02-07539
目录
  • 一、前言
  • 二、需求拆分
  • 三、实现方案
    • 1、点赞控件触摸事件处理
    • 2、点赞动画的实现
      • 2.1、点赞效果图片的获取和存储管理
      • 2.2、点赞表情图标动画实现
      • 2.3、点赞次数和点赞文案的绘制
    • 3、存放点赞动画的容器
      • 4、启动动画
      • 四、遇到的问题
        • 五、实现效果
          • 六、完整代码获取
            • 七、参考和感谢
              • 总结

                一、前言

                我们在今日头条app上会看到点赞动画效果,感觉非常不错,正好公司有点赞动画的需求,所以有了接下来的对此功能的实现的探索。

                二、需求拆分

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

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

                五、实现效果

                六、完整代码获取

                点击获取源码

                七、参考和感谢

                再次感谢

                1、superlike

                2、toutiaothumb

                总结

                到此这篇关于android实现仿今日头条点赞动画效果的文章就介绍到这了,更多相关android今日头条点赞动画内容请搜索萬仟网以前的文章或继续浏览下面的相关文章希望大家以后多多支持萬仟网!

                看完文章,还可以扫描下面的二维码下载快手极速版领4元红包

                快手极速版二维码

                快手极速版新人见面礼

                除了扫码领红包之外,大家还可以在快手极速版做签到,看视频,做任务,参与抽奖,邀请好友赚钱)。

                邀请两个好友奖最高196元,如下图所示:

                快手极速版邀请好友奖励

                扫描二维码推送至手机访问。

                版权声明:本文由34楼发布,如需转载请注明出处。

                本文链接:https://www.34l.com/post/7179.html

                分享给朋友:

                相关文章

                win7开始菜单设置在哪里(win7开始菜单变成经典模式)

                win7开始菜单设置在哪里(win7开始菜单变成经典模式)

                我们在用win7系统的时候,发现打开了一些程序后,会在开始菜单那里显示这些最近打开的程序。有些人不想把自己最近打开的程序显示在这里,那么怎么可以删除这些程序,或者彻底让这里不会显示最近打开程序呢?下面我来教大家删除或设置不显示最近打开的程序…

                拉夏贝尔启示录:扩张只会“一地鸡毛”

                拉夏贝尔启示录:扩张只会“一地鸡毛”

                图源:摄图网 编者按:本文来自微信公众号蓝莓财经(ID:ITparty),作者:蓝莓君,创业邦经授权转载 11月22日晚间,拉夏贝尔发布关于被债权人申请破产清算的提示性公告。24日“拉夏贝尔被申请破产清算”登上热搜第一。 拉夏贝尔的跌落,从…

                中小企业困在账期里,年关再掀清欠之战

                中小企业困在账期里,年关再掀清欠之战

                编者按:本文转自经济观察报,作者高若瀛,创业邦经授权转载。 中秋节当天,还在家陪孩子的杨劲松,突然接到事业部经理的电话。 对方在电话里有些紧张,说他们的大客户新力地产,股价“跌了5毛”。 杨劲松起初没当回事,晚上再瞄股价时,却从头凉到脚:股…

                物联网应用案例分析(物联网技术的应用案例)

                物联网应用案例分析(物联网技术的应用案例)

                近日,工信部公布了44项移动物联网应用案例入选名单。 案例主要面向三大重点方向: 一是围绕工业制造、仓储物流、智慧农业、智慧医疗等领域的产业数字化应用; 二是围绕智能计量、消防烟感、共享单车、环保监测等领域的治理智能化应用; 三是围绕消费…

                ipad怎么录音转文字(分享3个录音转文字的方法)

                ipad怎么录音转文字(分享3个录音转文字的方法)

                在我们日常工作和日常学习中,我们在一些需要将话语内容给记录下来的场合,通常会选择录音,这样之后我们可以进行反复听写并整理成文稿。但随着科技的不断发展,我们现在不仅可以直接录音,还可以直接把音频文件转换成文字,这样就不需要我们自己对音频文件进…