# Android 用Canvas 画几何图形，画出小黄人

## 实现步骤

1. 首先找到一张小黄人的图
• 然后调用`canvas.drawBitmap()`后画到画布上 - -。
好吧，一点都不好笑

### 准备工作

``````    private Paint mPaint;
private float bodyWidth;
private float bodyHeigh;
private static final float BODY_SCALE = 0.6f;//身体主干占整个view的比重
private static final float BODY_WIDTH_HEIGHT_SCALE = 0.6f; //        身体的比例设定为 w:h = 3:5

private float mStrokeWidth = 4;//描边宽度
private float offset;//计算时，部分需要 考虑描边偏移
private int colorClothes = Color.rgb(32, 116, 160);//衣服的颜色
private int colorBody = Color.rgb(249, 217, 70);//身体的颜色
private int colorStroke = Color.BLACK;
private RectF bodyRect;
private float handsHeight;//计算出吊带的高度时，可以用来做手的高度
private float footHeigh;//脚的高度，用来画脚部阴影时用
``````

### 初始化参数

``````    private void initParams() {
bodyWidth = Math.min(getWidth(), getHeight() * BODY_WIDTH_HEIGHT_SCALE) * BODY_SCALE;
bodyHeigh = Math.min(getWidth(), getHeight() * BODY_WIDTH_HEIGHT_SCALE) / BODY_WIDTH_HEIGHT_SCALE * BODY_SCALE;

mStrokeWidth = Math.max(bodyWidth / 50, mStrokeWidth);
offset = mStrokeWidth / 2;

bodyRect = new RectF();
bodyRect.left = (getWidth() - bodyWidth) / 2;
bodyRect.top = (getHeight() - bodyHeigh) / 2;
bodyRect.right = bodyRect.left + bodyWidth;
bodyRect.bottom = bodyRect.top + bodyHeigh;

handsHeight =  (getHeight() + bodyHeigh) / 2   + offset - radius * 1.65f ;
}
``````

### 画身体

``````    drawBody(canvas);//身体
drawBodyStroke(canvas);//最后画身体的描边，可以摭住一些过渡的棱角

private void drawBody(Canvas canvas) {
initPaint();
mPaint.setColor(colorBody);
mPaint.setStyle(Paint.Style.FILL);

}

private void drawBodyStroke(Canvas canvas) {
initPaint();
mPaint.setColor(colorStroke);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
}
``````

### 画衣服

• 首先先画上底下的半圆
``````
RectF rect = new RectF();

rect.left = (getWidth() - bodyWidth) / 2 + offset;
rect.top = (getHeight() + bodyHeigh) / 2 - radius * 2 + offset;
rect.right = rect.left + bodyWidth - offset * 2;
rect.bottom = rect.top + radius * 2 - offset * 2;

mPaint.setColor(colorClothes);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(mStrokeWidth);
canvas.drawArc(rect, 0, 180, true, mPaint);
``````
• 再画半圆上方的矩形,w表示矩形离左边身体的距离，h矩形的高
``````        int h = (int) (radius * 0.5);
int w = (int) (radius * 0.3);

rect.left += w;
rect.top = rect.top + radius - h;
rect.right -= w;
rect.bottom = rect.top + h;

canvas.drawRect(rect, mPaint);
``````
• 上面的画完之后，要在衣服上面描一层黑色的边，用`canvas.drawLines`把线一条条画出来吧，这边要同时考虑画笔的描边宽度，否则会出现连接点有锯齿的感觉。
``````
//画横线
initPaint();
mPaint.setColor(colorStroke);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(mStrokeWidth);
float[] pts = new float[20];//5条线

pts[0] = rect.left - w;
pts[1] = rect.top + h;
pts[2] = pts[0] + w;
pts[3] = pts[1];

pts[4] = pts[2];
pts[5] = pts[3] + offset;
pts[6] = pts[4];
pts[7] = pts[3] - h;

pts[8] = pts[6] - offset;
pts[9] = pts[7];
pts[10] = pts[8] + (radius - w) * 2;
pts[11] = pts[9];

pts[12] = pts[10];
pts[13] = pts[11] - offset;
pts[14] = pts[12];
pts[15] = pts[13] + h;

pts[16] = pts[14] - offset;
pts[17] = pts[15];
pts[18] = pts[16] + w;
pts[19] = pts[17];
canvas.drawLines(pts, mPaint);
``````
• 画吊带 就是一个直角梯形，把梯形的四个顶点计算出来，使用`canvas.drawPath`将其画上去，然后纽扣用一个实心的小圆表示
``````       //画左吊带
initPaint();
mPaint.setColor(colorClothes);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
Path path = new Path();
path.moveTo(rect.left - w - offset, handsHeight);
path.lineTo(rect.left + h / 4, rect.top + h / 2);
final float smallW = w / 2 * (float) Math.sin(Math.PI / 4);
path.lineTo(rect.left + h / 4 + smallW, rect.top + h / 2 - smallW);
final float smallW2 = w / (float) Math.sin(Math.PI / 4) / 2;
path.lineTo(rect.left - w - offset, handsHeight - smallW2);

canvas.drawPath(path, mPaint);
initPaint();
mPaint.setColor(colorStroke);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, mPaint);
initPaint();
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(rect.left + h / 4, rect.top + h / 4, mStrokeWidth, mPaint);

//画右吊带

initPaint();
mPaint.setColor(colorClothes);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
path.reset();
path.moveTo(rect.left - w + 2 * radius - offset, handsHeight);
path.lineTo(rect.right - h / 4, rect.top + h / 2);
path.lineTo(rect.right - h / 4 - smallW, rect.top + h / 2 - smallW);
path.lineTo(rect.left - w + 2 * radius - offset, handsHeight- smallW2);

canvas.drawPath(path, mPaint);
initPaint();
mPaint.setColor(colorStroke);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path, mPaint);
initPaint();
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(rect.right - h / 4, rect.top + h / 4, mStrokeWidth, mPaint);
``````
• 画中间的口袋 是一个下面两边是圆角的圆角矩形，但是貌似不能直接画这样的圆角矩形，所以我就用土办法，不就是一个多边形吗，用`canvas.drawPath`来画，在圆角的地方添加圆弧过渡`path.addArc`
``````       //中间口袋
initPaint();
mPaint.setColor(colorStroke);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);

path.reset();
float radiusBigPokect = w / 2.0f;
path.moveTo(rect.left + 1.5f * w, rect.bottom - h / 4);
path.lineTo(rect.right - 1.5f * w, rect.bottom - h / 4);
path.lineTo(rect.right - 1.5f * w, rect.bottom + h / 4);
rect.right - 1.5f * w, rect.bottom + h / 4 + radiusBigPokect, 0, 90);
path.lineTo(rect.left + 1.5f * w + radiusBigPokect, rect.bottom + h / 4 + radiusBigPokect);

path.addArc(rect.left + 1.5f * w, rect.bottom + h / 4 - radiusBigPokect,
rect.left + 1.5f * w + 2 * radiusBigPokect, rect.bottom + h / 4 + radiusBigPokect, 90, 90);
path.lineTo(rect.left + 1.5f * w, rect.bottom - h / 4 - offset);
canvas.drawPath(path, mPaint);
``````
• 左右两个小口袋也直接用一个小弧来解决掉
``````     //        下边一竖，分开裤子
canvas.drawLine(bodyRect.left + bodyWidth / 2, bodyRect.bottom - h * 0.8f, bodyRect.left + bodyWidth / 2, bodyRect.bottom, mPaint);
//      左边的小口袋
float radiusSamllPokect = w * 1.2f;
//      右边小口袋
//        canvas.drawArc(left + w/5,);
``````
• 嗯，衣服画完了。
``````    drawClothes(canvas);//衣服

private void drawClothes(Canvas canvas) {
initPaint();
//就是上面那一堆代码按顺序合起来啦。。。。。
}
``````

### 画脚

``````        drawFeet(canvas);//脚

private void drawFeet(Canvas canvas) {
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setColor(colorStroke);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

float leftFootStartX = bodyRect.left + radius - offset * 2;
float leftFootStartY = bodyRect.bottom - offset;
float footWidthA = radius * 0.5f;//脚宽度大-到半圆结束
float footWidthB = footWidthA / 3;//脚宽度-比较细的部分

//      左脚
Path path = new Path();
path.moveTo(leftFootStartX, leftFootStartY);
path.lineTo(leftFootStartX, leftFootStartY + footHeigh);
path.lineTo(leftFootStartX - footWidthA + radiusFoot, leftFootStartY + footHeigh);

RectF rectF = new RectF();
rectF.left = leftFootStartX - footWidthA;
rectF.top = leftFootStartY + footHeigh - radiusFoot * 2;
rectF.right = rectF.left + radiusFoot * 2;
rectF.bottom = rectF.top + radiusFoot * 2;
path.lineTo(rectF.left + radiusFoot + footWidthB, rectF.top);
path.lineTo(rectF.left + radiusFoot + footWidthB, leftFootStartY);
path.lineTo(leftFootStartX, leftFootStartY);
canvas.drawPath(path, mPaint);

//      右脚
float rightFootStartX = bodyRect.left + radius + offset * 2;
float rightFootStartY = leftFootStartY;
path.reset();
path.moveTo(rightFootStartX, rightFootStartY);
path.lineTo(rightFootStartX, rightFootStartY + footHeigh);
path.lineTo(rightFootStartX + footWidthA - radiusFoot, rightFootStartY + footHeigh);

rectF.left = rightFootStartX + footWidthA - radiusFoot * 2;
rectF.top = rightFootStartY + footHeigh - radiusFoot * 2;
rectF.right = rectF.left + radiusFoot * 2;
rectF.bottom = rectF.top + radiusFoot * 2;
path.lineTo(rectF.right - radiusFoot - footWidthB, rectF.top);
path.lineTo(rectF.right - radiusFoot - footWidthB, rightFootStartY);
path.lineTo(rightFootStartX, rightFootStartY);
canvas.drawPath(path, mPaint);

}
``````

### 画手

``````        drawHands(canvas);//手

private void drawHands(Canvas canvas) {
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(colorBody);

//        左手
Path path = new Path();
float hypotenuse = bodyRect.bottom - radius - handsHeight;
float radiusHand = hypotenuse / 6;

path.moveTo(bodyRect.left, handsHeight);
path.lineTo(bodyRect.left - hypotenuse / 2, handsHeight + hypotenuse / 2);
canvas.drawPath(path, mPaint);

mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(colorStroke);
canvas.drawPath(path, mPaint);

//        右手
path.reset();
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(colorBody);

path.moveTo(bodyRect.right, handsHeight);
path.lineTo(bodyRect.right + hypotenuse / 2, handsHeight + hypotenuse / 2);
canvas.drawPath(path, mPaint);

mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(colorStroke);
canvas.drawPath(path, mPaint);

//        一个慢动作  - -||| 拐点内侧
path.reset();
mPaint.setStyle(Paint.Style.FILL);
path.moveTo(bodyRect.left, handsHeight + hypotenuse / 2 - mStrokeWidth);
path.lineTo(bodyRect.left - mStrokeWidth * 2, handsHeight + hypotenuse / 2 + mStrokeWidth * 2);
path.lineTo(bodyRect.left, handsHeight + hypotenuse / 2 + mStrokeWidth);
canvas.drawPath(path, mPaint);

path.reset();
path.moveTo(bodyRect.right, handsHeight + hypotenuse / 2 - mStrokeWidth);
path.lineTo(bodyRect.right + mStrokeWidth * 2, handsHeight + hypotenuse / 2 + mStrokeWidth * 2);
path.lineTo(bodyRect.right, handsHeight + hypotenuse / 2 + mStrokeWidth);
canvas.drawPath(path, mPaint);

}

``````

### 画眼睛,嘴巴

``````        drawEyesMouth(canvas);//眼睛,嘴巴
private void drawEyesMouth(Canvas canvas) {

float eyesOffset = radius * 0.1f;//眼睛中心处于上半圆直径 往上的高度偏移
mPaint.setStrokeWidth(mStrokeWidth * 5);
//        计算眼镜带弧行的半径 分两段，以便眼睛中间有隔开的效果
RectF rect = new RectF();
rect.right = rect.left + radiusGlassesRibbon * 2;
rect.bottom = rect.top + radiusGlassesRibbon * 2;
canvas.drawArc(rect, 81, 3, false, mPaint);
canvas.drawArc(rect, 99, -3, false, mPaint);

//眼睛半径
initPaint();
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(colorStroke);
mPaint.setStyle(Paint.Style.STROKE);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(Color.WHITE);
canvas.drawCircle(bodyRect.left + bodyWidth / 2 - radiusEyes + radiusEyeballWhite - offset * 2,

//        画嘴巴，因为位置和眼睛有相对关系，所以写在一块
mPaint.setColor(colorStroke);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
rect.left = bodyRect.left;
rect.top = bodyRect.top - radiusMonth / 2.5f;
rect.right = rect.left + radiusMonth * 2;
rect.bottom = rect.top + radiusMonth * 2;
canvas.drawArc(rect, 95, -20, false, mPaint);

}

``````

### 脚下的阴影

``````        drawFeetShadow(canvas);//脚下的阴影

mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawOval(bodyRect.left + bodyWidth * 0.15f, bodyRect.bottom - offset + footHeigh,
bodyRect.right - bodyWidth * 0.15f, bodyRect.bottom - offset + footHeigh + mStrokeWidth * 1.3f, mPaint);
}

``````

### 在`onDraw`方法，依次调用上述的各种方法，画完收工。

``````

@Override
protected void onDraw(Canvas canvas) {
initParams();
initPaint();
drawFeet(canvas);//脚
drawHands(canvas);//手
drawBody(canvas);//身体
drawClothes(canvas);//衣服
drawEyesMouth(canvas);//眼睛,嘴巴
drawBodyStroke(canvas);//最后画身体的描边，可以摭住一些过渡的棱角
}
``````

### 三行代码搞定脑洞

``````    public void randomBodyColor() {
Random random = new Random();
colorBody = Color.rgb(random.nextInt(255), random.nextInt(255), random.nextInt(255));
invalidate();
}
``````

### 推荐阅读更多精彩内容

• 版权声明：本文为博主原创文章，未经博主允许不得转载 前言 Canvas 本意是画布的意思,然而将它理解为绘制工具一...
cc荣宣阅读 30,613评论 1 39
• 前言 Paint 可以决定你所画的几何图形、文本、位图的样式和颜色变化。 mPaint.measureXX mPa...
cc荣宣阅读 2,211评论 0 10
• 系列文章之 Android中自定义View(一)系列文章之 Android中自定义View(二)系列文章之 And...
YoungerDev阅读 1,844评论 2 8
• 前言： 在接触Android这么长时间，看到很多大牛都在和大家分享自己的知识，深有体会，刚好前段时间写了一个Dem...
杨艳伟阅读 668评论 0 5
• 请善待你的影子 他总会朝夕伴你 最亲的人莫过于父母 可终有一天也要离你而去 时间拉着他们，远你而去 前一秒他对你说...
BORNALONE阅读 33评论 0 0