Android Canvas用法之save、restore和saveLayer

本文跟上一篇博文类似,也是介绍自定义控件的相关内容,主要是onDraw()中常使用的方法。

主要涉及三个方法:save()、restore()、saveLayer(),当然了也会涉及到其它的方法,如restoreToCout()、setXfermode()。

重点需要掌握如何使用save()、restore()、saveLayer(),文中会通过示例代码以及运行后截图来对比展示,如果使用save()和restore()是什么效果,不使用运行效果又是如何。

save方法

public int save() {
	return nSave(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
}

一般save()和restore()都是成对出现的,还有一个很相似的方法saveLayer()方法,这个方法稍后介绍。

save()方法会保存当前Canvas的matrix和clip到一个私有栈中,save方法调用之后,仍然可以像平常一样调用translate,scale,rotate,skew,concat或者clipRect,clipPath方法,但是如果稍后调用了restore()方法,那么之前调用的方法并不会影响之后的操作,会将canvas恢复至save之前的状态。可以理解为,save之后的操作都是在新的图层上面进行的操作,所有的操作并不影响当前的图层。准确来说,这里保存的并不是一个新的图层,saveLayer()方法才会开启一个新的图层,所以相对于save方法而言,saveLayer()方法更耗性能。

从上面方法源码可以知道,save()方法是由返回值的,方法注释介绍很简单:可以通过restoreToCount方法返回到save之前的状态。save()方法是可以多次调用的,每调用一次,栈的深度就会+1,可以通过getSaveCount()方法获取当前栈的深度。

restore和restoreToCount方法

public void restore() {
	if (!nRestore(mNativeCanvasWrapper)
			&& (!sCompatibilityRestore || !isHardwareAccelerated())) {
		throw new IllegalStateException("Underflow in restore - more restores than saves");
	}
}
public void restoreToCount(int saveCount) {
	if (saveCount < 1) {
		if (!sCompatibilityRestore || !isHardwareAccelerated()) {
			// do nothing and throw without restoring
			throw new IllegalArgumentException(
					"Underflow in restoreToCount - more restores than saves");
		}
		// compat behavior - restore as far as possible
		saveCount = 1;
	}
	nRestoreToCount(mNativeCanvasWrapper, saveCount);
}

restore()和restoreToCount()都可以返回到save()之前的Canvas的状态,差异性也很明显,restore()只能一次弹出一个save()方法的状态栈,但是restoreToCount()可以一次性弹出saveCount个栈,当然了saveCount也是有限制的,saveCount不能<1,其次不能大于已保存的栈的深度。

上面介绍save()方法时说,一般save和restore方法成对出现,当然了也可以不成对出现,但是必须是save的操作比restore的次数多,如果restore调用次数比save多,会引发如下Error:

java.lang.IllegalStateException: Underflow in restore - more restores than saves
	at android.graphics.Canvas.restore(Canvas.java:580)
	at com.sunny.surface.view.MyCanvasView.onDraw(MyCanvasView.java:50)
	at android.view.View.draw(View.java:20373)
	at android.view.View.updateDisplayListIfDirty(View.java:19318)
	at android.view.View.draw(View.java:20096)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4421)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4207)
	at android.view.View.updateDisplayListIfDirty(View.java:19309)
	at android.view.View.draw(View.java:20096)

save和restore代码示例

public MyCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	paint = new Paint();
	paint.setAntiAlias(true);
	paint.setColor(Color.RED);
	paint.setStrokeWidth(5);
	paint.setStyle(Paint.Style.STROKE);

	rect = new Rect(10, 10, 400, 400);
}

@Override
protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	Log.d(TAG, "onDraw: " + canvas.getSaveCount());// 1
	canvas.drawRect(rect, paint);
	int count = canvas.save();
	Log.d(TAG, "onDraw count: " + count);// 1
	canvas.translate(100,100);
	paint.setColor(Color.BLUE);
	canvas.drawRect(rect, paint);
	Log.d(TAG, "onDraw: " + canvas.getSaveCount());// 2
	canvas.restore();
	canvas.translate(150,150);
	paint.setColor(Color.GREEN);
	canvas.drawRect(rect, paint);
	Log.d(TAG, "onDraw: " + canvas.getSaveCount());// 1
}

如下是使用save、restore方法和不使用的区别。

saveLayer方法

Canvas在一般的情况下可以看作是一张画布,所有的绘图操作如drawBitmap,drawCircle都发生在这张画布上,这张画板还定义了一些属性比如Matrix,颜色等等。但是如果需要实现一些相对复杂的绘图操作,比如多层动画,地图(地图可以有多个地图层叠加而成,比如:政区层,道路层,兴趣点层)。Canvas提供了图层(Layer)支持,缺省情况可以看作是只有一个图层Layer。如果需要按层次来绘图,Android的Canvas可以使用saveLayer、restore 来创建一些中间层,对于这些Layer是按照“栈结构“来管理的:

saveLayer()类似save()方法,但是它会生成一个offscreen bitmap,之后的所有操作都是在这个新的offscreen bitmap上。如果熟悉PS的话,可以理解为这里生成了一个新的图层。savelayer()这是一个非常耗费性能的方法,会导致绘制相同的内容渲染时耗费两倍多的资源。当需要的形状很大时(超屏幕)禁止使用这个方法。当自定义View使用硬件加速时推荐使用xfermode ,color filter或者alpha,而不推荐使用saveLayer()方法。

saveLayer与save的不同点

  • saveLayer()是生成一个独立的图层,而save()只是保存了一下画布的状态,这里说的画布的状态类似一个还原点。
  • saveLayer()由于会生成一个新的图层,所以更加耗费内存,需要慎用。
  • saveLayer()可以保存特定的区域。
  • 在使用混合模式setXfermode时会产生不同的影响。

saveLayer代码示例

public MyCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	paint = new Paint();
	paint.setAntiAlias(true);
	paint.setColor(Color.RED);

	rect = new RectF(0, 0, 400, 400);
	bm = BitmapFactory.decodeResource(context.getResources(), R.drawable.image);
}

@Override
protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	Log.d(TAG, "onDraw: " + canvas.getSaveCount());// 1
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
		saveCount = canvas.saveLayer(rect, paint);
	} else {
		saveCount = canvas.saveLayer(rect, paint, Canvas.ALL_SAVE_FLAG);
	}
	Log.d(TAG, "onDraw: " + canvas.getSaveCount() + "  " + saveCount);
	canvas.drawColor(Color.RED);
	canvas.drawBitmap(bm, 200, 200, paint);
	canvas.restore();
	canvas.drawBitmap(bm, 400, 400, paint);
	Log.d(TAG, "onDraw: " + canvas.getSaveCount());// 1
}

如下是使用saveLayer方法和不使用的区别。

saveLayer与xfermode示例

这示例很简单,就是取两个绘制图像的交汇上图层部分。如果想了解更多,可以参看Google官方有关于xfermode很详细的示例。

public MyCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
	super(context, attrs, defStyleAttr);
	paint = new Paint();
	paint.setStyle(Paint.Style.FILL);
	paint.setColor(Color.RED);
	paint.setAntiAlias(true);
	paint.setDither(true);
	paint.setFilterBitmap(true);

	xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
	rect = new RectF(0, 0, 400, 400);
	rectArea = new RectF(0, 0, 600, 600);
}

@Override
protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	Log.d(TAG, "onDraw01: " + canvas.getSaveCount()); //1
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
		saveCount = canvas.saveLayer(rectArea, paint);
	} else {
		saveCount = canvas.saveLayer(rectArea, paint, Canvas.ALL_SAVE_FLAG);
	}
	canvas.drawRect(rect, paint);
	paint.setXfermode(xfermode);
	paint.setColor(Color.BLUE);
	canvas.drawBitmap(makeSrc(), 0, 0, paint);
	paint.setXfermode(null);
	Log.d(TAG, "onDraw02: " + canvas.getSaveCount());//2
	canvas.restoreToCount(saveCount);
	Log.d(TAG, "onDraw03: " + canvas.getSaveCount());//1
}

private Bitmap makeSrc() {
	Bitmap bm = Bitmap.createBitmap(600, 600, Bitmap.Config.ARGB_8888);
	Canvas c = new Canvas(bm);
	Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
	p.setColor(Color.BLUE);
	c.drawCircle(400, 400, 200, p);
	return bm;
}

小结

本文介绍的内容不多,知道save()和restore()方法一般是成对出现,当然了,如果不成对出现,必须确保save()方法的使用次数多于restore()方法,否则一定会抛出异常,导致程序异常。还需要了解restoreToCount()方法的使用,该方法可以将指定saveCount个栈一次性弹出,所以使用上面也相当便捷。最后介绍了saveLayer和xfermode的使用,其实这里是最复杂的,有很多开发人员仿写Google官方的示例时,第一次运行时可能就出问题,需要注意setXfermode的时机,以及绘制图层时的先后顺序。

另外,需要知道自定义View绘制时的双缓冲机制和offscreen bitmap,saveLayer()方法就是使用了offscreen bitmap,而双缓冲机制就是先将图片绘制到内存中,然后在一次性绘制在画布上。

评论

您确定要删除吗?删除之后不可恢复