Newer
Older
SmartKitchen / app / src / main / java / com / casic / br / widgets / WaterRippleView.kt
package com.casic.br.widgets

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.view.View
import com.casic.br.R
import com.pengxh.kt.lite.extensions.convertColor
import com.pengxh.kt.lite.extensions.dp2px
import com.pengxh.kt.lite.extensions.sp2px
import java.util.*

/**
 * @description:  呼叫设备水波纹扩散动画控件
 * @author: Pengxh
 * @email: 290677893@qq.com
 * @date: 2020年9月17日14:24:45
 */
class WaterRippleView constructor(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val kTag = "WaterRippleView"

    //中心圆半径
    private val radius: Int

    //每次圆递增间距,值越大,扩散速度越快
    private val distance: Int

    //最大扩散距离,值越小,扩散效果越明显
    private val maxDistance: Int
    private val textSize: Int
    private val textColor: Int
    private val spreadColor: Int
    private val centerColor: Int

    //图片资源
    private val imageResourceId: Int
    private val spreadRadius: MutableList<Int> = ArrayList() //扩散圆层级数,元素为扩散的距离
    private val alphas: MutableList<Int> = ArrayList() //对应每层圆的透明度

    //圆心x
    private var centerX = 0f

    //圆心y
    private var centerY = 0f

    //扩散延迟间隔,越大扩散越慢
    private var animDuration = 50
    private var isStart = false
    private var text = "呼叫设备"
    private val textPaint by lazy { TextPaint() }
    private val imagePaint by lazy { Paint() }

    //中心圆paint
    private val centerPaint by lazy { Paint() }

    //扩散圆paint
    private val spreadPaint by lazy { Paint() }

    init {
        val a = context.obtainStyledAttributes(attrs, R.styleable.WaterRippleView)
        centerColor = a.getColor(
            R.styleable.WaterRippleView_ripple_centerColor,
            R.color.mainThemeColor.convertColor(context)
        )
        spreadColor = a.getColor(
            R.styleable.WaterRippleView_ripple_spreadColor,
            R.color.mainThemeColor.convertColor(context)
        )
        animDuration = a.getInteger(R.styleable.WaterRippleView_ripple_animDuration, animDuration)
        radius = a.getDimensionPixelOffset(
            R.styleable.WaterRippleView_ripple_radius, 50f.dp2px(context)
        )
        distance = a.getDimensionPixelOffset(
            R.styleable.WaterRippleView_ripple_distance, 3f.dp2px(context)
        )
        maxDistance = a.getDimensionPixelOffset(
            R.styleable.WaterRippleView_ripple_maxDistance, 30f.dp2px(context)
        )
        textSize = a.getDimensionPixelOffset(
            R.styleable.WaterRippleView_ripple_textSize, 16f.sp2px(context)
        )
        text = a.getString(R.styleable.WaterRippleView_ripple_text).toString()
        textColor = a.getColor(
            R.styleable.WaterRippleView_ripple_textColor, R.color.white.convertColor(context)
        )
        imageResourceId = a.getResourceId(R.styleable.WaterRippleView_ripple_image, R.mipmap.hujiao)
        a.recycle()

        //最开始不透明且扩散距离为0
        alphas.add(255)
        spreadRadius.add(0)
        //初始化画笔
        initPaint()
        if (isStart) {
            Log.d(kTag, "onClick: 重复启动动画")
        } else {
            start()
        }
    }

    private fun initPaint() {
        //中心圆画笔
        centerPaint.color = centerColor
        centerPaint.isAntiAlias = true

        //扩散圆圈画笔
        spreadPaint.isAntiAlias = true
        spreadPaint.alpha = 255
        spreadPaint.color = spreadColor

        //中心圆文字画笔
        textPaint.color = textColor
        textPaint.isAntiAlias = true
        textPaint.textSize = textSize.toFloat()

        //中心圆上面图片画笔
        imagePaint.isAntiAlias = true
        imagePaint.alpha = 255
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        //圆心位置
        centerX = (w shr 1).toFloat()
        centerY = (h shr 1).toFloat()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)
        // 获取宽
        val mWidth: Int = if (widthSpecMode == MeasureSpec.EXACTLY) {
            // match_parent/精确值
            widthSpecSize
        } else {
            // wrap_content
            (2 * (maxDistance + radius)).toFloat().dp2px(context)
        }
        // 获取高
        val mHeight: Int = if (heightSpecMode == MeasureSpec.EXACTLY) {
            // match_parent/精确值
            heightSpecSize
        } else {
            // wrap_content
            (2 * (maxDistance + radius)).toFloat().dp2px(context)
        }
        // 设置该view的宽高
        setMeasuredDimension(mWidth, mHeight)
    }

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (isStart) {
            for (i in spreadRadius.indices) {
                var alpha = alphas[i]
                spreadPaint.alpha = alpha
                val width = spreadRadius[i]
                //绘制扩散的圆
                canvas.drawCircle(centerX, centerY, (radius + width).toFloat(), spreadPaint)
                //每次扩散圆半径递增,圆透明度递减
                if (alpha > 0 && width < (maxDistance + radius).toFloat().dp2px(context)) {
                    alpha = (alpha - distance).coerceAtLeast(0)
                    alphas[i] = alpha
                    spreadRadius[i] = width + distance
                }
            }
            //当最外层扩散圆半径达到最大半径时添加新扩散圆
            val maxRadius = spreadRadius[spreadRadius.size - 1]
            if (maxRadius > maxDistance) {
                spreadRadius.add(0)
                alphas.add(255)
            }
            //超过5个扩散圆,删除最先绘制的圆,即最外层的圆
            if (spreadRadius.size >= 8) {
                alphas.removeAt(0)
                spreadRadius.removeAt(0)
            }
            postInvalidateDelayed(animDuration.toLong())
        }
        //中间的圆
        canvas.drawCircle(centerX, centerY, radius.toFloat(), centerPaint)

        //绘制文字
        val textRect = Rect()
        textPaint.getTextBounds(text, 0, text.length, textRect)
        val textWidth = textRect.width()
        val textHeight = textRect.height()
        //计算文字左下角坐标
        val textX = centerX - (textWidth shr 1)
        val textY = centerY + 4 * textHeight / 3 //让文字靠下点,所以Y坐标大一点
        canvas.drawText(text, textX, textY, textPaint)

        //绘制图片
        val bitmap = BitmapFactory.decodeResource(resources, imageResourceId) //实例化

        /**
         * 方法一:此方法局限性大,对于图片要求较高,必须是尺寸合适的图片,否则会显示异常
         */
        val imageWidth = bitmap.width
        val imageHeight = bitmap.height
        //计算图片左上角坐标
        val imageX = centerX - (imageWidth shr 1)
        val imageY = centerY - 4 * imageHeight / 3 //让图片靠上点,所以Y坐标小一点
        canvas.drawBitmap(bitmap, imageX, imageY, imagePaint)
        /**
         * 方法二:此方法是为将要绘制的图片划定显示区域,可以缓解图片尺寸导致显示异常问题
         */
//        RectF rectF = new RectF(centerX - (textWidth >> 2), centerY - (textHeight * 2),
//                centerX + (textWidth >> 2), centerY - (textHeight >> 1));
//        Log.d(kTag, "height: " + rectF.height() + " ==== width: " + rectF.width());
//        canvas.drawBitmap(bitmap, null, rectF, imagePaint);
        /**
         * 方法三名:采用矩阵方式实现,这可彻底解决图片尺寸导致显示异常的问题
         *
         * Matrix matrix=new Matrix();
         */
    }

    /**
     * 启动动画
     */
    fun start() {
        Log.d(kTag, "start: 启动动画")
        text = "正在搜索"
        isStart = true
        postInvalidate()
    }

    /**
     * 停止动画
     */
    fun stop() {
        Log.d(kTag, "stop: 停止动画")
        text = "停止搜索"
        isStart = false
        postInvalidate()
    }

    private var startListener: OnAnimationStartListener? = null

    interface OnAnimationStartListener {
        fun onStart(view: WaterRippleView?)
    }

    fun setOnAnimationStartListener(listener: OnAnimationStartListener?) {
        startListener = listener
    }
}