实战酷毙了的自定义View(二)

1,993 阅读5分钟

Gtihub传送门

目录

  1. 实战酷毙了的自定义View(一)
  2. 实战酷毙了的自定义View(二)
  3. 实战酷毙了的自定义View(三)

Canvas的高级使用

在我的Github开源库中,今天刚写好了一个饼图的绘制,这里主要就是对饼图绘制的一个讲解了。

之前我们讲过了一个Path,但是如果全用Path去实现,也不是说不能够实现,但是并不适用于很多的场景情况。

这里的话,我们主要讲的就是关于Canvas的使用了。之前我们在Path中用到过这样的一个函数moveTo,也就是把路径从当前的位置移动到新的坐标上。那我们的Canvas是否有这样的功能呢?

自然是有的了,这里直接作出一个解答,他的函数就是translate,但是有一点要注意!!!这个函数是一个相对移动,而不是绝对移动。

canvas?.translate(60f, 80f)
paint?.let { canvas?.drawCircle(0f, 0f, 100f, it) }
canvas?.translate(60f, 80f)paint?.color = Color.BLUE
paint?.let { canvas?.drawCircle(0f, 0f, 100f, it) }

上述就是实现代码以及一个贴图了。你可以直观的感受到这个移动其实是基于当前的基础来完成的。但是读者会说了,这种效果完全可以让我通过Path来完成啊,没必要通过Canvas,ok,确实可以,你也可以通过Path来实现。

但是难点现在才来,下图的样式该怎么实现??

在知道怎么写代码之前,那我们要知道肯定是他的组成成分到底是什么了。

  1. 两条线的组合。从直观上就可以看出,这个折线应该是两条直线进行组合
  2. 两行文字的组合。也就是图片中的描述四和6.0%

如果让我们直接去获取绝对坐标,然后进行绘制,那是不是会非常麻烦呢?所以这里要引入一个Canavs的操作方法。

Canvas操作

在使用之前需要注意,画布是需要保存的,不然画布将不断的保留上一次的状态进行绘制,那整体就会呈现一种叠加混乱的局面。而这个方法就是save()restore()的成对使用。

for (i in 0..2) {
        canvas?.save()
        paint?.color = Color.BLUE
        canvas?.translate(60f, 80f)
        canvas?.scale(0.5f, 0.5f)
        paint?.let { canvas?.drawCircle(0f, 0f, 100f, it) }
        canvas?.restore()
    }
使用前 使用后

你可以把这个操作理解为画布归位,或者说画布重置。而这个重置对应的就是保存了save()之前的操作过程。

Scale

这个效果不再赘述了。

Rotate

和其他的都是一样的会有两种画布的操作函数

  1. rotate(float degrees, float px, float py),就是自定义旋转圆心
  2. rotate(float degrees),画布当前位置作为旋转圆心
canvas?.save()
paint?.color = Color.BLUE
canvas?.translate(60f, 80f)
canvas?.rotate(45f)
// 以自身中心作为圆点旋转
// canvas?.rotate(45f,30f, 40f)
paint?.let { canvas?.drawRect(0f, 0f, 100f, 100f, it) }
canvas?.restore()

饼图的实现其实就是基于这个函数来完成的。

Shew

和其他的不同了,他并不再通过我们的坐标圆心,而是对标我们XY

canvas?.save()
paint?.color = Color.BLUE
canvas?.translate(60f, 80f)
canvas?.skew(0f, 1f)
paint?.let { canvas?.drawRect(0f, 0f, 100f, 100f, it) }
canvas?.restore()

传入的数据其实就是分别与XY轴的正切值,通过截图工具,你能够明显的发现这个问题。画了绿色框框的部分,你可以看到的是正好一个45度的大小。

通过画图你也可以这样理解,就是Y轴顺时针方向旋转45度。

饼图绘制的基本思路

上面讲过了画布的一些基本操作,那我们要具体讲一下这个饼图我们该怎么绘制了。讲实话,我代码里已经基本注释了,所以具体的以代码为准,这里只做简单的讲解。几个考虑部分:

  1. 圆和文字怎么兼容,这是一个重难点,我也只能说通过多次尝试,到圆为0.3倍的宽度最为合适了。因为0.5倍,正好占满;0.4倍,文字太小。这个值你可以自行做一个修改处理。
  2. 上面说过的介绍线怎么画。文字的的布局肯定直接根据介绍线来变动了,那这个介绍线就是至关重要的一点了。
  3. 弧形的颜色怎么变换。

基本就是以上问题,问题1我已经做出来回答,后续两个问题将一个个作出解释。

介绍线怎么画

在我的代码中,其实分为两块,一是圆弧,一是介绍,这也是他们的绘制顺序了。但是需要考虑一个问题:介绍线的位置确定?

这个问题你需要看一下我的图例了,你有没有注意到,我的介绍线,好像都是关于单个圆弧居中的呢?

那我们的方案其实就来了,上面我们讲到过了什么?Rotate还记得这个函数嘛!!通过数据运算,有了每个圆弧的大小,那我们还不能去进行绘制嘛?不就是先划线,再旋转吗。然后旋转就是这样的一个公式要去计算。

// 旋转角度 = 前面的弧度 + 当前弧度的一半
private fun getRatioSum(j: Int): Float {
        var sum = 0f
        for (i in 0 until j) {
            sum += mRatios!![i]
        }
        return sum
    }

    private fun getRatioHalfSumDegrees(j: Int): Float {
        var sum = getRatioSum(j)
        sum += mRatios!![j] / 2
        return sum * 360
    }

弧形的颜色怎么变换

跟前面的圆弧一样,但是是一个个圆弧来组成圆,那这个时候,就是要知道前面的弧度,然后对当前的弧度的计算,也就是扫过的区间进行计算了。

private fun drawArc(canvas: Canvas) {
        val drawArc = 360 * scale
        for (i in mRatios!!.indices) {
            mArcPaint?.color = mArcColors!![i]
            mArcPaint?.let {
                canvas.drawArc(
                    arcRect!!,
                    getRatioSum(i) * drawArc,
                    mRatios!![i] * drawArc,
                    true,
                    it
                )
            }
        }
    }

基本上就是靠上述的两个来完成的,是不是还是比较简单的呢。冲冲冲,Demo抄起来,你就有一份自己的饼图绘制了。