画布 Canvus

canvas.drawARGB(255,0,0,255);

canvas.drawRGB(123,34,45);

canvas.drawColor(Color.parseColor(“#343434”));

canvas.drawCircle(400,800,200,mPaint);

public void drawArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean useCenter, @NonNull Paint paint)

canvas.drawArc(200,200,1000,1000,-90,90,false,mPaint);

canvas.drawArc(200,200,1000,1000,-90,90,true,mPaint);

canvas.drawPoint(100,100,mPaint);
canvas.drawPoints(new float[]{100,100,300,300,400,400},mPaint);
canvas.drawLine(100,100,400,400,mPaint);

canvas.drawLines(new float[]{100,100,400,600,500,100,200,200},mPaint);
//这里需要注意的是点坐标数,必须是4的倍数

canvas.drawOval(200,400,800,800,mPaint);

canvas.drawRect(100,100,600,600,mPaint);

canvas.drawRoundRect(100,100,400,400,100,60,mPaint);

mPaint.setTextSize(100);
canvas.drawText(“Hello Android”,300,400,mPaint);

Path path = new Path();
path.moveTo(200,200);
path.addRect(200,200,400,400, Path.Direction.CW);
path.addArc(200,200,800,800,100,300);
canvas.drawPath(path,mPaint);

canvaus 画布操作:

Android 使用Layer堆栈来管理画布的,画布有多个图层,整个结构如下所示,下面就针对图层操作做将要介绍:

Canvas.save()  用来保存当前的matrix和clip到私有的栈中。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
任何matrix变换和clip操作都会在调用restore的时候还原,它有一个整形返回值可以传入到restoreToCount()方法,以返回到某个save状态之前。也可以直接调用restore返回到前一个状态。
Canvas.restore() 将save之前的所有操作和save之后的所有操作合并,它只回到上一个save调用之前的状态。
save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。
restoreToCount() 回到任何一个save()方法调用之前的状态
Canvas.getSaveCount(); 返回栈中保存的状态,值等于save()调用次数-restore()调用次数

Canvas.translate() 平移画布
Canvas.rotate() 旋转画布
Canvas.saveLayer(),saveLayerAlpha() 将图层入栈
本身和save方法差不多,但是它单独分配了一个画布用于绘制图层。它定义了一个画布区域(saveLayerAlpha可设置透明度),此方法之后的所有绘制都在此区域中绘制,直到调用canvas.restore()方法。
Layer入栈时,后续的操作都发生在这个Layer上,而Layer退栈时,就会把本层绘制的图像“绘制”到上层.在绘制到上层或是Canvas上时,可以指定Layer的透明度

Canvas && SurfaceView

在使用上面介绍的方式在View上绘图的时候,它是在GUI线程中绘制的,在GUI线程中同时也用来处理用户的交互事件,这是不大合理的,单绘制的元素太多时候,会阻塞线程,轻者影响用户体验,重则倒置ANR。
也许大家会想到将onDraw方法移到后台,但是这种方式是可行的,从后台线程修改GUI元素是不允许的。
并且我们知道View是通过系统发出VSYNC信号进行屏幕重绘的,一般刷新时间间隔为16ms,如果在16ms内可以完成所有的操作,那么将不会带来卡顿感觉,而如果超过这个时间还不能完成绘制任务,就会导致GUI线程的阻塞。
当需要快速更新View的UI,或者当前绘制阻塞GUI线程时间过长的时候,应该使用Surfaceview而不是View。SurfaceView封装的是Surface而不是Canvas。这一切原因是由于Surface使用后台线程进行绘制,而Canvase不能。
但是大家有时候会感到奇怪,为什么我们看到自定义View很多都是继承View而不是SurfaceView,这是因为SurfaceView使用的是后台线程进行绘制的,这回增加内存的消耗。所以一般能在16ms内完成绘制的话就直接继承View

所以最好在绘制高速图像,或者绘制工作不能在规定时间完成,又或者需要绘制3D图形的情况下才需要使用SurfaceView。

下面我们就来介绍下SurfaceView的用法:
在介绍SurfaceView的用法之前我们先了解下Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback。

如上面所述SurfaceView提供了一个专门用于绘制的surface。它允许在后台线程中进行绘制,你可以通过SurfaceHolder控制这个Surface的格式和尺寸。Surfaceview控制让这个Surface显示在屏幕的正确绘制位置。
Surface是Z-ordered(Z轴排序),这表明它总在自己所在窗口的后面。surfaceview在显示窗口处为Surface提供了一个可见区域,通过这个区域,才能看到Surface里面的内容。
由于这个特性我们可以放置一些覆盖图层(overlays)在Surface上面,如Button、Textview之类的。但是,需要注意的是,如果Surface上面有全透明的控件,那么随着Surface的每一次变化,这些全透明的控件就会重新渲染,这样有可能会增加系统的负担。

可以通过SurfaceHolder这个接口去访问Surface,而执行getHolder()方法可以得到SurfaceHolder接口。
SurfaceHolder是控制surface的一个抽象接口,它是一个句柄得到了这个句柄就可以得到其中的Canvas、原生缓冲器以及其它方面的内容。你可以通过SurfaceHolder来控制surface的尺寸和格式,或者修改surface的像素,监视surface的变化等等。
与直接控制SurfaceView来修改surface不同,使用SurfaceHolder来修改surface时,需要注意lockCanvas()和Callback.surfaceCreated()这两个方法。
SurfaceView有如下重要方法

abstract void addCallback(SurfaceHolder.Callback callback) 给SurfaceHolder添加一个一个回调对象。用于监视SurfaceView的某些状态,可以在这些回调方法中对某个特定事件发生的时候进行必要的处理
SurfaceHolder.Callback将会在后面进行介绍
abstract void removeCallback(SurfaceHolder.Callback callback) 移除回调对象

abstract Canvas lockCanvas(Rect dirty)
锁定画布中的某一个区域,返回的画布对象Canvas(当更新的内容只有一个区域时,同时要追求高效,可以只更新一部分的区域,而不必更新全部画布区域)

abstract Canvas lockCanvas() 锁定画布,返回的画布对象Canvas,和上面不同的是这里锁定的是整个画布
abstract void unlockCanvasAndPost(Canvas canvas) 结束锁定画布,并提交改变。

SurfaceHolder.Callback是监听surface改变的一个接口

public abstract void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
surface发生改变时被调用,传入的是新Surface的新尺寸

public abstract void surfaceCreated(SurfaceHolder holder)

在surface创建时被调用,一般在这个方法里面开启渲染屏幕的线程。

public abstract voidsurfaceDestroyed(SurfaceHolder holder)
销毁时被调用,一般在这个方法里将渲染的线程停止。

所有SurfaceView 和 SurfaceHolder.Callback的方法都应该在主线程(UI线程)里面调用。必须确保只有当Surface有效的时候,(也就是当Surface的生命周期在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间)才能让渲染进程访问。

下面是这几个对象的关系图

SurfaceView 绘图的模板:

public class surfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

private Context mContext = null;
private SurfaceHolder mHolder = null;
private Canvas mCanvas = null;
private boolean surfaceViewStatus = false;
private Thread mDrawThread = null;
private void init(Context context) {
mContext = context;
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
setKeepScreenOn(true);
}

public surfaceView(Context context) {
super(context);
init(context);
}

public surfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}

public surfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceViewStatus = true;
if(mDrawThread == null) {
mDrawThread = new Thread(this);
}
mDrawThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceViewStatus = false;
if(mDrawThread != null) {
try {
mDrawThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mDrawThread = null;
}
}

@Override
public void run() {
while (surfaceViewStatus) {
try {
mCanvas = mHolder.lockCanvas();
//在这里画图
}finally {
if(mCanvas != null) {
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
}
Contents
  1. 1. 画布 Canvus
  2. 2. Canvas && SurfaceView
    1. 2.0.1. 下面是这几个对象的关系图