自定义View:轨迹与饼图

1,844 阅读3分钟

一、轨迹的画法

轨迹的难点主要在于位置的测量,比如我们想得到下方的仪表盘(黑色部分):

0d315dd6cd17477db51bfaea7434a507_tplv-k3u1fbpfcp-watermark.png

预设常量

val DASH_WIDTH = 2f.dp2px   //刻度的宽
val DASH_LENGTH = 10f.dp2px   //刻度的长
val RAIUS = 100f.dp2px   //圆弧的半径
val LENGTH = 80f.dp2px   //指针的长度
const val OPEN_ANGLE = 120f    //圆弧开口的角

dp2px为Float的扩展属性

/**
 * Float的扩展属性
 */
val Float.dp2px
        get() = TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            this,
            Resources.getSystem().displayMetrics
        )

1、画圆弧

val paint = Paint(Paint.ANTI_ALIAS_FLAG)   //圆弧的画笔
val arcPath = Path()  //圆弧的路径
paint.strokeWidth = 2f  //画笔的宽度
paint.style = Paint.Style.STROKE  //画笔的模式
//设置路径
arcPath.addArc(
    width / 2f - RAIUS,
    height / 2f - RAIUS,
    width / 2f + RAIUS,
    height / 2f + RAIUS,
    90 + OPEN_ANGLE / 2f,
    360 - OPEN_ANGLE
)
//在onDraw方法中画圆弧
canvas.drawPath(arcPath, paint)

效果展示

image.png

画圆弧主要点在于Path路径的设置,通过设置左上右下4个点来定义一个矩形,然后在矩形内画弧。

2、画刻度

画刻度的思路为画虚线,设定好虚线实心段的长宽和间距即可。画虚线的路径与圆弧的路径相同。

设置一根画笔

private val paintEffect = Paint(Paint.ANTI_ALIAS_FLAG)  //画刻度
paintEffect.strokeWidth = 2f     //画笔的二条属性
paintEffect.style = Paint.Style.STROKE

设置二个Path,其中一个是虚线实心段的Path,一个是整个虚线的Path。

private val dashPath = Path()   //虚线实心段的Path
private val arcPath = Path()    //虚线的Path
//虚线实心段的Path
dashPath.addRect(0f, 0f, DASH_WIDTH, DASH_LENGTH, Path.Direction.CCW)
//虚线的Path
arcPath.addArc(
    width / 2f - RAIUS,
    height / 2f - RAIUS,
    width / 2f + RAIUS,
    height / 2f + RAIUS,
    90 + OPEN_ANGLE / 2f,
    360 - OPEN_ANGLE)

这里就会有一个疑问,虚线的间隔应该如何计算,也就是刻度的间隔应该如何计算?

1)计算虚线的Path的长度

//测量Path的长度
val arcPathMessure = PathMeasure(arcPath, false)   //false  path不封口

2)计算虚线的间距

 (arcPathMessure.length - DASH_WIDTH) / 20f   //这里要减掉一个虚线(刻度)的宽度

3)将虚线的属性设置给画笔

paintEffect.pathEffect =
    PathDashPathEffect(
        dashPath,
        (arcPathMessure.length - DASH_WIDTH) / 20f,
        0f,
        PathDashPathEffect.Style.ROTATE
    )

最后,我们完成画刻度的工作

canvas.drawPath(arcPath, paintEffect)

效果展示

image.png

3、画指针

指针的画法需要对三角函数有个基本的概念

image.png

正弦:sin α = y/r
余弦:cos α = x/r
正切:tan α = y/x
正割:sec α = 1/cosα = r/x
余割:csc α = 1/sinα = r/y
余切:cot α = 1/tanα = x/y

计算指针的角度,比如第五个刻度(开头的刻度不算)

90 + OPEN_ANGLE / 2f + (360 - OPEN_ANGLE) / 20f * 5

转化为代码

val radians = Math.toRadians((90 + OPEN_ANGLE / 2f + (360 - OPEN_ANGLE) / 20f * 5).toDouble())

画指针其实是画一条线,指定XY轴上的起点和终点

//画指针
canvas.drawLine(
    width / 2f,
    height / 2f,
    width / 2f + cos(radians).toFloat() * LENGTH,
    height / 2f + sin(radians).toFloat() * LENGTH,
    paint
)

效果展示

image.png 将刻度的代码抽出来

/**
 * 画指针
 * @param pointer 指针上的刻度
 */
private fun drawPointer(canvas: Canvas, pointer: Int) {
    //角度
    val radians =
        Math.toRadians((90 + OPEN_ANGLE / 2f + (360 - OPEN_ANGLE) / 20f * pointer).toDouble())
    //画指针
    canvas.drawLine(
        width / 2f,
        height / 2f,
        width / 2f + cos(radians).toFloat() * LENGTH,
        height / 2f + sin(radians).toFloat() * LENGTH,
        paint
    )
}

4、知识点总结

1)注意实线与虚线的画法,PathDashPathEffect的使用

2)注意Path的测量,PathMessure的使用

3)注意虚线间距的计算,需要减掉一个实心虚线的宽度

4)注意三角函数的使用

二、饼图的画法

预设常量

private val PIE_RAIUS = 100f.dp2px   //饼图的半径
private val ANGLES = floatArrayOf(60f, 90f, 120f, 90f)   //饼图的角度
private val COLORS = listOf<Int>(   //饼图的颜色
    Color.parseColor("#C2185B"),
    Color.parseColor("#00ACC2"),
    Color.parseColor("#558B2D"),
    Color.parseColor("#5D4024")
)
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)   //画饼的画笔

1、画饼图

/**
 * 画饼图
 */
private fun drawPie(canvas: Canvas) {
    var tempAngle = 0f
    for ((index, angle) in ANGLES.withIndex()) {
        paint.color = COLORS[index]
        canvas.drawArc(
            width / 2f - PIE_RAIUS,
            height / 2f - PIE_RAIUS,
            width / 2f + PIE_RAIUS,
            height / 2f + PIE_RAIUS,
            tempAngle,  //起始角
            angle,   //偏移量(这里容易写错,写错成tempAngle+angle)
            true,    //封口
            paint
        )
        tempAngle += angle
    }
}

2、画带偏移量的饼图

/**
 * 画饼图具有偏移量
 * @param translateValue 偏移的量
 * @param num 第几个偏移
 */
private fun drawPieWithTranslate(canvas: Canvas, translateValue: Int,num:Int) {
    var tempAngle = 0f
    for ((index, angle) in ANGLES.withIndex()) {
        paint.color = COLORS[index]
        if (index == num) {   //哪个偏移
            canvas.save()
            var radius = Math.toRadians((tempAngle + angle / 2).toDouble())  //计算角度
            canvas.translate(   //偏移的api,同样涉及三角函数的计算
                cos(radius).toFloat() * translateValue,
                sin(radius).toFloat() * translateValue
            )
        }
        canvas.drawArc(
            width / 2f - PIE_RAIUS,
            height / 2f - PIE_RAIUS,
            width / 2f + PIE_RAIUS,
            height / 2f + PIE_RAIUS,
            tempAngle,
            angle,
            true,
            paint
        )
        tempAngle += angle
        if (index == num) {  //哪个恢复
            canvas.restore()
        }
    }
}

效果图

image.png

3、知识点总结

1)偏移的Api是canvas.translate

2)注意drawArc方法角度的设置

3)注意canvas.save和canvas.restore的使用

--个人学习笔记--