对于自定义控件的意义不言而喻,所以对它的深入研究是很有必要的,前些年写过几篇关于UI效果的学习过程,但是中途比较懒一直就停滞了,而对于实际工作还是面试来说系统深入的了解自定义控件那是很有必要的,所以接下来会不断的去记录自己学习关于自定义控件一些心路历程,重点是要深入剖析其机制的原理。
而从最基础Canvas的绘制开始练习,没有扎实的基础何谈深入呢?而这里最终会实现一个人的笑脸的样子来学一些基础的canvas的用法,毕境它的用法太多太多,不过未来不会只满足于这片使用的,而是会不断的进行丰富,下面先贴一下最终实现的效果:
从上面的笑脸来看是由多个图形绘制而成的:有点、线、圆、孤、曲线,刚好可以用来操练Canvas的基础绘制方面,下面一步步来拆解整个实现过程:
1、画个圆脸:【学习如何用canvas画圆】
首先先搭建简单框架,当然Activity里面不会用到xml布局文件啦,直接用自定义的View,如下:
/** * 自定义View操练----画一个形象可爱的笑脸^_^ */ public class MyView extends View { public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } }
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new MyView(this)); } }
下面开始画圆,首先得初始化画笔:
public class MyView extends View { private Paint paint; public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(240, 400, 200, paint); } }
这里采用480x800的模拟器来运行,由于只是操练Canvas,所以重在API的使用,所以可以看到在画圆是都是写死的值,将圆画在屏幕居中的位置,运行结果如下:
稍等,有木有看出这个圆边缘不平滑,是滴,忘了抗锯齿啦,于是乎加上它:
编译运行看结果:
但是我们需要的是一个空心圆,而如何让它变成空心圆呢?继续设置画笔,如下:
运行:
完美完成第一步,但是目前坐标都是直接用的数字,这时多复制几行感受下:
是不是可读性比较差,这时可以用Android提供的PointF坐标类进行封装一下,比较简单直接用,再来体会下:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); } }
顺间可读性增强,所以以后关于坐标点都会用这种方式,至此第一步完美结束,继续实现第二步。
2、画一字眉:【学习如何用canvas画线】
人的眉毛应该是长在两眼之上的,但是这里比较卡通,直接用一根连线来表示,顺便学习下如何画一根直线,下面开始:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ } }
编译运行:
其中如何使用Canvas中的api已经在代码注释中详细描述了,很容易理解,两点连一线,那在做测试时如何能确定坐标位置呢?难道一次次运行之后再根据看到的结果偏差再不断调整到满意位置,显示是太LOW的方式,我是这样做滴,首先将屏幕截图导到硬盘上然后用PS打开:
由于我们的View是只有这个区域:
所以在View中绘的坐标都得以这个区域[0,0]为准,而不是带标题和软件盘的那个位置,所以用PS将图片多余的部份裁剪掉只剩图中红框的地方:
然后调出信息窗口【方法:窗口菜单-“信息”】就可以实时的看到当前鼠标停在图片上的坐标了,用鼠标停在想要画到的坐标上,如下:
可以看到线的开始坐标点就是我们在代码指定的(140,250),所以有了这个方法下面步骤中的坐标就可以按上面的方法来精准定位啦【PS去确定坐标点貌似有点杀鸡用牛刀的感觉,由于MAC上木有像Windows中的系统画图软件能够知道当前鼠标的坐标点,所以就笨笨的用它啦~】
3、画勾鼻:【学习如何用canvas画线】
依然是画两个直直的线来画『阴勾鼻』,比较简单,直接上代码及看运行效果:
/** * 自定义View操练----画一个形象可爱的笑脸^_^ */ public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 } }
编译运行:
哈哈,看起来还有模有样滴,下面继续
4、画两眼:【学习如何用canvas画圆】
眼睛是心灵的窗户,所以赶紧加上它,当然就是画两个正圆喽,也比较简单, 直接看代码:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); /* 左眼圆的坐标 */ private final PointF leftEyePoint = new PointF(170, 330); /* 右眼圆的坐标 */ private final PointF rightEyePoint = new PointF(310, 330); /* 眼睛圆的半径 */ private final int eyeRadius = 60; public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 //4、画两眼: canvas.drawCircle(leftEyePoint.x, leftEyePoint.y, eyeRadius, paint);//画左眼睛,用萌萌哒的正圆来形象的代表~~ canvas.drawCircle(rightEyePoint.x, rightEyePoint.y, eyeRadius, paint);//画右眼睛,用萌萌哒的正圆来形象的代表~~ } }
运行如下:
嗯,不错!下面继续完善:
5、画嘴:【学习如何用canvas画圆弧】
嘴是一个弧型,如何去画一个弧呢?下面看代码,具体使用在代码上有详细注释:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); /* 左眼圆的坐标 */ private final PointF leftEyePoint = new PointF(170, 330); /* 右眼圆的坐标 */ private final PointF rightEyePoint = new PointF(310, 330); /* 眼睛圆的半径 */ private final int eyeRadius = 60; /* 嘴的圆弧外接矩形 */ //参数为left(距屏幕左边的x坐标位置)、top(距屏幕左边的x坐标位置)、right(距屏幕左边的x坐标位置)、bottom(距屏幕左边的x坐标位置) private final RectF mouseRectF = new RectF(60, 300, 420, 550); /* 嘴的圆弧起始角度 */ private final float startAngle = 380f; /* 嘴的圆弧的弧度 */ private final float sweepAngle = 140f; public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 //4、画两眼: canvas.drawCircle(leftEyePoint.x, leftEyePoint.y, eyeRadius, paint);//画左眼睛,用萌萌哒的正圆来形象的代表~~ canvas.drawCircle(rightEyePoint.x, rightEyePoint.y, eyeRadius, paint);//画右眼睛,用萌萌哒的正圆来形象的代表~~ //5、画嘴: //参数1 RectF:矩形,是圆弧所在圆的外接矩形 //参数2 startAngle:弧的起始角度 //参数3 sweepAngle:弧的弧度 //参数4 useCenter:? canvas.drawArc(mouseRectF, startAngle, sweepAngle, false, paint); } }
看看效果:
对于这个画弧API还有些难理解,下面进一步说明一下:
这时将这个参数改为true,再看下效果:
另外对于第一个参数表示圆弧所在圆的外接矩形,如何来论证呢?下面可以稍加修改下代码来验证它:
再来看下,就比较好理解各个参数了:
具体画弧这个api还得实际当中多多使用才能熟练掌握,继续绘制下一个元素。
【注意】:特别是要注意一下RectF这个矩形类的参数意义,尤其是right、bottom这两个参数,很容易理解反,切记切记!!
6、画美丽青春痘:【学习如何用canvas画多个点】
绘制一个点这个比如容易,Canvas有直接现成的方法,这里学习下同时绘制多个点的方法,刚好想到了可以绘多来点来代表美丽青春痘【想当初它从初中就一直伴随我了,囧~】,也很好理解,直接看代码:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); /* 左眼圆的坐标 */ private final PointF leftEyePoint = new PointF(170, 330); /* 右眼圆的坐标 */ private final PointF rightEyePoint = new PointF(310, 330); /* 眼睛圆的半径 */ private final int eyeRadius = 60; /* 嘴的圆弧外接矩形 */ //参数为left(距屏幕左边的x坐标位置)、top(距屏幕左边的x坐标位置)、right(距屏幕左边的x坐标位置)、bottom(距屏幕左边的x坐标位置) private final RectF mouseRectF = new RectF(60, 300, 420, 550); /* 嘴的圆弧起始角度 */ private final float startAngle = 380f; /* 嘴的圆弧的弧度 */ private final float sweepAngle = 140f; /* 左美丽青春痘 */ private final float[] leftAcnePoints = {130[x], 414[y], 111, 442, 152, 442, 130, 470, 130, 442}; /* 左美丽青春痘 */ private final float[] rightAcnePoints = {350[x], 414[y], 331, 442, 372, 442, 350, 470, 350, 442}; public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 //4、画两眼: canvas.drawCircle(leftEyePoint.x, leftEyePoint.y, eyeRadius, paint);//画左眼睛,用萌萌哒的正圆来形象的代表~~ canvas.drawCircle(rightEyePoint.x, rightEyePoint.y, eyeRadius, paint);//画右眼睛,用萌萌哒的正圆来形象的代表~~ //5、画嘴: //参数1 RectF:矩形,是圆弧所在圆的外接矩形 //参数2 startAngle:弧的起始角度 //参数3 sweepAngle:弧的弧度 //参数4 useCenter: 如果为true则会以圆中心画一个弧;如果为false则只要一个弧线,没有中心点到弧的一个连线 canvas.drawArc(mouseRectF, startAngle, sweepAngle, false, paint); //for test:看一下是不是刚好圆弧就是在外切矩形里面 //canvas.drawRect(mouseRectF, paint); //6、画美丽青春痘: //参数1 pts: 绘制的点的坐标集合,[x0 y0 x1 y1 x2 y2 ...] //参数2 offset: 参数1中的这么一串数字哪个表示X,哪个表示Y,则由此参数决定,也就是指定参数1中哪个点作为起始点作为横坐标,然后依次顺延 //比如:如果传0,则第一个数表示x、第二个数表示y;而如果传1则第二个数是x,第三个数是y,之后的一次顺延 //参数3 count: 需要使用集合中的几个元素 canvas.drawPoints(leftAcnePoints, 0, leftAcnePoints.length, paint);//画左美丽青春痘 canvas.drawPoints(rightAcnePoints, 0, rightAcnePoints.length, paint);//画右美丽青春痘 } }
编译运行:
哈哈哈~~帅呆了~~简直就是当年的自己,哦!!发现木有耳朵~~呃~~下面继续完善。
7、画耳朵:【采用多种方法实现,从易到难】
- 方案一:利用矩形粗略的表达萌萌滴【学习如何用canvas画矩形】
先实现一个比较木讷滴耳朵,用简单的矩形来代替,因为刚好可以学习如何绘制矩形,由于上面在画嘴时已经接触到了如何绘制一个矩形,所以下面直接看下代码:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); /* 左眼圆的坐标 */ private final PointF leftEyePoint = new PointF(170, 330); /* 右眼圆的坐标 */ private final PointF rightEyePoint = new PointF(310, 330); /* 眼睛圆的半径 */ private final int eyeRadius = 60; /* 嘴的圆弧外接矩形 */ //参数为left(距屏幕左边的x坐标位置)、top(距屏幕左边的x坐标位置)、right(距屏幕左边的x坐标位置)、bottom(距屏幕左边的x坐标位置) private final RectF mouseRectF = new RectF(60, 300, 420, 550); /* 嘴的圆弧起始角度 */ private final float startAngle = 380f; /* 嘴的圆弧的弧度 */ private final float sweepAngle = 140f; /* 左美丽青春痘 */ private final float[] leftAcnePoints = {130, 414, 111, 442, 152, 442, 130, 470, 130, 442}; /* 左美丽青春痘 */ private final float[] rightAcnePoints = {350, 414, 331, 442, 372, 442, 350, 470, 350, 442}; /* 左朵耳,这里用一个矩形来表示,萌萌哒 */ private final RectF leftEarRectF = new RectF(30, 300, 80, 500); /* 右朵耳,这里用一个矩形来表示,萌萌哒 */ private final RectF rightEarRectF = new RectF(400, 300, 450, 500); public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 //4、画两眼: canvas.drawCircle(leftEyePoint.x, leftEyePoint.y, eyeRadius, paint);//画左眼睛,用萌萌哒的正圆来形象的代表~~ canvas.drawCircle(rightEyePoint.x, rightEyePoint.y, eyeRadius, paint);//画右眼睛,用萌萌哒的正圆来形象的代表~~ //5、画嘴: //参数1 RectF:矩形,是圆弧所在圆的外接矩形 //参数2 startAngle:弧的起始角度 //参数3 sweepAngle:弧的弧度 //参数4 useCenter: 如果为true则会以圆中心画一个弧;如果为false则只要一个弧线,没有中心点到弧的一个连线 canvas.drawArc(mouseRectF, startAngle, sweepAngle, false, paint); //for test:看一下是不是刚好圆弧就是在外切矩形里面 //canvas.drawRect(mouseRectF, paint); //6、画美丽青春痘: //参数1 pts: 绘制的点的坐标集合,[x0 y0 x1 y1 x2 y2 ...] //参数2 offset: 参数1中的这么一串数字哪个表示X,哪个表示Y,则由此参数决定,也就是指定参数1中哪个点作为起始点作为横坐标,然后依次顺延 //比如:如果传0,则第一个数表示x、第二个数表示y;而如果传1则第二个数是x,第三个数是y,之后的一次顺延 //参数3 count: 需要使用集合中的几个元素 canvas.drawPoints(leftAcnePoints, 0, leftAcnePoints.length, paint);//画左美丽青春痘 canvas.drawPoints(rightAcnePoints, 0, rightAcnePoints.length, paint);//画右美丽青春痘 //7、画耳朵: canvas.drawRect(leftEarRectF, paint);//画左耳朵 canvas.drawRect(rightEarRectF, paint);//画右耳朵 } }
哇~~简直!!!!丑!!!爆!!!啦~~赶紧用方案二优化优化~
- 方案二:利用Path来解决方案一的缺陷【学习如何用Path画复杂图形】
这个方案主要是来优化方案一中耳机很显示的一个问题,就是将与脸相交的部份给去掉,如下:
而这时就需要用到Path了,将不规则的点连成线,很显示要达到目的,是需要四个点按一定规则连成线,就拿左耳朵来说:
下面看下具体如何使用:
public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); /* 左眼圆的坐标 */ private final PointF leftEyePoint = new PointF(170, 330); /* 右眼圆的坐标 */ private final PointF rightEyePoint = new PointF(310, 330); /* 眼睛圆的半径 */ private final int eyeRadius = 60; /* 嘴的圆弧外接矩形 */ //参数为left(距屏幕左边的x坐标位置)、top(距屏幕左边的x坐标位置)、right(距屏幕左边的x坐标位置)、bottom(距屏幕左边的x坐标位置) private final RectF mouseRectF = new RectF(60, 300, 420, 550); /* 嘴的圆弧起始角度 */ private final float startAngle = 380f; /* 嘴的圆弧的弧度 */ private final float sweepAngle = 140f; /* 左美丽青春痘 */ private final float[] leftAcnePoints = {130, 414, 111, 442, 152, 442, 130, 470, 130, 442}; /* 左美丽青春痘 */ private final float[] rightAcnePoints = {350, 414, 331, 442, 372, 442, 350, 470, 350, 442}; /* 左朵耳相关的坐标点,用Path将其连接优化耳朵 */ private final PointF leftEarMovePoint = new PointF(65, 300); private final PointF leftEarPoint1 = new PointF(30, 300); private final PointF leftEarPoint2 = new PointF(30, 500); private final PointF leftEarPoint3 = new PointF(65, 500); /* 右朵耳相关的坐标点,用Path将其连接优化耳朵 */ private final PointF rightEarMovePoint = new PointF(413, 300); private final PointF rightEarPoint1 = new PointF(450, 300); private final PointF rightEarPoint2 = new PointF(450, 500); private final PointF rightEarPoint3 = new PointF(413, 500); public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 //4、画两眼: canvas.drawCircle(leftEyePoint.x, leftEyePoint.y, eyeRadius, paint);//画左眼睛,用萌萌哒的正圆来形象的代表~~ canvas.drawCircle(rightEyePoint.x, rightEyePoint.y, eyeRadius, paint);//画右眼睛,用萌萌哒的正圆来形象的代表~~ //5、画嘴: //参数1 RectF:矩形,是圆弧所在圆的外接矩形 //参数2 startAngle:弧的起始角度 //参数3 sweepAngle:弧的弧度 //参数4 useCenter: 如果为true则会以圆中心画一个弧;如果为false则只要一个弧线,没有中心点到弧的一个连线 canvas.drawArc(mouseRectF, startAngle, sweepAngle, false, paint); //for test:看一下是不是刚好圆弧就是在外切矩形里面 //canvas.drawRect(mouseRectF, paint); //6、画美丽青春痘: //参数1 pts: 绘制的点的坐标集合,[x0 y0 x1 y1 x2 y2 ...] //参数2 offset: 参数1中的这么一串数字哪个表示X,哪个表示Y,则由此参数决定,也就是指定参数1中哪个点作为起始点作为横坐标,然后依次顺延 //比如:如果传0,则第一个数表示x、第二个数表示y;而如果传1则第二个数是x,第三个数是y,之后的一次顺延 //参数3 count: 需要使用集合中的几个元素 canvas.drawPoints(leftAcnePoints, 0, leftAcnePoints.length, paint);//画左美丽青春痘 canvas.drawPoints(rightAcnePoints, 0, rightAcnePoints.length, paint);//画右美丽青春痘 //7、画耳朵方案二【利用Path来优化】: Path leftEarPath = new Path(); leftEarPath.moveTo(leftEarMovePoint.x, leftEarMovePoint.y); leftEarPath.lineTo(leftEarPoint1.x, leftEarPoint1.y); leftEarPath.lineTo(leftEarPoint2.x, leftEarPoint2.y); leftEarPath.lineTo(leftEarPoint3.x, leftEarPoint3.y); canvas.drawPath(leftEarPath, paint); Path rightEarPath = new Path(); rightEarPath.moveTo(rightEarMovePoint.x, rightEarMovePoint.y); rightEarPath.lineTo(rightEarPoint1.x, rightEarPoint1.y); rightEarPath.lineTo(rightEarPoint2.x, rightEarPoint2.y); rightEarPath.lineTo(rightEarPoint3.x, rightEarPoint3.y); canvas.drawPath(rightEarPath, paint); } }
编译运行:
是不是正常多了,其中需要仔细体会下Path的用法,方案三中也需要用到这个API,还是有些生涩滴,下面继续优化耳朵,让其更加好看!
- 方案三:利用贝塞尔二次曲线最后优化【学习如何用塞尔二次曲线构建更复杂的图形】
目前耳朵已经有模有样了,但是跟现实中的人的耳朵还是有很大的区别,怎么的应该也是这个样子滴:
也就是一只耳朵应该是由两个曲线组成,这时就需要用到贝塞尔曲线了,什么是贝塞尔曲线呢?先看一下来自维基百科上直观的动画演示:
这个也是我们需要使用的,也是比较常用的,由三个点协调配合生成一个曲线,其中P0表示起始点、P1为控制点、P2为终止点。
其它还有更高次复杂的贝塞尔曲线,这里贴出来感受下,反正目前我是有点蒙圈,等未来要真正用到时到时再现场学,先有个感知的认识:
看到最后的五阶木有,好复杂好复杂呀!!!不过从上面的动画可以感知到贝塞尔曲线的强大之处,貌似可以生成任意不规则的曲线,这里只需要用到基础的二次曲线既可完成我们的需求了,而从图中可以看到二次曲线的组成需要三个点来控制,那如何绘制呢?其实还是得用到Path,具体看下代码:public class MyView extends View { private Paint paint; /* 笑脸圆的坐标 */ private final PointF facePoint = new PointF(240, 400); /* 笑脸圆的半径 */ private final int faceRadius = 200; /* 眉毛线开始坐标 */ private final PointF eyebrowStartPoint = new PointF(140, 250); /* 眉毛线结束坐标 */ private final PointF eyebrowEndPoint = new PointF(340, 250); /* 鼻子中竖线开始坐标 */ private final PointF noseLine1StartPoint = new PointF(240, 250); /* 鼻子中竖线结束坐标 */ private final PointF noseLine1EndPoint = new PointF(240, 500); /* 鼻子左勾线结束坐标,其它的开始坐标为noseLine1EndPoint */ private final PointF noseLine2EndPoint = new PointF(150, 450); /* 左眼圆的坐标 */ private final PointF leftEyePoint = new PointF(170, 330); /* 右眼圆的坐标 */ private final PointF rightEyePoint = new PointF(310, 330); /* 眼睛圆的半径 */ private final int eyeRadius = 60; /* 嘴的圆弧外接矩形 */ //参数为left(距屏幕左边的x坐标位置)、top(距屏幕左边的x坐标位置)、right(距屏幕左边的x坐标位置)、bottom(距屏幕左边的x坐标位置) private final RectF mouseRectF = new RectF(60, 300, 420, 550); /* 嘴的圆弧起始角度 */ private final float startAngle = 380f; /* 嘴的圆弧的弧度 */ private final float sweepAngle = 140f; /* 左美丽青春痘 */ private final float[] leftAcnePoints = {130, 414, 111, 442, 152, 442, 130, 470, 130, 442}; /* 左美丽青春痘 */ private final float[] rightAcnePoints = {350, 414, 331, 442, 372, 442, 350, 470, 350, 442}; /* 左朵耳相关的坐标点,用Path将其连接优化耳朵 */ private final PointF leftEarMovePoint = new PointF(65, 300); private final PointF leftEarBezierEndPoint1 = new PointF(40, 400); private final PointF leftEarBezierEndPoint2 = new PointF(65, 500); private final PointF leftEarBezierControlPoint1 = new PointF(0, 336); private final PointF leftEarBezierControlPoint2 = new PointF(0, 460); /* 右朵耳相关的坐标点,用Path将其连接优化耳朵 */ private final PointF rightEarMovePoint = new PointF(413, 300); private final PointF rightEarBezierEndPoint1 = new PointF(440, 400); private final PointF rightEarBezierEndPoint2 = new PointF(413, 500); private final PointF rightEarBezierControlPoint1 = new PointF(480, 336); private final PointF rightEarBezierControlPoint2 = new PointF(480, 460); public MyView(Context context) { this(context, null); } public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { paint = new Paint(); //设置画笔的颜色 paint.setColor(Color.RED); //设置抗锯齿:开启它会有些性能损耗,但是画面更加平滑~ paint.setAntiAlias(true); //设置画笔的样式:绘制空心图形 paint.setStyle(Paint.Style.STROKE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1、画个圆脸: //参数1,2:圆心的x,y坐标 //参数3 radius:圆的半径 canvas.drawCircle(facePoint.x, facePoint.y, faceRadius, paint); //2、画一字眉: //参数1,2:线的起始坐标点 //参数3,4:线的结束坐标点 canvas.drawLine(eyebrowStartPoint.x, eyebrowStartPoint.y, eyebrowEndPoint.x, eyebrowEndPoint.y, paint); //画一个眉毛,这里用一横线来形象的代表~~ //3、画勾鼻: canvas.drawLine(noseLine1StartPoint.x, noseLine1StartPoint.y, noseLine1EndPoint.x, noseLine1EndPoint.y, paint); //画鼻子中竖线 canvas.drawLine(noseLine1EndPoint.x, noseLine1EndPoint.y, noseLine2EndPoint.x, noseLine2EndPoint.y, paint); //画鼻子左勾线 //4、画两眼: canvas.drawCircle(leftEyePoint.x, leftEyePoint.y, eyeRadius, paint);//画左眼睛,用萌萌哒的正圆来形象的代表~~ canvas.drawCircle(rightEyePoint.x, rightEyePoint.y, eyeRadius, paint);//画右眼睛,用萌萌哒的正圆来形象的代表~~ //5、画嘴: //参数1 RectF:矩形,是圆弧所在圆的外接矩形 //参数2 startAngle:弧的起始角度 //参数3 sweepAngle:弧的弧度 //参数4 useCenter: 如果为true则会以圆中心画一个弧;如果为false则只要一个弧线,没有中心点到弧的一个连线 canvas.drawArc(mouseRectF, startAngle, sweepAngle, false, paint); //for test:看一下是不是刚好圆弧就是在外切矩形里面 //canvas.drawRect(mouseRectF, paint); //6、画美丽青春痘: //参数1 pts: 绘制的点的坐标集合,[x0 y0 x1 y1 x2 y2 ...] //参数2 offset: 参数1中的这么一串数字哪个表示X,哪个表示Y,则由此参数决定,也就是指定参数1中哪个点作为起始点作为横坐标,然后依次顺延 //比如:如果传0,则第一个数表示x、第二个数表示y;而如果传1则第二个数是x,第三个数是y,之后的一次顺延 //参数3 count: 需要使用集合中的几个元素 canvas.drawPoints(leftAcnePoints, 0, leftAcnePoints.length, paint);//画左美丽青春痘 canvas.drawPoints(rightAcnePoints, 0, rightAcnePoints.length, paint);//画右美丽青春痘 //7、画耳朵方案三【利用贝塞尔曲线终极优化】: //利用path来优化耳朵,之前是一个太不真实的矩形代替滴 Path leftEarPath = new Path(); leftEarPath.moveTo(leftEarMovePoint.x, leftEarMovePoint.y); leftEarPath.quadTo(leftEarBezierControlPoint1.x, leftEarBezierControlPoint1.y, leftEarBezierEndPoint1.x, leftEarBezierEndPoint1.y); leftEarPath.quadTo(leftEarBezierControlPoint2.x, leftEarBezierControlPoint2.y, leftEarBezierEndPoint2.x, leftEarBezierEndPoint2.y); canvas.drawPath(leftEarPath, paint); Path rightEarPath = new Path(); rightEarPath.moveTo(rightEarMovePoint.x, rightEarMovePoint.y); rightEarPath.quadTo(rightEarBezierControlPoint1.x, rightEarBezierControlPoint1.y, rightEarBezierEndPoint1.x, rightEarBezierEndPoint1.y); rightEarPath.quadTo(rightEarBezierControlPoint2.x, rightEarBezierControlPoint2.y, rightEarBezierEndPoint2.x, rightEarBezierEndPoint2.y); canvas.drawPath(rightEarPath, paint); } }
编译运行就是我们在篇头看到的那个结果啦:
剖析上面的实现:用到的是Path中提供的quadTo方法,先看一下它的API的注释:而看看维基图:
看下代码:
而点是如何确定的呢?拿左耳朵为例:所以对应代码是:
而同理:对应代码:
而右耳朵同理,这样整个构建过程就完成啦~内容比较多,需细细体会~