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