上一篇已经讲解了自我绘制的步骤。今天我们按照上一篇的步骤:结合贝塞尔曲线绘制一个圆形波浪进度条
效果图:
按照步骤:
1,定义默认的宽度和高度
//控件默认的宽private var defult_Widht = DisplayUtils.dip2px(context, 150F)
//控件默认的高private var defult_Height = DisplayUtils.dip2px(context, 150F)2.告诉父布局:
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(resolveSize(defult_Widht.toInt(), widthMeasureSpec),
resolveSize(defult_Height.toInt(), heightMeasureSpec))}3.矫正绘制区域:
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
//矫正宽度和高度
defult_Widht = w.toFloat()
defult_Height = h.toFloat()
}4.分析内部元素:
绘制一个圆:用drawCircle方法。算出圆心和半径即可
绘制文字:用drawText方法。算出文字绘制起点即可
绘制曲线:使用贝塞尔曲线drawPath即可
为了让圆外部不出现波浪,需要使用 canvas.clipPath()方法
注意到上边的元素,绘制的关键是绘制贝塞尔曲线,而这个曲线的绘制关键是控制点的确定
那么怎么确定这些点呢?
自我绘制四中我们已知的条件是:确定的周期。确定一个波的周期 = 屏幕的宽度。在特殊情况下确定每一个点的位置,这里我们把周期和振幅和波长都设置成参数值,那么这个怎么算呢?
我画了一个草稿图:
图上边红线左边只显示两个完整的波形,右边也显示两个波形,实际可以动态设置,
看到这里如果我们需要算坐标需要一些必要的参数:
//波形的一个周期长度
private val cycle = DisplayUtils.dip2px(context, 90F)
//波振幅高度
private val waveHeight = DisplayUtils.dip2px(context, 20F)
//屏幕外波形绘制几个周期
private val cycle_Count = 4有了些参数,我们可以动态算出来坐标的位置,
因为需求是可以左右和上下移动波形,所以我们还需要定义两个参数,先默认是0,之后我们写动画时候用:
//平移的距离
private var movelenght = 0F
//Y方法关键点移动的距离
private var progress = 0F
然后我们先算左边所有坐标:
//开始点
var startX = -cycle * cycle_Count + movelenght
var startY = defult_Height + waveHeight / 2 - progress
mWavePath.moveTo(startX, startY)
//计算屏幕外左边所有控制点
for (i in cycle_Count downTo 1) {
//波峰
var fengX = -cycle * (i) + cycle / 4 + movelenght
var fengY = defult_Height - progress
//中间点1
var endX = -cycle * (i) + cycle / 2 + movelenght
var endY = defult_Height + waveHeight / 2 - progress
mWavePath.quadTo(fengX, fengY, endX, endY)
//波谷的点
var guX = -cycle * (i) + cycle * 3 / 4 + movelenght
var guY = defult_Height + waveHeight - progress
//中间点2
var endX2 = -cycle * (i - 1) + movelenght
var endY2 = defult_Height + waveHeight / 2 - progress
mWavePath.quadTo(fengX, fengY, endX, endY)
mWavePath.quadTo(guX, guY, endX2, endY2)
}然后算屏幕右边:
/计算屏幕右边所有控制点
for (i in 1..cycle_Count) {
//波峰
var fengX = cycle * (i) - cycle * 3 / 4 + movelenght
var fengY = defult_Height - progress
//中间点1
var endX = cycle * (i) - cycle / 2 + movelenght
var endY = defult_Height + waveHeight / 2 - progress
//波谷的点
var guX = cycle * (i) - cycle * 1 / 4 + movelenght
var guY = defult_Height + waveHeight - progress
//中间点2
var endX2 = cycle * (i) + movelenght
var endY2 = defult_Height + waveHeight / 2 - progress
mWavePath.quadTo(fengX, fengY, endX, endY)
mWavePath.quadTo(guX, guY, endX2, endY2)
}最后封口
mWavePath.lineTo(cycle_Count * cycle, defult_Height + waveHeight)
mWavePath.lineTo(-cycle_Count * cycle, defult_Height + waveHeight)
//以上吧点添加完毕,然后就是上移一下看看效果
canvas.drawPath(mWavePath, wave_Paint)之后我们就可以写一个属性动画,通过改变movelenght 和progress的值去实现动画。
完整代码:
package MyViews
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.animation.LinearInterpolator
/**
* Created by Administrator on 2018/7/13.
* 自定义水波纹进度球
*
*/
class CircleBezierProgress : View {
//控件默认的宽
private var defult_Widht = DisplayUtils.dip2px(context, 150F)
//控件默认的高
private var defult_Height = DisplayUtils.dip2px(context, 150F)
//默认圆绘制线宽度
private var defult_bg_StrokeWidth = DisplayUtils.dip2px(context, 4F)
//绘制圆的画笔
private lateinit var bg_Paint: Paint
//需要裁剪的区域
private val clipCirclePath: Path
//绘制波浪的路径
private val mWavePath: Path
//波形的一个周期长度
private val cycle = DisplayUtils.dip2px(context, 90F)
//波振幅高度
private val waveHeight = DisplayUtils.dip2px(context, 20F)
//屏幕外波形绘制几个周期
private val cycle_Count = 4
//画线波浪的画笔
private lateinit var wave_Paint: Paint
//平移的距离
private var movelenght = 0F
set(value) {
field = value
invalidate()
}
//Y方法关键点移动的距离
private var progress = 0F
set(value) {
field = value
invalidate()
}
//默认文字的大小
private val textSize = DisplayUtils.sp2px(context, 38F)
//绘制中心文字的画笔
private lateinit var centerText_Paint: Paint
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
initPaint()
//需要裁剪的区域
clipCirclePath = Path()
//绘制曲线的路径
mWavePath = Path()
}
//初始化画笔
private fun initPaint() {
//创建绘制弧形的画笔
bg_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
//画笔的颜色
bg_Paint.color = Color.parseColor("#FFAA00")
//设置画线模式
bg_Paint.style = Paint.Style.STROKE
//设置画线的宽度
bg_Paint.strokeWidth = defult_bg_StrokeWidth
//画线波浪的画笔
wave_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
//设置画线模式
wave_Paint.style = Paint.Style.FILL
//设置线的颜色
wave_Paint.color = Color.parseColor("#FFAA00")
//创建绘制中心文字的画笔
centerText_Paint = Paint(Paint.ANTI_ALIAS_FLAG)
//设置绘制文字画笔颜色
centerText_Paint.color = Color.parseColor("#eeeeee")
//计算文字开始大小
centerText_Paint.textSize = textSize.toFloat()
//设置中心位置
centerText_Paint.textAlign = Paint.Align.CENTER
//设置文字加粗
centerText_Paint.isFakeBoldText = true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
setMeasuredDimension(resolveSize(defult_Widht.toInt(), widthMeasureSpec), resolveSize(defult_Height.toInt(), heightMeasureSpec))
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
//矫正宽度和高度
defult_Widht = w.toFloat()
defult_Height = h.toFloat()
//添加裁剪区域
clipCirclePath.addCircle(defult_Widht / 2, defult_Height / 2, defult_Widht / 2, Path.Direction.CW)
//左右播放的动画
// 防止内存泄漏,记得停止动画,不是本文的重点,不再提供方法
val animator = ObjectAnimator.ofFloat(this, "movelenght", 0F, cycle * cycle_Count)
animator.duration = 4000
animator.repeatCount = -1
animator.interpolator = LinearInterpolator()
animator.start()
}
override fun onDraw(canvas: Canvas) {
//计算控件真实显示的宽高
val realWidht = right - left
val realHeight = bottom - top
//默认缩放比例
var scal = 1.0F
//说明需要缩放,计算缩放绘制
if (realWidht < defult_Widht || realHeight < defult_Height) {
scal = Math.min(realWidht / defult_Widht, realHeight / defult_Height)
canvas.save()
//缩放绘制
canvas.scale(scal, scal)
}
//裁剪
canvas.clipPath(clipCirclePath)
//绘制背景圆形
drawCircle(canvas)
//绘制波形图
drawBez(canvas)
drawText(canvas)
}
//画背景圆
private fun drawCircle(canvas: Canvas) {
//计算圆心和半径
val centerX = defult_Widht / 2
val centerY = defult_Height / 2
//这里的半径指定的是线宽度的中心位置
val radius = (defult_Widht - defult_bg_StrokeWidth) / 2
canvas.drawCircle(centerX, centerY, radius, bg_Paint)
}
//绘制波形图,用贝塞尔曲线,
private fun drawBez(canvas: Canvas) {
mWavePath.reset()
//开始点
var startX = -cycle * cycle_Count + movelenght
var startY = defult_Height + waveHeight / 2 - progress
mWavePath.moveTo(startX, startY)
//计算屏幕外左边所有控制点
for (i in cycle_Count downTo 1) {
//波峰
var fengX = -cycle * (i) + cycle / 4 + movelenght
var fengY = defult_Height - progress
//中间点1
var endX = -cycle * (i) + cycle / 2 + movelenght
var endY = defult_Height + waveHeight / 2 - progress
mWavePath.quadTo(fengX, fengY, endX, endY)
//波谷的点
var guX = -cycle * (i) + cycle * 3 / 4 + movelenght
var guY = defult_Height + waveHeight - progress
//中间点2
var endX2 = -cycle * (i - 1) + movelenght
var endY2 = defult_Height + waveHeight / 2 - progress
mWavePath.quadTo(fengX, fengY, endX, endY)
mWavePath.quadTo(guX, guY, endX2, endY2)
}
//计算屏幕右边所有控制点
for (i in 1..cycle_Count) {
//波峰
var fengX = cycle * (i) - cycle * 3 / 4 + movelenght
var fengY = defult_Height - progress
//中间点1
var endX = cycle * (i) - cycle / 2 + movelenght
var endY = defult_Height + waveHeight / 2 - progress
//波谷的点
var guX = cycle * (i) - cycle * 1 / 4 + movelenght
var guY = defult_Height + waveHeight - progress
//中间点2
var endX2 = cycle * (i) + movelenght
var endY2 = defult_Height + waveHeight / 2 - progress
mWavePath.quadTo(fengX, fengY, endX, endY)
mWavePath.quadTo(guX, guY, endX2, endY2)
}
mWavePath.lineTo(cycle_Count * cycle, defult_Height + waveHeight)
mWavePath.lineTo(-cycle_Count * cycle, defult_Height + waveHeight)
//以上吧点添加完毕,然后就是上移一下看看效果
canvas.drawPath(mWavePath, wave_Paint)
}
//要想绘制文字
private fun drawText(canvas: Canvas) {
//要想绘制文字,就需要计算文字的开始位置
val startX = defult_Widht / 2
val fontMetrics = centerText_Paint.fontMetrics
val baseLine = defult_Height / 2 + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom
canvas.drawText("${(progress / (defult_Height + waveHeight) * 100).toInt()}%", startX, baseLine, centerText_Paint)
}
/**
* 设置进度,然后进行动画播放
*/
fun setProgress(tagretProgress: Int, duration: Long = 5000) {
val animator2 = ObjectAnimator.ofFloat(this, "progress", 0F, (defult_Height + waveHeight) * tagretProgress / 100)
animator2.duration = duration
animator2.interpolator = LinearInterpolator()
animator2.start()
}
}