版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mengks1987/article/details/84135481
按照设置的百分比数组,设置的百分比数组之和要等于1,如果大于1会出现覆盖的情况。
这个效果里面有2个关注点:
1、通过百分比数组绘制多大的环。
2、切换比例的时候动画效果。
3、正在变化时接收到百分比变化的处理
1、通过百分比数组绘制多大的环
圆环一圈是360度,如果百分比是0.2,那么圆环就绘制360 * 0.2 = 72,起始位置是在前面进度的结尾处。代码如下:
var startAngle = -90F
for (i in 0 until mPercentages.size) {
if (i > mPercentageColors.size - 1) {
//如果没有为每一段百分比设置颜色值,则使用最后一种颜色值
mPaint.color = mPercentageColors[mPercentageColors.size - 1]
} else {
mPaint.color = mPercentageColors[i]
}
val sweepAngle = mPercentages[i] * 360
canvas?.drawArc(mArcRectF, startAngle, sweepAngle, false, mPaint)
startAngle += sweepAngle
}
2、切换比例的时候动画效果
1、补全数组
2个不同的百分比数组有可能长度不同,我们要较短那个后面补上0
/**
* 补全数组,使2个数组的长度相等
* fromValues : [0.1,0.2,0.4,0.3]
* toValues: [0.1,0.2,0.7]
*
* 将toValues变为:[0.1,0.2,0.7,0]
*/
private fun completionArrays(fromValues: Array<Float>, toValues: Array<Float>): Values {
return when {
fromValues.size == toValues.size -> Values(fromValues, toValues)
fromValues.size > toValues.size -> {
//补全toValues
var newValues: Array<Float> = Array(fromValues.size) { 0F }
for (i in 0 until toValues.size) {
newValues[i] = toValues[i]
}
Values(fromValues, newValues)
}
else -> {
//fromValues
var newValues: Array<Float> = Array(toValues.size) { 0F }
for (i in 0 until fromValues.size) {
newValues[i] = fromValues[i]
}
Values(newValues, toValues)
}
}
}
Values是一个内部类:
data class Values(val fromValues: Array<Float>, val toValues: Array<Float>)
2、开启一个动画,根据动画的进度计算当前百分比
/**
* 启动动画
*
*/
private fun startAnimator(fromValues: Array<Float>, toValues: Array<Float>) {
isChange = true
val (fv, tv) = completionArrays(fromValues, toValues)
val animator = ValueAnimator.ofFloat(0F, 1F)
animator.duration = mAnimDuration
animator.addUpdateListener {
computePercentages(it.animatedValue as Float, fv, tv)
postInvalidate()
if (it.animatedValue as Float == 1F) {
//动画结束
isChange = false
// val list = mPercentages.filter { it > 0 }
// mPercentages = Array(list.size) { i -> list[i] }
mNextToValues?.let { startAnimator(mPercentages, mNextToValues!!) }
mNextToValues = null
}
}
animator.start()
}
/**
* 计算当前动画百分比的值
*/
private fun computePercentages(animatedValue: Float, fromValues: Array<Float>, toValues: Array<Float>) {
var newValues = Array(fromValues.size) { 0F }
for (i in 0 until fromValues.size) {
newValues[i] = fromValues[i] - (fromValues[i] - toValues[i]) * animatedValue
}
mPercentages = newValues
}
根据当前的进度实时绘制饼状图。
3、正在变化时接收到百分比变化的处理
如果当前正在变化,又接收到百分比变化,则记录此次变化,等上个动画结束时马上再次启动动画。
整体code:
PieChart.kt:
class PieChart @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : View(context, attrs, defStyleAttr, defStyleRes) {
/**
* 百分比数组,数组和要=1
*/
private var mPercentages: Array<Float> = arrayOf(1.0F)
/**
* 不同百分比的颜色值
*/
private var mPercentageColors: Array<Int> = arrayOf(Color.parseColor("#fff4e0"),
Color.parseColor("#f8b500"),
Color.parseColor("#ff4d4d"),
Color.parseColor("#42d3b7"),
Color.parseColor("#334d5c"))
/**
* 圆环画笔
*/
private var mPaint: Paint = Paint()
/**
* 圆环的大小
*/
private var mArcRectF: RectF = RectF()
/**
* 圆环宽度
*/
private var mStrokeWidth: Float = 20F
/**
* 圆环变化时是否使用动画
*/
private var mUseAnimation: Boolean = true
/**
* 动画时长,单位ms
*/
private var mAnimDuration: Long = 500L
/**
* 圆环正在改变时又有新的数据来记录下来,等圆环改变结束在刷新新的数据
*/
private var mNextToValues: Array<Float>? = null
private var isChange = false
init {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.PercentageRing, defStyleAttr, 0)
mStrokeWidth = a.getDimension(R.styleable.PercentageRing_stroke_width, 10F)
//设置圆环画笔
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = mStrokeWidth
mPaint.isAntiAlias = true
}
override fun onDraw(canvas: Canvas?) {
drawRing(canvas)
}
/**
* 绘制圆环
*
* @param canvas 画布
*/
private fun drawRing(canvas: Canvas?) {
var ringWidth = width
var height = height
mArcRectF.left = mStrokeWidth / 2
mArcRectF.right = width - mStrokeWidth / 2
mArcRectF.top = mStrokeWidth / 2
mArcRectF.bottom = height - mStrokeWidth / 2
var startAngle = -90F
for (i in 0 until mPercentages.size) {
if (i > mPercentageColors.size - 1) {
//如果没有为每一段百分比设置颜色值,则使用最后一种颜色值
mPaint.color = mPercentageColors[mPercentageColors.size - 1]
} else {
mPaint.color = mPercentageColors[i]
}
val sweepAngle = mPercentages[i] * 360
canvas?.drawArc(mArcRectF, startAngle, sweepAngle, false, mPaint)
startAngle += sweepAngle
}
}
/**
* 设置百分比数据和中间显示文案
*
* @param percentages 百分比
*/
fun setPercentages(percentages: Array<Float>) {
if (mUseAnimation) {
if (isChange) {
mNextToValues = percentages
return
}
startAnimator(mPercentages, percentages)
} else {
mPercentages = percentages
postInvalidate()
}
}
fun setPercentages(percentages: Array<Float>, colors: Array<Int>) {
mPercentages = percentages
setColors(colors)
postInvalidate()
}
/**
* 设置圆环颜色
*
* @param colors 颜色RGB值
*/
fun setColors(colors: Array<Int>) {
mPercentageColors = colors
}
/**
* 启动动画
*
*/
private fun startAnimator(fromValues: Array<Float>, toValues: Array<Float>) {
isChange = true
val (fv, tv) = completionArrays(fromValues, toValues)
val animator = ValueAnimator.ofFloat(0F, 1F)
animator.duration = mAnimDuration
animator.addUpdateListener {
computePercentages(it.animatedValue as Float, fv, tv)
postInvalidate()
if (it.animatedValue as Float == 1F) {
//动画结束
isChange = false
// val list = mPercentages.filter { it > 0 }
// mPercentages = Array(list.size) { i -> list[i] }
mNextToValues?.let { startAnimator(mPercentages, mNextToValues!!) }
mNextToValues = null
}
}
animator.start()
}
/**
* 补全数组,使2个数组的长度相等
* fromValues : [0.1,0.2,0.4,0.3]
* toValues: [0.1,0.2,0.7]
*
* 将toValues变为:[0.1,0.2,0.7,0]
*/
private fun completionArrays(fromValues: Array<Float>, toValues: Array<Float>): Values {
return when {
fromValues.size == toValues.size -> Values(fromValues, toValues)
fromValues.size > toValues.size -> {
//补全toValues
var newValues: Array<Float> = Array(fromValues.size) { 0F }
for (i in 0 until toValues.size) {
newValues[i] = toValues[i]
}
Values(fromValues, newValues)
}
else -> {
//fromValues
var newValues: Array<Float> = Array(toValues.size) { 0F }
for (i in 0 until fromValues.size) {
newValues[i] = fromValues[i]
}
Values(newValues, toValues)
}
}
}
/**
* 计算当前动画百分比的值
*/
private fun computePercentages(animatedValue: Float, fromValues: Array<Float>, toValues: Array<Float>) {
var newValues = Array(fromValues.size) { 0F }
for (i in 0 until fromValues.size) {
newValues[i] = fromValues[i] - (fromValues[i] - toValues[i]) * animatedValue
}
mPercentages = newValues
}
data class Values(val fromValues: Array<Float>, val toValues: Array<Float>)
companion object {
const val TAG = "PieChart"
}
}
自定义属性 attr.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PieChart">
<attr name="stroke_width" format="dimension" />
</declare-styleable>
</resources>
使用及测试 TestActivity.kt :
class TestActivity : AppCompatActivity() {
val handler = object : Handler() {
override fun handleMessage(msg: Message?) {
pieChart.setPercentages(list[index++ % list.size])
sendEmptyMessageDelayed(0, 1000)
}
}
var list = arrayListOf<Array<Float>>(
arrayOf(0.1F, 0.2F, 0.3F, 0.4F),
arrayOf(0.2F, 0.3F, 0.2F, 0.3F),
arrayOf(0.5F, 0.5F),
arrayOf(0.1F, 0.2F, 0.3F, 0.4F),
arrayOf(0.3F, 0.5F, 0.2F),
arrayOf(0.1F, 0.8F, 0.1F)
)
var index = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
handler.sendEmptyMessage(0)
}
}
布局文件 activity_test.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PieChart
android:id="@+id/pieChart"
android:layout_width="300dp"
android:layout_height="300dp"
app:stroke_width="@dimen/dp_11"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>