涉及知識(shí)
繪制過(guò)程
類別 |
API |
描述 |
布局 |
onMeasure |
測(cè)量View與Child View的大小 |
|
onLayout |
確定Child View的位置 |
|
onSizeChanged |
確定View的大小 |
繪制 |
onDraw |
實(shí)際繪制View的內(nèi)容 |
事件處理 |
onTouchEvent |
處理屏幕觸摸事件 |
重繪 |
invalidate |
調(diào)用onDraw方法,重繪View中變化的部分 |
(如果對(duì)繪制過(guò)程與構(gòu)造函數(shù)還不了解的,請(qǐng)查看我之前文章 自定義View——Android坐標(biāo)系與View繪制流程 )
Canvas涉及方法
類別 |
API |
描述 |
繪制圖形 |
drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc |
依次為繪制點(diǎn)、直線、矩形、圓角矩形、橢圓、圓、扇形 |
繪制文本 |
drawText, drawPosText, drawTextOnPath |
依次為繪制文字、指定每個(gè)字符位置繪制文字、根據(jù)路徑繪制文字 |
畫(huà)布變換 |
translate, scale, rotate, skew |
依次為平移、縮放、旋轉(zhuǎn)、傾斜(錯(cuò)切) |
畫(huà)布裁剪 |
clipPath, clipRect, clipRegion |
依次為按路徑、按矩形、按區(qū)域?qū)Ξ?huà)布進(jìn)行裁剪 |
畫(huà)布狀態(tài) |
save,restore |
保存當(dāng)前畫(huà)布狀態(tài),恢復(fù)之前保存的畫(huà)布 |
Paint涉及方法
類別 |
API |
描述 |
顏色 |
setColor,setARGB,setAlpha |
依次為設(shè)置畫(huà)筆顏色、透明度 |
類型 |
setStyle |
填充(FILL),描邊(STROKE),填充加描邊(FILL_AND_STROKE) |
抗鋸齒 |
setAntiAlias |
畫(huà)筆是否抗鋸齒 |
字體大小 |
setTextSize |
設(shè)置字體大小 |
字體測(cè)量 |
getFontMetrics(),getFontMetricsInt() |
返回字體的測(cè)量,返回值一次為float、int |
文字寬度 |
measureText |
返回文字的寬度 |
文字對(duì)齊方式 |
setTextAlign |
左對(duì)齊(LEFT),居中對(duì)齊(CENTER),右對(duì)齊(RIGHT) |
寬度 |
setStrokeWidth |
設(shè)置畫(huà)筆寬度 |
筆鋒 |
setStrokeCap |
默認(rèn)(BUTT),半圓形(ROUND),方形(SQUARE) |
(PS: 因API較多,只列出了涉及的方法,想了解更多,請(qǐng)查看官方文檔)
(注意: 以下的代碼中未指定函數(shù)名的都是在onDraw函數(shù)中進(jìn)行使用,同時(shí)為了演示方便,在onDraw中使用了一些new方法,請(qǐng)?jiān)趯?shí)際使用中不要這樣做,因?yàn)閛nDraw函數(shù)是經(jīng)常需要重新運(yùn)行的)
一、Canvas
1、創(chuàng)建畫(huà)筆
創(chuàng)建畫(huà)筆并初始化
//創(chuàng)建畫(huà)筆
private Paint mPaint = new Paint();
private void initPaint(){
//初始化畫(huà)筆
mPaint.setStyle(Paint.Style.FILL);//設(shè)置畫(huà)筆類型
mPaint.setAntiAlias(true);//抗鋸齒
}
2、繪制坐標(biāo)軸
使用onSizeChanged方法,獲取根據(jù)父布局等因素確認(rèn)的View寬高
//寬高
private int mWidth;
private int mHeight;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
把原點(diǎn)從左上角移動(dòng)到畫(huà)布中心
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mWidth/2,mHeight/2);// 將畫(huà)布坐標(biāo)原點(diǎn)移動(dòng)到中心位置
}
繪制坐標(biāo)原點(diǎn)
//繪制坐標(biāo)原點(diǎn)
mPaint.setColor(Color.BLACK);//設(shè)置畫(huà)筆顏色
mPaint.setStrokeWidth(10);//為了看得清楚,設(shè)置了較大的畫(huà)筆寬度
canvas.drawPoint(0,0,mPaint);
繪制坐標(biāo)系的4個(gè)端點(diǎn),一次繪制多個(gè)點(diǎn)
//繪制坐標(biāo)軸4個(gè)斷點(diǎn)
canvas.drawPoints(new float[]{
mWidth/2*0.8f,0
,0,mHeight/2*0.8f
,-mWidth/2*0.8f,0
,0,-mHeight/2*0.8f},mPaint);
繪制坐標(biāo)軸
mPaint.setStrokeWidth(1);//恢復(fù)畫(huà)筆默認(rèn)寬度
//繪制X軸
canvas.drawLine(-mWidth/2*0.8f,0,mWidth/2*0.8f,0,mPaint);
//繪制Y軸
canvas.drawLine(0,mHeight/2*0.8f,0,mHeight/2*0.8f,mPaint);
繪制坐標(biāo)軸箭頭,一次繪制多條線
mPaint.setStrokeWidth(3);
//繪制X軸箭頭
canvas.drawLines(new float[]{
mWidth/2*0.8f,0,mWidth/2*0.8f*0.95f,-mWidth/2*0.8f*0.05f, mWidth/2*0.8f,0,mWidth/2*0.8f*0.95f,mWidth/2*0.8f*0.05f
},mPaint);
//繪制Y軸箭頭
canvas.drawLines(new float[]{
0,mHeight/2*0.8f,mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f,
0,mHeight/2*0.8f,-mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f,
},mPaint);
canvas.scale(1,-1);//翻轉(zhuǎn)Y軸
3、畫(huà)布變換
繪制矩形
//繪制矩形
mPaint.setStyle(Paint.Style.STROKE);//設(shè)置畫(huà)筆類型
canvas.drawRect(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8,mPaint);
平移,同時(shí)使用 new Rect 方法設(shè)置矩形
canvas.translate(200,200);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);
縮放
canvas.scale(0.5f,0.5f);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);
旋轉(zhuǎn)
canvas.rotate(90);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);
錯(cuò)切
canvas.skew(1,0.5f);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);
4、畫(huà)布的保存和恢復(fù)
save():用于保存canvas的狀態(tài),之后可以調(diào)用canvas的平移、旋轉(zhuǎn)、縮放、錯(cuò)切、裁剪等操作。restore():在save之后調(diào)用,用于恢復(fù)之前保存的畫(huà)布狀態(tài),從而在之后的操作中忽略save與restore之間的畫(huà)布變化。
float point = Math.min(mWidth,mHeight)*0.06f/2;
float r = point*(float) Math.sqrt(2);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.BLACK);
canvas.save();
canvas.rotate(90);
canvas.drawCircle(200,0,r,mPaint);//圓心(200,0)
canvas.restore();
mPaint.setColor(Color.BLUE);
canvas.drawCircle(200,0,r,mPaint);//圓心(200,0)
保存畫(huà)布,旋轉(zhuǎn)90°,繪制一個(gè)圓,之后恢復(fù)畫(huà)布,使用相同參數(shù)再繪制一個(gè)圓。可以看到在恢復(fù)畫(huà)布前后,相同參數(shù)繪制的圓,分別顯示在了坐標(biāo)系的不同位置。
二、豆瓣加載動(dòng)畫(huà)
繪制2個(gè)點(diǎn)和一個(gè)半圓弧
mPaint.setStyle(Paint.Style.STROKE);//設(shè)置畫(huà)筆樣式為描邊,如果已經(jīng)設(shè)置,可以忽略
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(10);
float point = Math.min(mWidth,mHeight)*0.2f/2;
float r = point*(float) Math.sqrt(2);
RectF rectF = new RectF(-r,-r,r,r);
canvas.drawArc(rectF,0,180,false,mPaint);
canvas.drawPoints(new float[]{
point,-point
,-point,-point
},mPaint);
但是豆瓣表情在旋轉(zhuǎn)的過(guò)程中,是一個(gè)鏈接著兩個(gè)點(diǎn)的270°的圓弧
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(10);
float point = Math.min(mWidth,mHeight)*0.2f/2;
float r = point*(float) Math.sqrt(2);
RectF rectF = new RectF(-r,-r,r,r);
canvas.drawArc(rectF,-180,270,false,mPaint);
這里使用ValueAnimator類,來(lái)進(jìn)行演示(實(shí)際上應(yīng)該是根據(jù)touch以及網(wǎng)絡(luò)情況來(lái)進(jìn)行加載的變化)
簡(jiǎn)單說(shuō)下ValueAnimator類:
API |
簡(jiǎn)介 |
ofFloat(float… values) |
構(gòu)建ValueAnimator,設(shè)置動(dòng)畫(huà)的浮點(diǎn)值,需要設(shè)置2個(gè)以上的值 |
setDuration(long duration) |
設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng),默認(rèn)的持續(xù)時(shí)間為300毫秒。 |
setInterpolator(TimeInterpolator value) |
設(shè)置動(dòng)畫(huà)的線性非線性運(yùn)動(dòng),默認(rèn)AccelerateDecelerateInterpolator |
addUpdateListener(ValueAnimator.AnimatorUpdateListener listener) |
監(jiān)聽(tīng)動(dòng)畫(huà)屬性每一幀的變化 |
分解步驟,計(jì)算一下總共需要的角度:
1、一個(gè)笑臉,x軸下方的圓弧旋轉(zhuǎn)135°,覆蓋2個(gè)點(diǎn),此過(guò)程中圓弧增加45°
2、畫(huà)布旋轉(zhuǎn)135°,此過(guò)程中圓弧增加45°
3、畫(huà)布旋轉(zhuǎn)360°,此過(guò)程中圓弧減少360/5度
4、畫(huà)布旋轉(zhuǎn)90°,此過(guò)程中圓弧減少90/5度
5、畫(huà)布旋轉(zhuǎn)135°,釋放覆蓋的2個(gè)點(diǎn)
動(dòng)畫(huà)部分:
private ValueAnimator animator;
private float animatedValue;
private long animatorDuration = 5000;
private TimeInterpolator timeInterpolator = new DecelerateInterpolator();
private void initAnimator(long duration){
if (animator !=null &&animator.isRunning()){
animator.cancel();
animator.start();
}else {
animator=ValueAnimator.ofFloat(0,855).setDuration(duration);
animator.setInterpolator(timeInterpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
}
表情部分:在繪制前最好使用sava()方法保存當(dāng)前的畫(huà)布狀態(tài),在結(jié)束后使用restore()恢復(fù)之前保存的狀態(tài)。為了是表情看上去更自然,所以減少10°的初始角度
private void doubanAnimator2(Canvas canvas, Paint mPaint){
mPaint.setStyle(Paint.Style.STROKE);//描邊
mPaint.setStrokeCap(Paint.Cap.ROUND);//圓角筆觸
mPaint.setColor(Color.rgb(97, 195, 109));
mPaint.setStrokeWidth(15);
float point = Math.min(mViewWidth,mViewWidth)*0.06f/2;
float r = point*(float) Math.sqrt(2);
RectF rectF = new RectF(-r,-r,r,r);
canvas.save();
// rotate
if (animatedValue>=135){
canvas.rotate(animatedValue-135);
}
// draw mouth
float startAngle=0, sweepAngle=0;
if (animatedValue<135){
startAngle = animatedValue +5;
sweepAngle = 170+animatedValue/3;
}else if (animatedValue<270){
startAngle = 135+5;
sweepAngle = 170+animatedValue/3;
}else if (animatedValue<630){
startAngle = 135+5;
sweepAngle = 260-(animatedValue-270)/5;
}else if (animatedValue<720){
startAngle = 135-(animatedValue-630)/2+5;
sweepAngle = 260-(animatedValue-270)/5;
}else{
startAngle = 135-(animatedValue-630)/2-(animatedValue-720)/6+5;
sweepAngle = 170;
}
canvas.drawArc(rectF,startAngle,sweepAngle,false,mPaint);
// draw eye
canvas.drawPoints(new float[]{
-point,-point
,point,-point
},mPaint);
canvas.restore();
}
在調(diào)試完成之后就可以刪除,坐標(biāo)系部分的代碼了
三、小結(jié)
本文介紹了canvas的變化,文中的不同部分穿插說(shuō)明了canvas繪制各種圖形的方法,以及結(jié)合ValueAnimator制作的豆瓣加載動(dòng)畫(huà)。之后的一篇文章會(huì)主要分析字符串的長(zhǎng)度和寬度,根據(jù)這些來(lái)參數(shù)調(diào)整字符串的位置,以達(dá)到居中等效果,再后一篇文章內(nèi)容應(yīng)該就會(huì)編寫(xiě) PieChart 了。如果在閱讀過(guò)程中,有任何疑問(wèn)與問(wèn)題,歡迎與我聯(lián)系。
GitHub: https://github.com/Idtk
博客:http://www.
郵箱: Idtkma@gmail.com