手把手教你实现一个炫酷的环形进度条。
支持纯色/渐变,可选线条样式,动画时长。
可实现扇形进度条、多个进度叠加、最小/最大进度等。
先上个效果图: Demo点我
实现思路
环形进度条,本质就是根据角度、半径等条件实现画圆的过程
- 创建一个类继承View,并实现几个构造方法
- 定义样式属性,获取属性值
- 创建画笔,设置线条等样式
- 在onDraw方法中进行绘制
-
定义的属性样式
<declare-styleable name="RingProgressView"> <!--前景环形是否使用渐变--> <attr format="boolean" name="f_useGradient"/> <!--前景环形渐变起始颜色--> <attr format="color" name="f_startColor"/> <!--前景环形渐变中间颜色--> <attr format="color" name="f_centerColor"/> <!--前景环形渐变结束颜色--> <attr format="color" name="f_endColor"/> <!--前景环形颜色--> <attr format="color" name="f_ringColor"/> <!--背景环形颜色--> <attr format="color" name="f_ringBgColor"/> <!--前景环形起始角度--> <attr format="float" name="f_startAngle"/> <!--前景环形结束角度--> <attr format="float" name="f_endAngle"/> <!--背景环形起始角度--> <attr format="float" name="f_startBgAngle"/> <!--背景环形结束角度--> <attr format="float" name="f_endBgAngle"/> <!--环形线宽--> <attr format="dimension" name="f_roundWidth"/> <!--进度变化动画时长--> <attr format="integer" name="f_duration"/> <!--前景环形开始进度--> <attr format="float" name="f_startProgress"/> <!--前景环形结束进度--> <attr format="float" name="f_endProgress"/> <!--前景环形目标进度(你想设置的)--> <attr format="float" name="f_progress"/> <!--环形线帽样式Paint.Cap--> <attr format="enum" name="f_strokeCap"> <!--默认没有--> <enum name="butt" value="0"/> <!--圆角--> <enum name="round" value="1"/> <!--直角--> <enum name="square" value="2"/> </attr> <!--环形描边样式,详见Paint.Join--> <attr format="enum" name="f_strokeJoin"> <enum name="miter" value="0"/> <enum name="round" value="1"/> <enum name="bevel" value="2"/> </attr> </declare-styleable>
-
定义的属性,及初始化
xml属性的读取,变量含义可参考上面的属性定义
//属性配置 val ta = context.obtainStyledAttributes(attrs, R.styleable.RingProgressView) ringColor = ta.getColor(R.styleable.RingProgressView_f_ringColor, ringColor) ringBgColor = ta.getColor(R.styleable.RingProgressView_f_ringBgColor, ringBgColor) startColor = ta.getColor(R.styleable.RingProgressView_f_startColor, startColor) centerColor = ta.getColor(R.styleable.RingProgressView_f_centerColor, centerColor) endColor = ta.getColor(R.styleable.RingProgressView_f_endColor, endColor) //渐变色设置检查 if (startColor != Color.TRANSPARENT || startColor != Color.TRANSPARENT || startColor != Color.TRANSPARENT) { useGradient = true } useGradient = ta.getBoolean(R.styleable.RingProgressView_f_useGradient, useGradient) startAngle = ta.getFloat(R.styleable.RingProgressView_f_startAngle, startAngle) endAngle = ta.getFloat(R.styleable.RingProgressView_f_endAngle, endAngle) startBgAngle = ta.getFloat(R.styleable.RingProgressView_f_startBgAngle, startBgAngle) endBgAngle = ta.getFloat(R.styleable.RingProgressView_f_endBgAngle, endBgAngle) roundWidth = ta.getDimensionPixelSize(R.styleable.RingProgressView_f_roundWidth, roundWidth) strokeCap = when (ta.getInteger(R.styleable.RingProgressView_f_strokeCap, 0)) { 0 -> Paint.Cap.BUTT 1 -> Paint.Cap.ROUND else -> Paint.Cap.SQUARE } strokeJoin = when (ta.getInteger(R.styleable.RingProgressView_f_strokeJoin, 0)) { 0 -> Paint.Join.MITER 1 -> Paint.Join.ROUND else -> Paint.Join.BEVEL } duration = ta.getInt(R.styleable.RingProgressView_f_duration, duration.toInt()).toLong() startProgress = ta.getFloat(R.styleable.RingProgressView_f_startProgress, startProgress) endProgress = ta.getFloat(R.styleable.RingProgressView_f_endProgress, endProgress) progress = ta.getFloat(R.styleable.RingProgressView_f_progress, progress) tempProgress = progress ta.recycle()
-
动画插值器及画笔
这里使用的是线性插值器(LinearInterpolator),插值器是控制动画过程的关键,也有其他可代替的方案有Scroller、ValueAnimator等
有关插值器等了解看文章最后的参考部分
init { va.interpolator = LinearInterpolator() va.setFloatValues(startProgress, progress) va.duration = duration va.addUpdateListener { tempProgress = it.animatedValue as Float postInvalidate() } va.start() } /** * 初始化参数 */ private fun init() { //设置画笔参数 bgPaint.strokeWidth = roundWidth.toFloat() bgPaint.strokeCap = strokeCap bgPaint.strokeJoin = Paint.Join.ROUND bgPaint.style = Paint.Style.STROKE bgPaint.isAntiAlias = true bgPaint.color = ringBgColor forePaint.strokeWidth = roundWidth.toFloat() forePaint.strokeCap = strokeCap forePaint.strokeJoin = Paint.Join.ROUND forePaint.style = Paint.Style.STROKE forePaint.isAntiAlias = true //着色器颜色筛选 val filter = intArrayOf(startColor, centerColor, endColor).filter { it != Color.TRANSPARENT } val colorArray:IntArray = when{ filter.isEmpty()-> intArrayOf(startColor, centerColor, endColor) filter.size>1-> filter.toIntArray() else->{ intArrayOf(filter.first(),filter.first()) } } //着色器/颜色设置 if (useGradient) { forePaint.shader = LinearGradient( 0f, 0f, width.toFloat(), height.toFloat(), colorArray, null, Shader.TileMode.CLAMP ) } else { forePaint.shader = null forePaint.color = ringColor } //矩形 rectF .set( 0f + roundWidth / 2, 0f + roundWidth / 2, width.toFloat() - roundWidth / 2, height.toFloat() - roundWidth / 2 ) if (init) { postInvalidate() } }
-
绘制部分
override fun onDraw(canvas: Canvas) { //画背景环形 canvas.drawArc(rectF, startBgAngle, endBgAngle - startBgAngle, false, bgPaint) //计算前景环形角度 sweepAngle = tempProgress / (endProgress - startProgress) * (endAngle - startAngle) //画前景环形 canvas.drawArc(rectF, startAngle, sweepAngle, false, forePaint) }
总结
- 动画的实现主要依赖于插值器的配合
- 渐变的功能实现在于着色器(Paint.shader及LinearGradient)的应用