绘制半圆_Android Canvas 绘制小黄人
學習往往是枯燥的,如果能用一個有趣 Demo 來學習和練習技術,那對知識的掌握就會更牢固。我在學習 Canvas 繪制 API 的時候就是這樣做的。
?截圖鎮樓
效果圖
我覺得這個繪制小黃人的自定義 View 就很有意思,也為我后來工作中的自定義 View 實現打下了良好的基礎。雖然這是 4 年半以前寫的文章,但是大部分關注我們的同學應該沒看過,今天咱們一起來拷古翻新一下代碼(程序員的事,怎么能叫炒冷飯呢,這明明是溫故而知新)。以后有機會還會分享項目實用自定義 View,敬請關注。
實現步驟
其實很簡單
正文 ↓
準備工作
自定義MinionView extends View,定義以下成員變量,備用(可以先不看,后面的代碼看到莫名其妙出來的變量再上來看下)
private?float?bodyWidth;private?float?bodyHeight;
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?float?radius;?//?身體上下半圓的半徑
private?int?colorClothes?=?Color.rgb(32,?116,?160);?//?衣服的顏色
private?int?colorBody?=?Color.rgb(249,?217,?70);?//?身體的顏色
private?int?colorStroke?=?Color.BLACK;
private?RectF?bodyRect?=?new?RectF();
private?float?handsHeight;//?計算出吊帶的高度時,可以用來做手的高度
private?float?footHeight;?//?腳的高度,用來畫腳部陰影時用
初始化參數
重寫 onSizeChanged 方法,尺寸變化時初始化一下繪制的參數(會經常看到一些奇怪的數字,用做比例換算,別問我怎么來的,目測 + 一點點微調得來的- -。)
private?void?initParams()?{????bodyWidth?=?Math.min(getWidth(),?getHeight()?*?BODY_WIDTH_HEIGHT_SCALE)?*?BODY_SCALE;
????bodyHeight?=?Math.min(getWidth(),?getHeight()?*?BODY_WIDTH_HEIGHT_SCALE)?/?BODY_WIDTH_HEIGHT_SCALE?*?BODY_SCALE;
????mStrokeWidth?=?Math.max(bodyWidth?/?50,?mStrokeWidth);
????offset?=?mStrokeWidth?/?2;
????bodyRect.left?=?(getWidth()?-?bodyWidth)?/?2;
????bodyRect.top?=?(getHeight()?-?bodyHeight)?/?2;
????bodyRect.right?=?bodyRect.left?+?bodyWidth;
????bodyRect.bottom?=?bodyRect.top?+?bodyHeight;
????radius?=?bodyWidth?/?2;
????footHeight?=?radius?*?0.4333f;?
????handsHeight?=??(getHeight()?+?bodyHeight)?/?2???+?offset?-?radius?*?1.65f;
}
繪制參數好了,接下來就是一步步繪制幾何圖形了
畫身體
顯然身體是一個矩形加上,上下半圓,這邊只要用一個圓角矩形,然后圓角的弧度半徑用身體寬度的一半就可以達到這個效果了。把身體的矩形外存起來,后面經常要用到其相對位置進行對其它部位的定位,代碼如下:
protected?void?onDraw(Canvas?canvas)?{????...
????drawBody(canvas);???????//?身體
????drawBodyStroke(canvas);?//?最后畫身體的描邊,可以摭住一些過渡的棱角
}
private?void?drawBody(Canvas?canvas)?{
????mPaint.setColor(colorBody);
????mPaint.setStyle(Paint.Style.FILL);
????canvas.drawRoundRect(bodyRect,?radius,?radius,?mPaint);
}
private?void?drawBodyStroke(Canvas?canvas)?{
????mPaint.setColor(colorStroke);
????mPaint.setStrokeWidth(mStrokeWidth);
????mPaint.setStyle(Paint.Style.STROKE);
????canvas.drawRoundRect(bodyRect,?radius,?radius,?mPaint);
}
畫衣服
這是穿上褲子的樣子- 首先畫 底下的半圓
rect.top?=?(getHeight()?+?bodyHeight)?/?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?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把線一條條畫出來吧,這邊要同時考慮畫筆的描邊寬度,否則會出現連接點有鋸齒的感覺。( 2020 注:這是當時最直接的想法,現在來看用 Path 來繪制,每個點用 rLineTo 去連接,代碼會簡單得多。)
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將其畫上去,然后紐扣用一個實心的小圓表示
path.reset();
path.moveTo(rect.left?-?w?-?offset,?handsHeight);
path.lineTo(rect.left?+?h?/?4f,?rect.top?+?h?/?2f);
final?float?smallW?=?w?/?2f?*?(float)?Math.sin(Math.PI?/?4);
path.lineTo(rect.left?+?h?/?4f?+?smallW,?rect.top?+?h?/?2f?-?smallW);
final?float?smallW2?=?w?/?(float)?Math.sin(Math.PI?/?4)?/?2;
path.lineTo(rect.left?-?w?-?offset,?handsHeight?-?smallW2);
canvas.drawPath(path,?mPaint);
mPaint.setColor(colorStroke);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawPath(path,?mPaint);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(rect.left?+?h?/?5f,?rect.top?+?h?/?4f,?mStrokeWidth?*?0.7f,?mPaint);
//?畫右吊帶,代碼差不多省略了,坐標對稱
- 畫中間的口袋 是一個下面兩邊是圓角的圓角矩形,但是貌似不能直接畫這樣的圓角矩形,所以我就用土辦法,不就是一個多邊形嗎,用canvas.drawPath來畫,在圓角的地方添加圓弧過渡path.addArc
float?radiusBigPocket?=?w?/?2.0f;
path.moveTo(rect.left?+?1.5f?*?w,?rect.bottom?-?h?/?4f);
path.lineTo(rect.right?-?1.5f?*?w,?rect.bottom?-?h?/?4f);
path.lineTo(rect.right?-?1.5f?*?w,?rect.bottom?+?h?/?4f);
path.addArc(rect.right?-?1.5f?*?w?-?radiusBigPocket?*?2,?rect.bottom?+?h?/?4f?-?radiusBigPocket,
????????rect.right?-?1.5f?*?w,?rect.bottom?+?h?/?4f?+?radiusBigPocket,?0,?90);
path.lineTo(rect.left?+?1.5f?*?w?+?radiusBigPocket,?rect.bottom?+?h?/?4f?+?radiusBigPocket);
path.addArc(rect.left?+?1.5f?*?w,?rect.bottom?+?h?/?4f?-?radiusBigPocket,
????????rect.left?+?1.5f?*?w?+?2?*?radiusBigPocket,?rect.bottom?+?h?/?4f?+?radiusBigPocket,?90,?90);
path.lineTo(rect.left?+?1.5f?*?w,?rect.bottom?-?h?/?4f?-?offset);
canvas.drawPath(path,?mPaint);????
- 左右兩個小口袋也直接用一個小弧來解決掉
canvas.drawLine(bodyRect.left?+?bodyWidth?/?2,?bodyRect.bottom?-?h?*?0.8f,?bodyRect.left?+?bodyWidth?/?2,?bodyRect.bottom,?mPaint);
//?左邊的小口袋
float?radiusSmallPocket?=?w?*?1.2f;
canvas.drawArc(bodyRect.left?-?radiusSmallPocket,?bodyRect.bottom?-?radius?-?radiusSmallPocket,
???????bodyRect.left?+?radiusSmallPocket,?bodyRect.bottom?-?radius?+?radiusSmallPocket,?80,?-60,?false,?mPaint);
//?右邊小口袋
canvas.drawArc(bodyRect.right?-?radiusSmallPocket,?bodyRect.bottom?-?radius?-?radiusSmallPocket,
????????bodyRect.right?+?radiusSmallPocket,?bodyRect.bottom?-?radius?+?radiusSmallPocket,?100,?60,?false,?mPaint);
- 嗯,衣服畫完了。
????...
????drawClothes(canvas);//衣服
}
private?void?drawClothes(Canvas?canvas)?{
????//就是上面那一堆代碼按順序合起來啦。。。。。
}
畫腳
腳這部分比較簡單,從身體的下方,一個豎直的矩形下來,再加上一個左邊圓角的圓角矩形,還是通過畫Path來實現。
private?void?drawFeet(Canvas?canvas)?{????mPaint.setStrokeWidth(mStrokeWidth);
????mPaint.setColor(colorStroke);
????mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
????float?radiusFoot?=?radius?/?3?*?0.4f;
????float?leftFootStartX?=?bodyRect.left?+?radius?-?offset?*?2;
????float?leftFootStartY?=?bodyRect.bottom?-?offset;
????float?footWidthA?=?radius?*?0.5f;//腳寬度大-到半圓結束
????float?footWidthB?=?footWidthA?/?3;//腳寬度-比較細的部分
????//?左腳
????path.reset();
????path.moveTo(leftFootStartX,?leftFootStartY);
????path.lineTo(leftFootStartX,?leftFootStartY?+?footHeight);
????path.lineTo(leftFootStartX?-?footWidthA?+?radiusFoot,?leftFootStartY?+?footHeight);
????rect.left?=?leftFootStartX?-?footWidthA;
????rect.top?=?leftFootStartY?+?footHeight?-?radiusFoot?*?2;
????rect.right?=?rect.left?+?radiusFoot?*?2;
????rect.bottom?=?rect.top?+?radiusFoot?*?2;
????path.addArc(rect,?90,?180);
????path.lineTo(rect.left?+?radiusFoot?+?footWidthB,?rect.top);
????path.lineTo(rect.left?+?radiusFoot?+?footWidthB,?leftFootStartY);
????path.lineTo(leftFootStartX,?leftFootStartY);
????canvas.drawPath(path,?mPaint);
??//?右腳與左腳實現一致,坐標對稱,代碼略
}
畫手
這里是雙手放在后背的樣子手我用的是一個等腰直角三角形來實現,斜邊就是吊帶到褲子,從直角頂點作高到斜邊,通過小直角三角形的直角邊相等就可以算出頂點的坐標。這個時候還是有個圓角,剛開始我實現的時候是像上面那些通過path.addArc加上圓角,但是這邊計算好之后和原來的銜接一直有問題,在調了半天之后,偶然發現mPaint.setPathEffect(new CornerPathEffect(radiusHand));這個方法,可以使path的拐角用圓角來過渡,一下子就簡單到爆了,果然科學技術是第一生產力。
private?void?drawHands(Canvas?canvas)?{????...???????
????//?左手
????path.moveTo(bodyRect.left,?handsHeight);
????path.lineTo(bodyRect.left?-?hypotenuse?/?2,?handsHeight?+?hypotenuse?/?2);
????path.lineTo(bodyRect.left?+offset,?bodyRect.bottom?-?radius?+offset);
????path.lineTo(bodyRect.left,?handsHeight);
????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);
????...
?}
畫眼睛,嘴巴
三個字,圓圓圓反正就是各種畫圓,或者弧形,嘴巴部分偷懶也就一條小弧一筆帶過了,哈哈
private?void?drawEyesMouth(Canvas?canvas)?{????//?眼睛中心處于上半圓直徑?往上的高度偏移
????float?eyesOffset?=?radius?*?0.1f;
????mPaint.setStrokeWidth(mStrokeWidth?*?5);
????//?計算眼鏡帶弧行的半徑?分兩段,以便眼睛中間有隔開的效果
????float?radiusGlassesRibbon?=?(float)?(radius?/?Math.sin(Math.PI?/?20));
????rect.left?=?bodyRect.left?+?radius?-?radiusGlassesRibbon;
????rect.top?=?bodyRect.top?+?radius?-?(float)?(radius?/?Math.tan(Math.PI?/?20))?-?radiusGlassesRibbon?-?eyesOffset;
????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);
????//?眼睛半徑
????float?radiusEyes?=?radius?/?3;
????mPaint.setColor(Color.WHITE);
????mPaint.setStrokeWidth(mStrokeWidth);
????mPaint.setStyle(Paint.Style.FILL);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?-?radiusEyes?-?offset,?bodyRect.top?+?radius?-?eyesOffset,?radiusEyes,?mPaint);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?+?radiusEyes?+?offset,?bodyRect.top?+?radius?-?eyesOffset,?radiusEyes,?mPaint);
????mPaint.setColor(colorStroke);
????mPaint.setStyle(Paint.Style.STROKE);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?-?radiusEyes?-?offset,?bodyRect.top?+?radius?-?eyesOffset,?radiusEyes,?mPaint);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?+?radiusEyes?+?offset,?bodyRect.top?+?radius?-?eyesOffset,?radiusEyes,?mPaint);
????final?float?radiusEyeballBlack?=?radiusEyes?/?3;
????mPaint.setStyle(Paint.Style.FILL);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?-?radiusEyes?-?offset,?bodyRect.top?+?radius?-?eyesOffset,?radiusEyeballBlack,?mPaint);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?+?radiusEyes?+?offset,?bodyRect.top?+?radius?-?eyesOffset,?radiusEyeballBlack,?mPaint);
????mPaint.setColor(Color.WHITE);
????final?float?radiusEyeballWhite?=?radiusEyeballBlack?/?2;
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?-?radiusEyes?+?radiusEyeballWhite?-?offset?*?2,
????????????bodyRect.top?+?radius?-?radiusEyeballWhite?+?offset?-?eyesOffset,
????????????radiusEyeballWhite,?mPaint);
????canvas.drawCircle(bodyRect.left?+?bodyWidth?/?2?+?radiusEyes?+?radiusEyeballWhite,
????????????bodyRect.top?+?radius?-?radiusEyeballWhite?+?offset?-?eyesOffset,
????????????radiusEyeballWhite,?mPaint);
????//?畫嘴巴,因為位置和眼睛有相對關系,所以寫在一塊
????mPaint.setColor(colorStroke);
????mPaint.setStyle(Paint.Style.STROKE);
????mPaint.setStrokeWidth(mStrokeWidth);
????float?radiusMonth?=?radius;
????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);
}
腳下的陰影
這是最后一步了,直接畫一個非常扁的橢圓放在腳下面就可以了
不科學啊,長這么胖,為毛影子這么瘦(別在意這些細節)private?void?drawFeetShadow(Canvas?canvas)?{????mPaint.setColor(getResources().getColor(android.R.color.darker_gray));
????canvas.drawOval(bodyRect.left?+?bodyWidth?*?0.15f,
????????????bodyRect.bottom?-?offset?+?footHeight,
????????????bodyRect.right?-?bodyWidth?*?0.15f,
????????????bodyRect.bottom?-?offset?+?footHeight?+?mStrokeWidth?*?1.3f,?mPaint);
}
重寫 onDraw 方法
按層級依次調用上述的各種方法,畫完收工。
@Overrideprotected?void?onDraw(Canvas?canvas)?{
????drawFeetShadow(canvas);?//?腳下的陰影
????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();
}
然后效果就變成了這樣。
看起來還有點小酷炫希望大家喜歡,完整源碼在?github ,可點擊“閱讀原文”查看推薦閱讀設計模式概覽:六大設計原則
RecyclerView 的緩存復用機制
ServiceManager 的工作原理
關注我
助你升職加薪
Android 面試官
原創不易,在看支持! 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的绘制半圆_Android Canvas 绘制小黄人的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 芯明天debug assertion f
- 下一篇: pb 如何导出csv_Firefox火狐