1.在自定義控件當(dāng)中你onMeasure和onLayout的工作做完成以后就該繪制該控件了,有時候需要自己在控件上添加一些修飾來滿足需求
復(fù)寫onDraw(Canvas canvas),其中Canvas就像是一塊畫布,你自定義控件的樣式就是在它上面完成的。
Canvas ,Paint等基本概念就不贅述了。
2.下面就直接用demo來解釋標題列出的方法先介紹save()和saveLayer()
save(): 用來保存Canvas的狀態(tài),save()方法之后的代碼,可以調(diào)用Canvas的平移、放縮、旋轉(zhuǎn)、裁剪等操作!
restore():用來恢復(fù)Canvas之前保存的狀態(tài)(可以想成是保存坐標軸的狀態(tài)),防止save()方法代碼之后對Canvas執(zhí)行的操作,繼續(xù)對后續(xù)的繪制會產(chǎn)生影響,通過該方法可以避免連帶的影響
通過一個例子說明一下:
例如:我們想在畫布上繪制一個向右的三角箭頭,當(dāng)然,我們可以直接繪制,另外,我們也可以先把畫布旋轉(zhuǎn)90°,畫一個向上的箭頭,然后再旋轉(zhuǎn)回來(這種旋轉(zhuǎn)操作對于畫圓周上的標記非常有用),最后,我們在右下角繪一個20像素的圓!
網(wǎng)上對這個問題的解決說是旋轉(zhuǎn)回來,我的感覺其實save()保存的就是Canvas中坐標軸的狀態(tài)。
MyView:
public class MyView extends View {
public final static String TAG = "Example";
private Paint mPaint = null;
public MyView(Context context) {
super(context);
mPaint = new Paint();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint background = new Paint();
Paint line = new Paint();
line.setStrokeWidth(4);
background.setColor(Color.GRAY);
line.setColor(Color.RED);
int px = 500;
int py = 500;
canvas.drawRect(0, 0, px, py, background);
canvas.save();
canvas.rotate(90, px / 2, py / 2);
// 畫一個向上的箭頭
canvas.drawLine(px / 2, 0, 0, py / 2, line);
// 左邊的斜杠
canvas.drawLine(px / 2, 0, px, py / 2, line);
// 右邊的斜杠
canvas.drawLine(px / 2, 0, px / 2, py, line);
// 垂直的豎杠
canvas.restore();
canvas.drawCircle(px - 100, py - 100, 50, line);
}
}
運行后的效果是:
將canvas.save()和canvas.restore()這兩行代碼注釋掉以后運行的效果是:
為什么有這種差異出現(xiàn)呢?
在 canvas.save()之前,Canvas的坐標軸是:
save()之后就是把這種狀態(tài)的坐標軸狀態(tài)保存了下來,
canvas.rotate(90, px / 2, py / 2)圍著圓心旋轉(zhuǎn)之后,坐標軸變成:
canvas.drawLine(px / 2, 0, 0, py / 2, line); // 左邊的斜杠
canvas.drawLine(px / 2, 0, px, py / 2, line);// 右邊的斜杠 canvas.drawLine(px / 2, 0, px / 2, py, line);// 垂直的豎杠
這一些列畫圖操作時在變換后的坐標軸上畫出來的,所以是一個往右方向的箭頭。
當(dāng)調(diào)用canvas.restore()后坐標軸恢復(fù)到canvas.save()之前的狀態(tài)。所以canvas.drawCircle(px - 100, py - 100, 50, line)參考的坐標軸是cnavas.save()之前的坐標軸。
這樣也就能說通為什么不用Canvas.save()和用Canvas.save()圓圈位置為什么不相同的原因了。
saveLayer
Canvas 在一般的情況下可以看作是一張畫布,所有的繪圖操作如drawBitmap, drawCircle都發(fā)生在這張畫布上,這張畫板還定義了一些屬性比如Matrix,顏色等等。但是如果需要實現(xiàn)一些相對復(fù)雜的繪圖操作,比如多層動 畫,地圖(地圖可以有多個地圖層疊加而成,比如:政區(qū)層,道路層,興趣點層)。Canvas提供了圖層(Layer)支持,缺省情況可以看作是只有一個圖 層Layer。如果需要按層次來繪圖,Android的Canvas可以使用SaveLayerXXX, Restore 來創(chuàng)建一些中間層,對于這些Layer是按照“棧結(jié)構(gòu)“來管理的:
創(chuàng)建一個新的Layer到“棧”中,可以使用saveLayer, savaLayerAlpha, 從“棧”中推出一個Layer,可以使用restore,restoreToCount。但Layer入棧時,后續(xù)的DrawXXX操作都發(fā)生在這個 Layer上,而Layer退棧時,就會把本層繪制的圖像“繪制”到上層或是Canvas上,在復(fù)制Layer到Canvas上時,可以指定Layer的 透明度(Layer),這是在創(chuàng)建Layer時指定的:public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)本例Layers 介紹了圖層的基本用法:Canvas可以看做是由兩個圖層(Layer)構(gòu)成的,為了更好的說明問題,我們將代碼稍微修改一下,缺省圖層繪制一個紅色的 圓,在新的圖層畫一個藍色的圓,新圖層的透明度為0×88。
public class Layers extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG |
Canvas.CLIP_SAVE_FLAG11 | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG
| Canvas.CLIP_TO_LAYER_SAVE_FLAG;
private Paint mPaint;
public SampleView(Context context) {
super(context);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.translate(10, 10);
mPaint.setColor(Color.RED);
canvas.drawCircle(75, 75, 75, mPaint);
canvas.saveLayerAlpha(0, 0, 200, 200, 0x88, LAYER_FLAGS);
mPaint.setColor(Color.BLUE);
canvas.drawCircle(125, 125, 75, mPaint);
canvas.restore();
}
}
}
分析:canvas.saveLayerAlpha(0, 0, 200, 200, 0x88, LAYER_FLAGS)將一個layer推入棧中,后續(xù)的
mPaint.setColor(Color.BLUE);
canvas.drawCircle(125, 125, 75, mPaint);
畫一個藍色圓是在這個layer中畫的,和之前畫紅色圓的不是同一個layer層。
在canvas.restore()被保存的layer就在紅色圓layer上面了。
效果圖是: