Canvas 在一般的情况下可以看作是一张画布,所有的绘图操作如drawBitmap, drawCircle都发生在这张画布上,这张画板还定义了一些属性比如Matrix,颜色等等。
但是如果需要实现一些相对复杂的绘图操作,比如多层动画,地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)。
Canvas提供了图层(Layer)支持,缺省情况可以看作是只有一个图层Layer。如果需要按层次来绘图,Android的Canvas可以使用SaveLayerXXX, Restore 来创建一些中间层,对于这些Layer是按照“栈结构“来管理的:
创建一个新的Layer到“栈”中,可以使用saveLayer, savaLayerAlpha, 从“栈”中推出一个Layer,可以使用restore,restoreToCount。但Layer入栈时,后续的DrawXXX操作都发生在这个Layer上,而Layer退栈时,就会把本层绘制的图像“绘制”到上层或是Canvas上,在复制Layer到Canvas上时,可以指定Layer的透明度(Layer),这是在创建Layer时指定的:
public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)
本例Layers 介绍了图层的基本用法:Canvas可以看做是由两个图层(Layer)构成的,为了更好的说明问题,我们将代码稍微修改一下,缺省图层绘制一个红色的圆,在新的图层画一个蓝色的圆,新图层的透明度为0×88。
1 | @Override protected void onDraw(Canvas canvas) { |
2 | canvas.drawColor(Color.WHITE); |
4 | canvas.translate( 10 , 10 ); |
6 | mPaint.setColor(Color.RED); |
7 | canvas.drawCircle( 75 , 75 , 75 , mPaint); |
9 | canvas.saveLayerAlpha( 0 , 0 , 200 , 200 , 0x88 , LAYER_FLAGS); |
12 | mPaint.setColor(Color.BLUE); |
13 | canvas.drawCircle( 125 , 125 , 75 , mPaint); |
在调用canvas.saveLayerAlpha 创建一个新图层之后,后续的canvas.drawCircle 都会发生的这个新图层上, canvas.restore() 将这个新图层绘制的图像“复制”到Canvas的缺省图层上,透明度为0×88。

public class Layers extends GraphicsActivity {
@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_FLAG |
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
Canvas.CLIP_TO_LAYER_SAVE_FLAG;
//ALL_SAVE_FLAG;
//该参数当画布执行restore 操作时,需要restore的状态属性分别对应:matrix,clip,alpha,color,越界的clip,everything
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);// 第二层的 透明度是0x88
mPaint.setColor(Color.BLUE);
canvas.drawCircle(125, 125, 75, mPaint);
canvas.restore(); // canvas.restore() 将这个新图层绘制的图像“复制”到Canvas的缺省图层上,透明度为0×88。
}
}
}
public class MeasureText extends GraphicsActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private Paint mPaint;
private float mOriginX = 10;
private float mOriginY = 80;
public SampleView(Context context) {
super(context);
setFocusable(true);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(5);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setTextSize(64);
mPaint.setTypeface(Typeface.create(Typeface.SERIF,
Typeface.ITALIC));
}
private void showText(Canvas canvas, String text, Paint.Align align) {
// mPaint.setTextAlign(align);
Rect bounds = new Rect();
float[] widths = new float[text.length()];
int count = mPaint.getTextWidths(text, 0, text.length(), widths);
float w = mPaint.measureText(text, 0, text.length());//使用paint 测量text的长度
mPaint.getTextBounds(text, 0, text.length(), bounds); //拿到字体的文字的矩形区域
mPaint.setColor(0xFF88FF88);
canvas.drawRect(bounds, mPaint);//绘制背景
mPaint.setColor(Color.BLACK);
canvas.drawText(text, 0, 0, mPaint);//绘制文字
float[] pts = new float[2 + count*2];
float x = 0;
float y = 0;
pts[0] = x;
pts[1] = y;
for (int i = 0; i < count; i++) {
x += widths[i];
pts[2 + i*2] = x;
pts[2 + i*2 + 1] = y;
}
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(0);
canvas.drawLine(0, 0, w, 0, mPaint);//绘制下线
mPaint.setStrokeWidth(5);
canvas.drawPoints(pts, 0, (count + 1) << 1, mPaint);//绘制下面的点号 pts就是位置 :x0 y0 x1y1 x2 y2 ...
}
@Override protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.translate(mOriginX, mOriginY);
showText(canvas, "Measure", Paint.Align.LEFT);
canvas.translate(0, 80);
showText(canvas, "wiggy!", Paint.Align.CENTER);
canvas.translate(0, 80);
showText(canvas, "Text", Paint.Align.RIGHT);
}
}
}
public class PathEffects extends GraphicsActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private Paint mPaint;
private Path mPath;
private PathEffect[] mEffects;
private int[] mColors;
private float mPhase;
private static void makeEffects(PathEffect[] e, float phase) {
e[0] = null; // no effect
e[1] = new CornerPathEffect(10); //柔化圆角CornerPathEffect 修改路径上线段连接处的显示模式(从尖角改为可以指定圆弧半径的圆角连接)。
//DashPathEffect 虚线线显示效果,使用数组来指定需线和间隔的长度。数组中序数(下标)为偶数表示虚线的实线长度,
//奇数为虚线的间隔长度。 只对Paint设为STROKE 或STROKE_AND_FILL时有效。
e[2] = new DashPathEffect(new float[] {10, 5, 5, 5}, phase);
e[3] = new PathDashPathEffect(makePathDash(), 12, phase,
PathDashPathEffect.Style.ROTATE);
e[4] = new ComposePathEffect(e[2], e[1]);
e[5] = new ComposePathEffect(e[3], e[1]);
// e[0] 不含任何PathEffect,缺省绘制Path的风格,单色实线,连接处为尖角。
// e[1] 连接使用圆弧连接。
// e[2] 使用缺省的虚线绘制路径。
// e[3] 使用自定义的图形(本例为一箭头)的虚线绘制路径
// e[4] 为使用e[2]和e[1]综合效果,虚线并圆弧连接。
// e[5] 为使用e[3]和e[1]综合效果,自定义虚线并圆弧连接。
// CornerPathEffect 修改路径上线段连接处的显示模式(从尖角改为可以指定圆弧半径的圆角连接)。
// DashPathEffect 虚线显示效果,使用数组来指定需线和间隔的长度。数组中序数为偶数表示虚线的实线长度,奇数为虚线的间隔长度。 只对Paint设为STROKE 或STROKE_AND_FILL时有效。
// PathDashPathEffect 和DashPathEffect类似显示虚线,但可以自定义虚线的模式(DashPathEffect使用的是矩形,而PathDashPathEffect允许使用Path对象自定义虚线的模式),同样只对Paint设为STROKE 或STROKE_AND_FILL时有效。
// ComposePathEffect 允许组合两个PathEffect (outer ,inner) , 分两次综合结果ComposePathEffect = Outer(inner(path)).
// SumPathEffect 组合两个两个PathEffect (first, second) SumPathEffect=first(path) + second(path).
// DiscretePathEffect 将路径划分成指定长度的线段,然后把每条线段随机偏移原来的位置。
}
public SampleView(Context context) {
super(context);
setFocusable(true);
setFocusableInTouchMode(true);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
mPath = makeFollowPath();
mEffects = new PathEffect[6];
mColors = new int[] { Color.BLACK, Color.RED, Color.BLUE,
Color.GREEN, Color.MAGENTA, Color.BLACK
};
}
@Override protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
RectF bounds = new RectF();
mPath.computeBounds(bounds, false);
canvas.translate(10 - bounds.left, 10 - bounds.top);
makeEffects(mEffects, mPhase);
mPhase += 1;
invalidate();
for (int i = 0; i < mEffects.length; i++) {
mPaint.setPathEffect(mEffects[i]);
mPaint.setColor(mColors[i]);
canvas.drawPath(mPath, mPaint);
canvas.translate(0, 28);
}
}
@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
mPath = makeFollowPath(); //按方向键上“中间键”将随机产生一个新的绘制路径。
return true;
}
return super.onKeyDown(keyCode, event);
}
private static Path makeFollowPath() { //绘制 路径 android.graphics.Path允许使用线段,二次曲线,三次曲线构成路径(类似SVG)
Path p = new Path();
p.moveTo(0, 0);
for (int i = 1; i <= 15; i++) {
p.lineTo(i*20, (float)Math.random() * 35);
}
return p;
}
private static Path makePathDash() {// makePathDash定义了自定义虚线类型上的小箭头图案:
Path p = new Path();
p.moveTo(4, 0);
p.lineTo(0, -4);
p.lineTo(8, -4);
p.lineTo(12, 0);
p.lineTo(8, 4);
p.lineTo(0, 4);
return p;
}
}
}