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