Android点赞控件--仿掘金点赞七成效果

0.

前些日子偶然看到掘金推荐里的点赞效果,感觉有些酷炫,然后在一个无所事事的早上,我决定实现一个类似的功能,但是只有七成的效果,效果图如下。

1.

这个效果我把它们分成了几个阶段:

(1)默认阶段:就是一个竖起来的大拇指

(2)收缩阶段:大拇指逐渐缩小直到消失

(3)放大阶段:圆圈由小到大

(4)圆环阶段:圆圈有中心破裂,露出大拇指

(5)卫星阶段:出现卫星,向远处逐渐偏离同时逐渐消失

2.

话不多说,上代码吧

package com.skateboard.favouriteview

import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import android.view.View

class FavouriteView(context: Context, attrs: AttributeSet?, defStyle: Int) : View(context, attrs, defStyle)
{
    private lateinit var paint: Paint

    private lateinit var statellitePaint: Paint

    private lateinit var path: Path

    private var spaceBetweenHandAndShoulder = 5

    private var state = STATE_NORMAL

    private lateinit var valueAnimator: ValueAnimator

    private var size = 0

    private var centerX = 0f

    private var centerY = 0f

    private var strokeWithScaleFraction = 1f

    private var statelliteOffsetFraction = 0f

    private var selectedColor = Color.BLACK

    private var normalColor = Color.BLACK

    companion object
    {
        val STATE_SELECTED = 1

        val STATE_CIRCLE = 2

        val STATE_RING = 3

        val STATE_STATELLITE = 4

        val STATE_NORMAL = 0
    }

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context) : this(context, null, 0)

    init
    {
        if (attrs != null)
        {
            initParse(attrs)
        }
        initPaint()
        initStatellitePaint()
        initClickEvent()
    }

    private fun initParse(attrs: AttributeSet)
    {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FavouriteView)
        selectedColor = typedArray.getColor(R.styleable.FavouriteView_selected_color, Color.BLACK)
        normalColor = typedArray.getColor(R.styleable.FavouriteView_normal_color, Color.BLACK)
        typedArray.recycle()
    }

    private fun initPaint()
    {
        paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.color = normalColor
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 4f
        path = Path()
        val cornerPathEffect = CornerPathEffect(10f)
        paint.pathEffect = cornerPathEffect
    }

    private fun initStatellitePaint()
    {
        statellitePaint = Paint(Paint.ANTI_ALIAS_FLAG)
        statellitePaint.color = selectedColor
        statellitePaint.style=Paint.Style.FILL
    }

    private fun initAnimator()
    {
        valueAnimator = ValueAnimator.ofFloat(0f, 400f)
        valueAnimator.duration = 500
        valueAnimator.addUpdateListener(updateListener)
        valueAnimator.addListener(object : Animator.AnimatorListener
        {
            override fun onAnimationStart(animation: Animator?)
            {

            }

            override fun onAnimationRepeat(animation: Animator?)
            {

            }


            override fun onAnimationEnd(animation: Animator?)
            {
                scaleX = Math.max(1f, scaleX)
                scaleY = Math.max(1f, scaleY)
                setState(STATE_SELECTED)
            }

            override fun onAnimationCancel(animation: Animator?)
            {

            }


        })
    }

    private val updateListener = ValueAnimator.AnimatorUpdateListener {

        val time = Math.round(it.animatedValue as Float)
        when
        {
            time <= 100.0f ->
            {
                scaleX = Math.max(0f, 1f - time / 100f)
                scaleY = Math.max(0f, 1f - time / 100f)
            }
            time in 101..200 ->
            {
                scaleX = Math.min(1f, (time - 100) / 100f)
                scaleY = Math.min(1f, (time - 100) / 100f)
                setState(STATE_CIRCLE)

            }

            time in 201..300 ->
            {
                scaleX = 1f
                scaleY = 1f
                strokeWithScaleFraction = ((time - 200) / 100f)
                setState(STATE_RING)
                postInvalidate()
            }

            else ->
            {
                statelliteOffsetFraction = ((time - 300) / 100f)
                setState(STATE_STATELLITE)
                postInvalidate()
            }
        }
    }

    private fun setState(newState: Int)
    {
        if (state != newState)
        {
            state = newState
            postInvalidate()
        }
    }

    override fun onAttachedToWindow()
    {
        super.onAttachedToWindow()
        initAnimator()
    }


    private fun initClickEvent()
    {
        setOnClickListener {

            if (state == STATE_NORMAL)
            {
                startAnimate()
            } else
            {
                setState(STATE_NORMAL)
            }

        }
    }

    private fun startAnimate()
    {
        if (valueAnimator.isRunning)
        {
            return
        } else
        {
            valueAnimator.start()
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int)
    {
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        size = Math.min(widthSize, heightSize)
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

    }

    override fun onDraw(canvas: Canvas?)
    {
        super.onDraw(canvas)
        prepareToDraw()
        if (canvas != null)
        {
            when (state)
            {
                STATE_NORMAL ->
                {
                    resetPaintColor()
                    drawFinger(canvas)
                }
                STATE_SELECTED ->
                {
                    resetPaintColor()
                    drawFinger(canvas)
                }

                STATE_CIRCLE ->
                {
                    resetPaintColor()
                    drawCircle(canvas)
                }

                STATE_RING ->
                {

                    resetPaintColor()
                    drawRing(canvas)
                    drawFinger(canvas)
                }

                STATE_STATELLITE ->
                {

                    resetPaintColor()
                    drawStatellite(canvas)
                }
            }

        }
    }

    private fun resetPaintColor()
    {
        when
        {

            state == STATE_NORMAL ->
            {
                paint.colorFilter = null
                paint.color = normalColor
            }

            state != STATE_STATELLITE ->
            {
                paint.colorFilter = null
                paint.color = selectedColor
            }

            else ->
            {
                paint.color = selectedColor
                if (statelliteOffsetFraction <= 0.5f)
                {
                    val parameter = 1f
                    statellitePaint.colorFilter = ColorMatrixColorFilter(floatArrayOf(1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, parameter, 0f))
                } else
                {
                    val parameter = 1 - statelliteOffsetFraction
                    statellitePaint.colorFilter = ColorMatrixColorFilter(floatArrayOf(1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, parameter, 0f))
                }
            }
        }
    }


    private fun prepareToDraw()
    {
        path.reset()
        centerX = (width / 2).toFloat()
        centerY = (height / 2).toFloat()
    }


    private fun drawFinger(canvas: Canvas)
    {
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 4f
        val fingerWidth = (size / 2).toFloat()
        val fingerHeight = (size / 2).toFloat()
        val centerX = (width / 2).toFloat() + fingerWidth / 8
        val centerY = (height / 2).toFloat() + fingerHeight / 8
        path.addRect(centerX - fingerWidth / 2, centerY - fingerHeight / 4, centerX - fingerWidth / 3, centerY + fingerHeight / 4, Path.Direction.CW)
        path.moveTo(centerX - fingerWidth / 3 + spaceBetweenHandAndShoulder, centerY - fingerHeight / 4)
        path.rLineTo(fingerWidth / 8, 0f)
        path.rLineTo(fingerWidth / 8, -fingerHeight / 2)
        path.rLineTo(fingerWidth / 6, fingerHeight / 4)
        path.rLineTo(-fingerWidth / 8, fingerHeight / 4)
        path.rLineTo(fingerWidth / 2 - fingerWidth / 6 - spaceBetweenHandAndShoulder, 0f)
        path.rLineTo(-fingerWidth / 8, fingerHeight / 2)
        path.lineTo(centerX - fingerWidth / 3 + spaceBetweenHandAndShoulder, centerY + fingerHeight / 4)
        path.close()
        canvas.drawPath(path, paint)
    }

    private fun drawCircle(canvas: Canvas)
    {
        val radius = (size.toFloat()) / 3
        paint.style = Paint.Style.FILL
        canvas.drawCircle(centerX, centerY, radius, paint)
    }

    private fun drawStatellite(canvas: Canvas)
    {
        drawFinger(canvas)
        drawSmallStatellites(canvas)
    }

    private fun drawRing(canvas: Canvas)
    {
        val radius = (size.toFloat()) / 3
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = ((1 - strokeWithScaleFraction) * radius)
        canvas.drawCircle(centerX, centerY, radius - paint.strokeWidth / 2, paint)
    }

    private fun drawSmallStatellites(canvas: Canvas)
    {
        val bigRadius = (size.toFloat()) / 3
        val smallRadius = (centerY - bigRadius) / 3
        val offset = size / 2 - bigRadius - 2 * smallRadius
        canvas.drawCircle(centerX, centerY - bigRadius - offset * statelliteOffsetFraction, smallRadius, statellitePaint)
        canvas.drawCircle(centerX + bigRadius + offset * statelliteOffsetFraction, centerY, smallRadius, statellitePaint)
        canvas.drawCircle(centerX, centerY + bigRadius + offset * statelliteOffsetFraction, smallRadius, statellitePaint)
        canvas.drawCircle(centerX - bigRadius - offset * statelliteOffsetFraction, centerY, smallRadius, statellitePaint)
    }
}
复制代码

这个控件的所有代码就在这里了,重点分一下几个关键点

3.

绘制大拇指 private fun drawFinger(canvas: Canvas) { paint.style = Paint.Style.STROKE paint.strokeWidth = 4f val fingerWidth = (size / 2).toFloat() val fingerHeight = (size / 2).toFloat() val centerX = (width / 2).toFloat() + fingerWidth / 8 val centerY = (height / 2).toFloat() + fingerHeight / 8 path.addRect(centerX - fingerWidth / 2, centerY - fingerHeight / 4, centerX - fingerWidth / 3, centerY + fingerHeight / 4, Path.Direction.CW) path.moveTo(centerX - fingerWidth / 3 + spaceBetweenHandAndShoulder, centerY - fingerHeight / 4) path.rLineTo(fingerWidth / 8, 0f) path.rLineTo(fingerWidth / 8, -fingerHeight / 2) path.rLineTo(fingerWidth / 6, fingerHeight / 4) path.rLineTo(-fingerWidth / 8, fingerHeight / 4) path.rLineTo(fingerWidth / 2 - fingerWidth / 6 - spaceBetweenHandAndShoulder, 0f) path.rLineTo(-fingerWidth / 8, fingerHeight / 2) path.lineTo(centerX - fingerWidth / 3 + spaceBetweenHandAndShoulder, centerY + fingerHeight / 4) path.close() canvas.drawPath(path, paint) } 大拇指的绘制是通过path来实现的,size=Math.min(width,height)大拇指占整个size的1/2,另外还要注意的是,当然还要注意为path设置patheffect private fun initPaint() { paint = Paint(Paint.ANTI_ALIAS_FLAG) paint.color = normalColor paint.style = Paint.Style.STROKE paint.strokeWidth = 4f path = Path() val cornerPathEffect = CornerPathEffect(10f) paint.pathEffect = cornerPathEffect } 否则,你的大拇指将尖锐无比。

#3. 动画开启后,根据时间来区分状态,这里我决定用valueanimator,众所周知,valueanimator是用来做动画的,但事实上他只是对传入的数值进行修改,但是并不会对view起到什么作用,真正起作用的是对valueanimator设置updatelistener,根据改变的数值修改view的属性才起到动画作用的。这正是我们需要的,所以看updatelistener吧 `` private val updateListener = ValueAnimator.AnimatorUpdateListener {

    val time = Math.round(it.animatedValue as Float)
    when
    {
复制代码

//收缩阶段 time <= 100.0f -> { scaleX = Math.max(0f, 1f - time / 100f) scaleY = Math.max(0f, 1f - time / 100f) } //放大阶段 time in 101..200 -> { scaleX = Math.min(1f, (time - 100) / 100f) scaleY = Math.min(1f, (time - 100) / 100f) setState(STATE_CIRCLE)

        }
复制代码

//圆环阶段 time in 201..300 -> { scaleX = 1f scaleY = 1f strokeWithScaleFraction = ((time - 200) / 100f) setState(STATE_RING) postInvalidate() } //卫星阶段 else -> { statelliteOffsetFraction = ((time - 300) / 100f) setState(STATE_STATELLITE) postInvalidate() } } } #4. 卫星的绘制有两点,一是一个距离的改变,二是颜色的逐渐alpha逐渐变0. private fun drawSmallStatellites(canvas: Canvas) { val bigRadius = (size.toFloat()) / 3 val smallRadius = (centerY - bigRadius) / 3 val offset = size / 2 - bigRadius - 2 * smallRadius canvas.drawCircle(centerX, centerY - bigRadius - offset * statelliteOffsetFraction, smallRadius, statellitePaint) canvas.drawCircle(centerX + bigRadius + offset * statelliteOffsetFraction, centerY, smallRadius, statellitePaint) canvas.drawCircle(centerX, centerY + bigRadius + offset * statelliteOffsetFraction, smallRadius, statellitePaint) canvas.drawCircle(centerX - bigRadius - offset * statelliteOffsetFraction, centerY, smallRadius, statellitePaint) } `` 距离的改变是根据statelliteOffsetFraction决定的,statelliteOffsetFraction变量这个是在updatelistener里进行不断改变的。

至于颜色的变化 paint.color = selectedColor if (statelliteOffsetFraction <= 0.5f) { val parameter = 1f statellitePaint.colorFilter = ColorMatrixColorFilter(floatArrayOf(1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, parameter, 0f)) } else { val parameter = 1 - statelliteOffsetFraction statellitePaint.colorFilter = ColorMatrixColorFilter(floatArrayOf(1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, parameter, 0f)) } 这里使用了colormatrix。

#5. 为什么说是七成效果,因为卫星的位置不一样,圆环阶段爆炸后大拇指有一个由大到正常的恢复过程,还有wrap_content以及padding等没有实现,为什么会这样?一个字,懒,懒得做。。。 附上github地址

#结尾 终于写完了,关灯,睡觉。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值