绘图之五角星

时间:2024-04-05 09:07:47

本系列为记录学习自定义控件的文章,尽管Google为我们提供了各式各样的的原生控件,但是大多数时候,原生控件并不能满足项目的需求,这时候就需要自定义控件了。
那么,
自定义控件是什么?怎么写?怎么用?
懵逼三连!!!
没错,你是否正处于这样尴尬的局面,是否一提到自定义控件就望而却步,没关系,下面跟我一起走进自定义控件的奇妙世界。

注:本文中出现的所有图片均来自网络,如有侵权,请联系我删除,谢谢。

绘图基础

Android当中的绘图和我们平时画图其实是一样的,无论在精美的图画,都是通过两样工具画出来的,画笔和画布,那么在Android中画笔和画布又是什么呢?

Paint(画笔)

具有设置颜色、填充样式、大小等特性。

Canvas(画布)

具有旋转、位移、缩放等特性。

其实这两个工具,具有很多特性,这里就不一一赘述了。回到主题,使用绘图工具画一个五角星,要实现这样的自定义控件,就不得不提到Android当中的坐标系,因为自定义控件很多时候都涉及到坐标的计算,角度的计算,已经弧度的计算。
要知道,Android坐标系和数学坐标系是有区别的,如图所示:

数学坐标系

绘图之五角星

Android坐标系

绘图之五角星
在解释图2、3之前,先来了解下Android中的坐标原点,见图4、5:
绘图之五角星
绘图之五角星
在Android中,屏幕左上角为坐标原点,既如图4所示,以原点垂直向右为X轴正方向,垂直向下Y轴正方向,并且Android中的角度坐标系和数学的角度坐标系也不一样,以原点竖直向下为90°,竖直向上为270°,如图5所示。讲到这里,想必图2、3应该就很容易理解了。
既然说到Android的角度坐标系,不妨说下Android中的正负角度,这对本文主题至关重要,见图1:
绘图之五角星
先假设红点为A,则∠p1OA=-30°,∠AOp2=30°,可能很奇怪,为什么∠p1OA为负?
前面已经讲到,Android角度坐标系同样区别与数学角度坐标系,想必聪明的你已经发现了区别在哪里。
在Android中,以OA为轴,顺时针为角度增大方向,逆时针为角度负增大方向。

与角度对应的还有一个弧度的概念,简单来说角度和弧度都是为了描述一个角的大小,但是两者也有区别,角度是60进制,弧度是10进制,并且定义也不相同:

角度: 两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆周长的360分之一时,两条射线的夹角的大小为1度.

弧度: 两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度.

如图6、7所示:
绘图之五角星
言归正传,回到本文,画一个五角星,先来看下效果图,如图9所示:

绘图之五角星

分析

五角星其实就是由10的点连接组成,关键是怎么算出这10的点的坐标,仔细想一想,把一个圆平均分成五份,以圆心为起点,画五条直线把圆分成均等五份,五条直线与圆周的交点假设为A、B、C、D、E(逆时针方向),然后按照A-D-B-E-C-A,连接起来,是不是就构成一个互相交错的五角星?

画圆

画圆很简单,先计算圆心坐标以及半径,这里就不再赘述,直接看代码:

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        radius = Math.min(w, h) / 2 * 0.9f;
        secondRadius = radius / 2;
        centerX = w / 2;
        centerY = h / 2;
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, mPaint);
    }

外五点坐标 A-E

如果你不熟悉三角函数,请自行了解。既然提到三角函数,请注意,在Android中,Math.cos()函数中,这里的参数是弧度而不是角度。

角度与弧度的换算公式:

弧度 = 角度 * Math.PI /180°
角度 = 弧度 * 180° / Math.PI

同样在Android中可以使用Math函数来计算:

角度: Math.toDegrees()
弧度:Math.toRadians()

A点:

A点,相对于其他几点是最简单的一个,因为A点并不涉及到弧度以及三角函数的计算。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        Path path = new Path();
        path.moveTo(centerX,centerY);
        //A
        path.lineTo(centerX, centerY - radius);
        canvas.drawPath(path,mPaint);
    }

如图10所示:
绘图之五角星

E点:

为了方便讲解,先见图11所示:
绘图之五角星
圆中间有两条直线,顺时针方向,第一条为OA,第二条为OE,把圆平均分成五份,则每两条相邻直线所形成的角度=360/5=72°,那么OE与X轴所形成的角度同样通过计算可知90-72=18°,别忘了,在Android中,逆时针方向为负,则∠EOX = -18°,∠EOX所对应的弧长=Math.toRadians(-18),弧长得到,通过三角函数便可得到E点的坐标,最后比忘了加上圆心的X、Y坐标。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        Path path = new Path();
        path.moveTo(centerX,centerY);
        //A
        path.lineTo(centerX, centerY - radius);
        //E
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        canvas.drawPath(path,mPaint);
    }

D点:

同理可知,∠EOD=72°,∠EOX = 18°,则∠XOD = 72-18 = 54°,既∠XOD所对应的弧长为Math.toRadians(54)

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        Path path = new Path();
        path.moveTo(centerX,centerY);
        //A
        path.lineTo(centerX, centerY - radius);
        //E
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        //D
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        canvas.drawPath(path,mPaint);
    }

C、B点:

同理,通过计算可知C点角度=126°,B点角度=198°

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        Path path = new Path();
        path.moveTo(centerX,centerY);
        //A
        path.lineTo(centerX, centerY - radius);
        //E
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        //D
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        //C
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(126)) * radius, centerY + (float) Math.sin(Math.toRadians(126)) * radius);

        //B
        path.lineTo(centerX,centerY);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(180 + 18)) * radius, centerY + (float) Math.sin(Math.toRadians(180 + 18)) * radius);
        canvas.drawPath(path,mPaint);
    }

A-E坐标已经确定完毕,先来看下效果图,如图12所示:
绘图之五角星
这样已经把圆均分为5等份,但是这好像并没有满足我们的需求,没关系,A-E(逆时针方向)五个点的坐标已经计算出来,接下来就按照之前规定好的顺序连接起来便可,A-D-B-E-C-A

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(centerX, centerY, radius, mPaint);

        Path path = new Path();
        path.moveTo(centerX, centerY - radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(180 + 18)) * radius, centerY + (float) Math.sin(Math.toRadians(180 + 18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(126)) * radius, centerY + (float) Math.sin(Math.toRadians(126)) * radius);
        path.close();
        canvas.drawPath(path,mPaint);
    }

如图13所示:
绘图之五角星
可以看到,一个互相连接的五角星已经形成,但是看上去好像和效果图有一些不太一样,别急,前文提到,要实现一个效果图中的五角星,其实就是由10个点连接而成,现在完成只是最外边的5个点而已。

仔细回想一下,外边的5个点,是把一个圆均分为五份,然后取边上的5个点,那么里边的5点,是不是可以以相同的方法获取,圆心相同,半径为外圆的一半,然后再均分为五份,两者唯一的区别就是角度和弧度不同。

内五点坐标 F、G、H、I、K(顺时针方向)

先画一个同心圆

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        //外圆
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        //内圆
        canvas.drawCircle(centerX, centerY, secondRadius, leftPaint);
        Path path = new Path();
        path.moveTo(centerX, centerY - radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(180 + 18)) * radius, centerY + (float) Math.sin(Math.toRadians(180 + 18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(126)) * radius, centerY + (float) Math.sin(Math.toRadians(126)) * radius);
        path.close();
        canvas.drawPath(path, mPaint);
    }

为了便于理解,连接五角星同样保留,如图14所示:
绘图之五角星
先忽略红色的外圆与连接的五角星,只看蓝色的内圆,前面讲到只需要在内圆上同样找到降内圆均分五份的内点,在与外圆上的五点依次连接便可完成开篇讲到的五角星,其实找点的方法已经讲过了,内圆与外圆的区别只是角度不同而已。为了便于讲解,见图8所示:
绘图之五角星

F点

逆时针方向A-E为外五点,顺时针方向F-K为内五点,十条直线两条相邻直线所形成的角度=360/10=36°,直线圆心到F(简称F直线,下文皆使用简称)与X轴所形成的角度为=36+18=-54°,切记在Android中逆时针方向为负。通过外五点的计算很容易得到F点坐标的计算方法。

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        //外圆
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        //内圆
        canvas.drawCircle(centerX, centerY, secondRadius, leftPaint);
        Path path = new Path();
        path.moveTo(centerX, centerY - radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(180 + 18)) * radius, centerY + (float) Math.sin(Math.toRadians(180 + 18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(126)) * radius, centerY + (float) Math.sin(Math.toRadians(126)) * radius);
        path.close();
        canvas.drawPath(path, mPaint);
        Path path1 = new Path();
        //F
        path1.moveTo(centerX, centerY);
        path1.lineTo(centerX + (float) Math.cos(Math.toRadians(-(72 / 2 + 18))) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(-(72 / 2 + 18))) * secondRadius);
        canvas.drawPath(path1,leftPaint);
    }

如图15所示:
绘图之五角星

G、H、I、K点

这四个点和F点计算方法是一样,关于在于角度和弧度的计算,因此不再一一赘述。通过计算可知四点角度分别为:
G:18
H:90
I:36 * 4 + 18
K:36 * 6 + 18

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    
        //外圆
        canvas.drawCircle(centerX, centerY, radius, mPaint);
        //内圆
        canvas.drawCircle(centerX, centerY, secondRadius, leftPaint);
        Path path = new Path();
        path.moveTo(centerX, centerY - radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(180 + 18)) * radius, centerY + (float) Math.sin(Math.toRadians(180 + 18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(126)) * radius, centerY + (float) Math.sin(Math.toRadians(126)) * radius);
        path.close();
        canvas.drawPath(path, mPaint);
        Path path1 = new Path();
        //F
        path1.moveTo(centerX, centerY);
        path1.lineTo(centerX + (float) Math.cos(Math.toRadians(-(72 / 2 + 18))) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(-(72 / 2 + 18))) * secondRadius);
        //G
        path1.lineTo(centerX, centerY);
        path1.lineTo(centerX + (float) Math.cos(Math.toRadians(18)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(18)) * secondRadius);
        //H
        path1.lineTo(centerX, centerY);
        path1.lineTo(centerX + (float) Math.cos(Math.toRadians(90)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(90)) * secondRadius);

        //I
        path1.lineTo(centerX, centerY);
        path1.lineTo(centerX + (float) Math.cos(Math.toRadians(36 * 4 + 18)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(18 + 36 * 4)) * secondRadius);
        //K
        path1.lineTo(centerX, centerY);
        path1.lineTo(centerX + (float) Math.cos(Math.toRadians(36 * 6 + 18)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(18 + 36 * 6)) * secondRadius);
        canvas.drawPath(path1, leftPaint);
    }

如图16所示:
绘图之五角星
图8和图16对比可以看出,红色外圆上的五点逆时针方向分别为A-E,蓝色内圆上的五点顺时针方向分别为F-K,根据开篇得知五角星,就是通过10个点连接而成,现在10个点的坐标已经求出,按照图8所示,只需要按照A-F-E-G-D-H-C-I-B-K-A顺序连接,便可形成一个五角星,光说不做假把式,直接看代码。

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       //把10个点连接起来 A-F-E-G-D-H-C-I-B-K-A
        Path path = new Path();
        path.moveTo(centerX, centerY - radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-(72 / 2 + 18))) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(-(72 / 2 + 18))) * secondRadius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(-18)) * radius, centerY + (float) Math.sin(Math.toRadians(-18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(18)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(18)) * secondRadius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(54)) * radius, centerY + (float) Math.sin(Math.toRadians(54)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(90)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(90)) * secondRadius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(126)) * radius, centerY + (float) Math.sin(Math.toRadians(126)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(36 * 4 + 18)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(18 + 36 * 4)) * secondRadius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(180 + 18)) * radius, centerY + (float) Math.sin(Math.toRadians(180 + 18)) * radius);
        path.lineTo(centerX + (float) Math.cos(Math.toRadians(36 * 6 + 18)) * secondRadius,
                centerY + (float) Math.sin(Math.toRadians(18 + 36 * 6)) * secondRadius);
        path.close();
        canvas.drawPath(path, mPaint);
    }

如图17所示:
绘图之五角星
至于为什么是空心的,只是画笔Paint的样式为描边,改为填充便可实现和开篇一样的效果了,当然还可以实现半边填充,如图18所示:
绘图之五角星

除此之外,还有一个蜘蛛网,只要掌握了计算方法,一样很好实现,放一张效果图,源码在文末。
绘图之五角星
Android自定义控件远不止这些,这只是长征路上的第一步,不过不要气馁,越过眼前的这座山,便可进入自定义控件的美好世界,加油,我与你并肩前行!!!!

五角星

源码:https://github.com/qylfzy/FiveStarDemo

蜘蛛网

源码:https://github.com/qylfzy/SpiderWeb