簡述:
- Android中自定義View開發的遇到的問題?
Android開發中自定義View可以說是常見的技術,不管是新手還是老司機都非常了解。但是目前Android自定義View的現狀是很多人對自定義View都能說得出來一些,但是實際上根據需求開發起來卻比較難。老實說這也是我之前的想法,我也一直在思考這個原因,個人覺得原因是:
第一 對自定義View中的API不熟悉;
第二 對自定義View沒有一個系統認識和深入了解;
第三 對掌握的自定義View的掌握沒有一個分類管理思想;
canvas繪制的API的細節深入的學習能解決什么問題?
此篇博客canvas的API的學習可以系統掌握canvas.drawxxx()系列的方法
以及繪制原理。本系列博客能給你帶來什么?
個人認為Android中的自定義View API的學習主要分為幾個方面:
第一 Canvas繪制系列的API的學習;
第二 Paint 畫筆系列的API的學習;
第三 輔助繪制(clipxxx系列方法)canvas的幾何變換(位移、旋轉、錯切)API的學習;
第四 自定義View的測量、繪制(onMeasure、onSizeChange、onDraw);
第五 自定義ViewGroup的測量、布局、繪制(onMeasure、onSizeChange、onLayout、onDraw);
第六 事件分發、事件攔截、滑動沖突以及觸摸反饋的接口的回調設計;
1、canvas繪制顏色
一般用于在繪制之前設置底色,或者在繪制之后為界面設置半透明蒙版
canvas中繪制顏色主要幾種方法:
- canvas.drawColor(int color)
- canvas.drawRGB(int r, int g, int b)
- canvas.drawARGB(int a, int r, int g, int b)
canvas.drawColor(Color.parseColor("#00bfa5"));//int color
canvas.drawRGB(100, 200, 100);//設置red值(0~255),green值(0~255),blue值(0~255)
canvas.drawARGB(100, 100, 200, 100);//設置alpha值(0~255),設置red值(0~255),green值(0~255),blue值(0~255)
2、canvas繪制形狀
- 繪制圓形:
- canvas.drawCircle(float dx, float dy, float radius, Paint paint)
mPaint.setStyle(Paint.Style.FILL);//設置填充style為FILL
mPaint.setStyle(Paint.Style.STROKE);//設置填充style為STROKE
canvas.drawCircle(mWidth / 2, mHeight / 2, 200, mPaint);//(dx:圓心橫坐標 dy:圓心縱坐標 radius:半徑 paint 畫筆)
-
繪制矩形:
canvas中繪制矩形主要幾種方法:
Rect和RectF的細微區別是Rect的繪制的單位類型是int,而RectF的繪制單位類型是float - canvas.drawRect(int left, int top, int right, int bottom, Paint paint);
- canvas.drawRect(Rect rect, Paint paint);
- canvas.drawRect(RectF rectF, Paint paint);
mPaint.setStyle(Paint.Style.FILL);//設置填充style為FILL
mPaint.setStyle(Paint.Style.STROKE);//設置填充style為STROKE
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRect(mWidth / 2 - 200, 200, mWidth / 2 + 200, 600, mPaint);//left:矩形的左邊離Y軸的距離;top:矩形的頂邊離X軸的距離;right的右邊離Y軸的距離;bottom:矩形的底部邊離X軸的距離
-
繪制圓角矩形:
canvas繪制圓角矩形主要有兩個方法:
但是這兩個方法是等價的,繪制出來的效果都是一樣的。 - drawRoundRect(RectF rect, float rx, float ry, Paint paint)
- drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
int rx = 80;//圓角矩形的圓角所處的橢圓的橫向半徑
int ry = 40;//圓角矩形的圓角所處的橢圓的縱向半徑
int left = mWidth / 2 - 200;//left:矩形的左邊離Y軸的距離;
int top = 200;//top:矩形的頂邊離X軸的距離;
int right = mWidth / 2 + 200;//right的右邊離Y軸的距離;
int bottom = 400;//bottom:矩形的底部邊離X軸的距離
mPaint.setStyle(Paint.Style.FILL);//設置填充style為FILL
mPaint.setStyle(Paint.Style.STROKE);//設置填充style為STROKE
mPaint.setStrokeWidth(5f);//設置stroke線形的寬度
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRoundRect(left, top, right, bottom, rx, ry, mPaint);//繪制圓角矩形
提出問題:圓角矩形繪制的原理是怎樣的?為什么會有rx,ry兩個半徑?
圓角矩形的四個圓角實際上對應著四個橢圓弧的一部分,注意是橢圓弧不是正圓弧。那么rx也就是橢圓弧的橫向半徑,ry是橢圓弧的縱向半徑,正好對應著橢圓的短半徑和長半徑。
(圖片來源于GcsSloop大神,地址:http://ww3.sinaimg.cn/large/005Xtdi2jw1f2748fjw2bj308c0dwmx8.jpg)
結論:
當rx等于ry時,此時的四個橢圓弧也就變成四個正圓弧,那么每個圓角即為正圓弧的1/4;
當rx等于矩形寬度的一半,ry等于矩形高度的一半時,此時四個圓角所處的橢圓的重合,正好為矩形的內接橢圓;
當rx大于矩形寬度的一半,ry大于矩形高度的一半時,此時四個圓角所處橢圓實際上是無法計算出圓弧的,所以drawRoundRect對大于該數值的參數進行了修改,如果大于rx大于矩形寬度一半,ry大于矩形高度一半的參數均按照一半來處理。
結論論證:
可以針對圓角矩形繪制四個半徑rx,ry橢圓來驗證,若橢圓中有一部分圓弧與之重合,正好就證實我們的原理。
int rx = 80;//圓角矩形的圓角所處的橢圓的橫向半徑
int ry = 40;//圓角矩形的圓角所處的橢圓的縱向半徑
int left = mWidth / 2 - 200;//left:矩形的左邊離Y軸的距離;
int top = 200;//top:矩形的頂邊離X軸的距離;
int right = mWidth / 2 + 200;//right的右邊離Y軸的距離;
int bottom = 400;//bottom:矩形的底部邊離X軸的距離
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRoundRect(left, top, right, bottom, rx, ry, mPaint);//繪制圓角矩形
canvas.drawOval(left, top, left + 2 * rx, top + 2 * ry, mPaint);//繪制左上角圓角的所處的橢圓
canvas.drawOval(right - 2 * rx, top, right, top + 2 * ry, mPaint);//繪制右上角圓角的所處的橢圓
canvas.drawOval(left, bottom - 2 * ry, left + 2 * rx, bottom, mPaint);//繪制左下角圓角的所處的橢圓
canvas.drawOval(right - 2 * rx, bottom - 2 * ry, right, bottom, mPaint);//繪制右下角圓角的所處的橢圓
當rx等于ry時,此時的四個橢圓弧也就變成四個正圓弧,那么每個圓角即為正圓弧的1/4;
當rx接近矩形寬度的一半,ry接近矩形高度的一半時,此時四個圓角所處的橢圓接近重合
int rx = 195;//矩形寬度的一半為200
int ry = 95;//矩形高度的一半為100
int left = mWidth / 2 - 200;//left:矩形的左邊離Y軸的距離;
int top = 200;//top:矩形的頂邊離X軸的距離;
int right = mWidth / 2 + 200;//right的右邊離Y軸的距離;
int bottom = 400;//bottom:矩形的底部邊離X軸的距離
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.parseColor("#e91e63"));
canvas.drawRoundRect(left, top, right, bottom, rx, ry, mPaint);//繪制圓角矩形
canvas.drawOval(left, top, left + 2 * rx, top + 2 * ry, mPaint);//繪制左上角圓角的所處的橢圓
canvas.drawOval(right - 2 * rx, top, right, top + 2 * ry, mPaint);//繪制右上角圓角的所處的橢圓
canvas.drawOval(left, bottom - 2 * ry, left + 2 * rx, bottom, mPaint);//繪制左下角圓角的所處的橢圓
canvas.drawOval(right - 2 * rx, bottom - 2 * ry, right, bottom, mPaint);//繪制右下角圓角的所處的橢圓
當rx等于矩形寬度的一半,ry等于矩形高度的一半時,此時四個圓角所處的橢圓的重合,正好為矩形的內接橢圓;
當rx大于矩形寬度的一半,ry大于矩形高度的一半時,此時四個圓角所處橢圓實際上是無法計算出圓弧的,所以drawRoundRect對大于該數值的參數進行了修改,如果大于rx大于矩形寬度一半,ry大于矩形高度一半的參數均按照一半來處理。(為了便于區別把橢圓的顏色繪制為grey)
-
繪制點:
canvas繪制點主要有幾種方法: - canvas.drawPoint(float x, float y, Paint paint)//繪制單個點
- canvas.drawPoints(float[] pts, int offset, int count, Paint paint)//有選擇性繪制多個點
- canvas.drawPoints(float[] pts, Paint paint)//繪制多個點
mPaint.setColor(Color.parseColor("#dd2c00"));
mPaint.setStrokeWidth(80f);
mPaint.setStrokeCap(Paint.Cap.ROUND);//繪制點Point,canvas.drawPoint默認繪制方形點,設置畫筆的setStrokeCap可以繪制圓點
canvas.drawPoint(mWidth / 2, mHeight / 2, mPaint);
//繪制多個點:drawPoints(float[] opts, Paint paint)
//opts 點的一對(x,y)坐標的數組。(相鄰的兩個數分為一個點的x,y)
mPaint.setColor(Color.parseColor("#304ffe"));
float[] points = {mWidth / 2 - 200, 200, mWidth / 2 + 200, 200, mWidth / 2 - 200, 600, mWidth / 2 + 200, 600};
canvas.drawPoints(points, mPaint);
//有選擇繪制多個點: drawPoints(float[] opts, offset, count, Paint paint)
//opts 點的一對(x,y)坐標的數組。(相鄰的兩個數分為一個點的x,y)
//offset:是相對于對points數組的元素下標做偏移
//count: 設置points數組從offset偏移算起元素的個數
//經過offset和count操作產生新的點的數組
mPaint.setColor(Color.RED);
canvas.drawPoints(points, 4, 4, mPaint);//在前兩個點后畫兩個點
-
繪制橢圓:
canvas繪制橢圓主要有兩種方法:
但是這兩個方法是等價的,繪制出來的效果都是一樣的。 - drawOval(RectF oval, Paint paint)
- drawOval(float left, float top, float right, float bottom, Paint paint)
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.parseColor("#FF9800"));
canvas.drawOval(mWidth / 2 - 400, 200, mWidth / 2 + 400, 600, mPaint);//外接矩形的left,top,right,bottom
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.parseColor("#0091EA"));
RectF rectF = new RectF(mWidth / 2 - 200, 0, mWidth / 2 + 200, 800);//外接矩形的left,top,right,bottom
canvas.drawOval(rectF, mPaint);
-
繪制線段:
canvas繪制線段主要有幾種方法: - drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
- drawLines(float[] pts, int offset, int count, Paint paint)
- drawLines(float[] pts, Paint paint)
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5f);
mPaint.setColor(Color.GREEN);
canvas.drawLine(mWidth / 2 - 200, 200, mWidth / 2 + 200, 600, mPaint);//startX線的起點X坐標,startY線的起點Y坐標,endX線的終點X坐標,endY線的終點Y坐標
float[] points2 = {
mWidth / 2 - 200, 200,
mWidth / 2 + 200, 600,
mWidth / 2 + 200, 200,
mWidth / 2 - 200, 600,
mWidth / 2, 200,
mWidth / 2, 600,
mWidth / 2 - 200, 400,
mWidth / 2 + 200, 400
};//points2 點的一對(startX,startY,endX,endY)坐標的數組。(相鄰的四個數為一條線的起點和終點的x,y坐標)
canvas.drawLines(points2, mPaint);//繪制多條線段
float[] points2 = {
mWidth / 2 - 200, 200,
mWidth / 2 + 200, 600,
mWidth / 2 + 200, 200,
mWidth / 2 - 200, 600,
mWidth / 2, 200,
mWidth / 2, 600,
mWidth / 2 - 200, 400,
mWidth / 2 + 200, 400
};//points2 點的一對(startX,startY,endX,endY)坐標的數組。(相鄰的四個數為一條線的起點和終點的x,y坐標)
//offset:原理和drawPonits一樣,是相對于對points2數組的元素下標做偏移
//count: 設置points2數組從offset偏移算起元素的個數
//經過offset和count產生新的點的數組
canvas.drawLines(points2, 4, 8, mPaint);
-
繪制弧形或者扇形(是針對所在橢圓來繪制弧形和扇形):
canvas繪制弧形或者扇形(是針對所在橢圓來繪制弧形和扇形)主要有兩種方法:但是這兩個方法是等價的,繪制出來的效果都是一樣的。 - drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
- drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
//canvas.drawArc(left,top,right,bottom,startAngle,sweepAngle,userCenter,paint)
//left,top,right,bottom: 固定出橢圓
//startAngle起始角度(0度為圓心橫坐標向左,正數為順時針,負數為逆時針)
//sweepAngle掃過的角度
//userCenter是否連接圓心(true:連接圓心,畫扇形;false:不連接圓心,畫弧形)
mPaint.setStyle(Paint.Style.STROKE);//設置橢圓填充style為STROKE
RectF rectF1 = new RectF(mWidth / 2 - 400, 200, mWidth / 2 + 400, 600);
mPaint.setStyle(Paint.Style.FILL);//設置扇形填充style為FILL
mPaint.setStyle(Paint.Style.STROKE);//設置扇形填充style為STROKE
canvas.drawOval(rectF1,mPaint);//繪制扇形所處的橢圓
canvas.drawArc(rectF1, 0, 120, true, mPaint);//扇形
//canvas.drawArc(left,top,right,bottom,startAngle,sweepAngle,userCenter,paint)
//left,top,right,bottom: 固定出橢圓
//startAngle起始角度(0度為圓心橫坐標向左,正數為順時針,負數為逆時針)
//sweepAngle掃過的角度
//userCenter是否連接圓心(true:連接圓心,畫扇形;false:不連接圓心,畫弧形)
mPaint.setStyle(Paint.Style.STROKE);//設置橢圓填充style為STROKE
RectF rectF1 = new RectF(mWidth / 2 - 400, 200, mWidth / 2 + 400, 600);
mPaint.setStyle(Paint.Style.FILL);//設置扇形填充style為FILL
mPaint.setStyle(Paint.Style.STROKE);//設置弧形填充style為STROKE
canvas.drawOval(rectF1,mPaint);//繪制弧形所處的橢圓(注意:繪制STROKE的弧形的時候不要繪制橢圓,否則會重合不便于觀察結果)
canvas.drawArc(rectF1, 0, 120, false, mPaint);//弧形
-
繪制圖片Bitmap:
canvas圖片Bitmap主要有幾種方法: - drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint)
- drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
canvas.translate(mWidth / 2, mHeight / 2);//移動坐標原點到View組件的正中間位置,這個操作會很方便我們繪制時算坐標。(涉及到canvas的幾何變換后期會說到)
float[] points3 = {-mWidth / 2, 0, mWidth / 2, 0, 0, -mHeight / 2, 0, mHeight / 2};
canvas.drawLines(points3, mPaint);//繪制出坐標系線
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
canvas.drawBitmap(bitmap, -bitmap.getWidth() / 2, -bitmap.getHeight() / 2, mPaint);//圖片左上角x,y坐標對應left,top
-
繪制文字:
canvas中涉及到文本的繪制的細節有很多,后期會專門說下關于文本的繪制,這里先了解下。 - canvas.drawText(char[] text, int index, int count, float x, float y, Paint paint)
- canvas.drawText(String text, float x, float y, Paint paint)
- canvas.drawText(String text, int start, int end, float x, float y, Paint paint)
- canvas.drawText(CharSequence text, int start, int end, float x, float y, Paint paint)
canvas.translate(mWidth / 2, mHeight / 2);//移動坐標原點到View組件的正中間位置
mPaint.setTextSize(50);
mPaint.setColor(Color.GREEN);
mPaint.setFakeBoldText(true);
String text = "今天好像是情人節,單身狗還是擼擼代碼吧";
mPaint.getTextBounds(text, 0, text.length(), mTextBound);//通過傳入mTextBound(Rect對象),將文字的尺寸固定住,作用相當于測量文本尺寸。
canvas.drawText(text, -mTextBound.width() / 2, mTextBound.height() / 2, mPaint);//x,y指的是文本繪制起點坐標,注意文本的繪制起點是第一個字左下角還要向外偏移一點,至于為什么是這樣后期會說到.
-
繪制Path類型自定義圖型:
Path的知識在Canvas中繪制很重要,合理使用Path可以做出很多炫酷的組件以及提高繪制的效率。所以這里先給出個例子,下一期博客將會深入講解canvas繪制中的Path。情人節了,這張圖或許適合你,哈哈哈
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.RED);
mPath.arcTo(200, 200, 400, 400, -225, 225, true);
mPath.arcTo(400, 200, 600, 400, -180, 225, false);
mPath.lineTo(400, 542);
canvas.drawPath(mPath, mPaint);
結束:
到這里,這期博客就結束了,該博客旨在新手學習沉淀。感謝GcsSloop和扔物線大神對自定義View的博客的無私奉獻。本系列博客均為自己學大神們自定義View總結。
小例子:本來是扔物線大神第一期的直方圖的例子,在其基礎上擴充幾點:第一就是點擊每個直方圖會有監聽回調的觸摸返回,并把當前點擊的直方圖數據信息顯示出來;第二就是在直方圖基礎上繪制了折線圖。
拋出一個思考的問題:對于一個自定義View如何給某個繪制區域設置監聽的點擊事件觸摸回調?
例子源碼下篇博客給出。