package com.yidianling.fm.widget /** * @author <a href="https://www.jianshu.com/u/c1e5310dd724">xujian</a> * @描述: FM播放页面动画效果 * @Copyright Copyright (c) 2019 * @Company 壹点灵 * @date 2019/05/08 */ import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.SurfaceHolder import android.view.SurfaceView import android.view.View import com.yidianling.common.tools.RxDeviceTool import java.lang.Exception import java.util.* class FMSurfaceView(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs), SurfaceHolder.Callback { private var mHolder: SurfaceHolder? = null private var paint: Paint? = null private var path: Path? = null private var canvas: Canvas? = null private var timer: Timer? = null private var timerTask: TimerTask? = null private var minRadius: Float = (RxDeviceTool.getScreenWidth(context) * 150 / 375 / 2).toFloat() // 初始化最小六边形所在圆半径 private var ringRadius: Float = (RxDeviceTool.getScreenWidth(context) * 145 / 375 / 2).toFloat() // 初始化内部圆环半径 private var ringWidth: Float = (RxDeviceTool.getScreenWidth(context) * 10 / 375 / 2).toFloat() // 初始化内部圆环宽度 private var presentRadius: Float = minRadius // 初始化当前最小六边形所在圆半径 private var presentRadiusChangeNumber: Float = 1f private var maxRadius: Float = (RxDeviceTool.getScreenWidth(context) * 198 / 375 / 2).toFloat() // 初始化六边形所在圆最大时的半径 private var circleSpace: Float = maxRadius - minRadius // 初始化圆边之间的距离 private var clockwise: Boolean = true // 默认最小的六边形为顺时针旋转 private var hintColor: String = "FFFFFF" // 默认不透明色 private var percentColorNum: Float = 105f // 初始化初始颜色透明度 255f的时候最小的六边形为纯色没有透明度,颜色为hintColor private var cornerRadius: Float = (RxDeviceTool.getScreenWidth(context) * 150 / 375 / 2).toFloat() // paint初始拐角半径 private var periodTime: Long = 30 // 定时任务间隔时间 private var firstPlay: Boolean = false private var xfermode: PorterDuffXfermode? = null init { initPaint() initPath() mHolder = holder mHolder?.addCallback(this) //该行代码为设置透明 setZOrderOnTop(true) mHolder?.setFormat(PixelFormat.TRANSLUCENT) xfermode = PorterDuffXfermode(PorterDuff.Mode.XOR)//绘制交集,显示非交集 } private fun initPath() { path = Path() } private fun initPaint() { paint = Paint(Color.GRAY) paint?.color = Color.WHITE paint?.isAntiAlias = true //抗锯齿 paint?.style = Paint.Style.FILL //设置画笔为填充模式 paint?.strokeCap = Paint.Cap.ROUND //画笔拐角为圆弧 paint?.pathEffect = CornerPathEffect(cornerRadius) //设置path拐角半径 } fun playTimer() { if (null == timer) { timer = Timer() timerTask = timerTask ?: object : TimerTask() { override fun run() { drawAnimate() } } timer?.let { timer!!.schedule(timerTask, 0, periodTime) } } } private fun drawAnimate() { synchronized(this) { mHolder?.let { canvas = mHolder?.lockCanvas() canvas?.let { //清空画布,进行重绘 canvas?.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) //重置最小六边形半径 resetPresentRadius() paint?.style = Paint.Style.FILL //设置画笔为填充模式 //绘制四个变化的六边形 drawMinHexagon() drawMiddleHexagon() drawMaxHexagon() drawMMaxHexagon() //绘制图片外面包围的一层圆 canvas?.let { paint?.strokeWidth = ringWidth + 0.5f paint?.style = Paint.Style.STROKE //设置画笔为线模式 paint?.color = Color.parseColor("#D0FFFFFF") paint?.setShadowLayer(ringWidth, 1F, 1F, Color.parseColor("#D0FFFFFF")) canvas!!.drawCircle((canvas?.width!! / 2).toFloat(), (canvas?.height!! / 2).toFloat(), ringRadius + 0.5f, paint!!) paint?.setShadowLayer(0F, 0F, 0F, Color.WHITE) } presentRadius += presentRadiusChangeNumber //每次循环presentRadius+presentRadiusChangeNumber } //这里有些机型上面会出现异常,暴力try一次,也可以用 mHolder?.surface?.isValid先进行一次判断后进行try try { mHolder?.unlockCanvasAndPost(canvas) // android 4.3 会有IllegalArgumentException } catch (e: Exception) { } } } } private fun drawPath(point: Point, pointRadius: Float) { path = Path() path?.moveTo(point.x + canvas?.width!! / 2, point.y + canvas?.height!! / 2) for (i in 1..5) { path?.lineTo((point.x * Math.cos(getRadian(60 * i)) - point.y * Math.sin(getRadian(60 * i))).toFloat() + canvas?.width!! / 2, (point.x * Math.sin(getRadian(60 * i)) + point.y * Math.cos(getRadian(60 * i))).toFloat() + canvas?.height!! / 2) } path?.close() path?.addCircle((canvas?.width!! / 2).toFloat(), (canvas?.height!! / 2).toFloat(), minRadius, Path.Direction.CCW) path?.fillType = Path.FillType.EVEN_ODD /** * 这边是2,1,根号3的三角形, 这部分代码绘制了从圆到六边形的变化 */ val mRadius = (pointRadius * 1.7320508075689 / 2).toFloat() if (mRadius > minRadius) { //设置paint 拐角半径 if (mRadius - minRadius < 50f) paint?.pathEffect = CornerPathEffect(cornerRadius - (mRadius - minRadius) * 2) else paint?.pathEffect = CornerPathEffect(cornerRadius - 100f) canvas?.drawPath(path!!, paint!!) } } private fun getRotatePoint(point: Point, angle: Int): Point { return Point( (point.x * Math.cos(getRadian(angle)) - point.y * Math.sin(getRadian(angle))).toFloat(), (point.x * Math.sin(getRadian(angle)) + point.y * Math.cos(getRadian(angle))).toFloat() ) } /** * 绘制最小的六边形 */ private fun drawMinHexagon() { val radius: Float = presentRadius var percentOfColor: String = getColorPercent(radius) percentOfColor = if (percentOfColor.length == 2) percentOfColor else "0${percentOfColor[0]}" paint?.color = Color.parseColor("#$percentOfColor$hintColor") canvas?.let { if (clockwise) { drawPath(getRotatePoint(Point(0f, radius), (radius - minRadius).toInt()), radius) } else { drawPath(getRotatePoint(Point(0f, radius), -(radius - minRadius).toInt()), radius) } } } /** * 绘制中间的六边形 */ private fun drawMiddleHexagon() { val radius: Float = presentRadius + circleSpace var percentOfColor: String = getColorPercent(radius) percentOfColor = if (percentOfColor.length == 2) percentOfColor else "0${percentOfColor[0]}" paint?.color = Color.parseColor("#$percentOfColor$hintColor") canvas?.let { if (clockwise) { drawPath(getRotatePoint(Point(0f, radius), -(radius - minRadius).toInt()), radius) } else { drawPath(getRotatePoint(Point(0f, radius), (radius - minRadius).toInt()), radius) } } } /** * 绘制最大的六边形 */ private fun drawMaxHexagon() { val radius: Float = presentRadius + circleSpace * 2 var percentOfColor: String = getColorPercent(radius) percentOfColor = if (percentOfColor.length == 2) percentOfColor else "0${percentOfColor[0]}" paint?.color = Color.parseColor("#$percentOfColor$hintColor") canvas?.let { if (clockwise) { drawPath(getRotatePoint(Point(0f, radius), (radius - minRadius).toInt()), radius) } else { drawPath(getRotatePoint(Point(0f, radius), -(radius - minRadius).toInt()), radius) } } } /** * 绘制最大的六边形之再来一个更大的 */ private fun drawMMaxHexagon() { val radius: Float = presentRadius + circleSpace * 3 var percentOfColor: String = getColorPercent(radius) percentOfColor = if (percentOfColor.length == 2) percentOfColor else "0${percentOfColor[0]}" paint?.color = Color.parseColor("#$percentOfColor$hintColor") canvas?.let { if (clockwise) { drawPath(getRotatePoint(Point(0f, radius), -(radius - minRadius).toInt()), radius) } else { drawPath(getRotatePoint(Point(0f, radius), (radius - minRadius).toInt()), radius) } } } private fun getColorPercent(radius: Float): String { return Integer.toHexString(((1f - (radius - minRadius) / (maxRadius + circleSpace * 3 - minRadius)) * percentColorNum).toInt()) } /** * 重置最小半径 */ private fun resetPresentRadius() { if (presentRadius >= maxRadius) { presentRadius = minRadius clockwise = !clockwise firstPlay = true } } fun stopTimer() { releaseResource() } /** * 清除定时任务,进行资源释放 */ private fun releaseResource() { timer?.let { timer?.cancel() timerTask?.cancel() timerTask = null timer = null } } /** * 根据角度获取弧度值 */ private fun getRadian(angle: Int): Double { return angle * (Math.PI / 180) } override fun surfaceDestroyed(holder: SurfaceHolder?) { //mHolder?.removeCallback(this) releaseResource() this.setLayerType(View.LAYER_TYPE_SOFTWARE, null) } override fun surfaceCreated(holder: SurfaceHolder?) { this.setLayerType(View.LAYER_TYPE_SOFTWARE, paint) drawAnimate() } override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { } class Point(val x: Float, val y: Float) }