View与SurfaceView
SurfaceView理解
surface可以这样理解:它是内存中一块区域,它是surfaceview不可见那个部分,绘图操作作用于它,然后它就会被显卡之类的显示控制器绘制到屏幕上。
surface是个啥,大概已经有了些概念了。因为它对应了一个内存区,大家都知道,内存区的对象是有生命周期的,可以动态的申请创建和销毁,当然也可能会更新。于是,就有了作用于这个内存区的操作,这些操作就是surfaceCreated/Changed/Destroyed。三个操作放在一起,就是callback,
所以在很多例子里看到,会有callback。
SurfaceView 与 View的比较
- 双缓冲
- View适用主动更新,SurfaceView 适用被动更新,如频繁的刷新
- View主线程 SurfaceView子线程刷新(需要界面迅速更新、对帧率要求较高的情况)
双缓冲
在运用时可以理解为:SurfaceView在更新视图时用到了两张Canvas,一张frontCanvas和一张backCanvas,每次实际显示的是frontCanvas,backCanvas存储的是上一次更改前的视图,当使用lockCanvas()获取画布时,得到的实际上是backCanvas而不是正在显示的frontCanvas,之后你在获取到的backCanvas上绘制新视图,再unlockCanvasAndPost(canvas)此视图,那么上传的这张canvas将替换原来的frontCanvas作为新的frontCanvas,原来的frontCanvas将切换到后台作为backCanvas。例如,如果你已经先后两次绘制了视图A和B,那么你调用lockCanvas()获取视图,获得的将是A而不是正在显示的B,之后你讲重绘的C视图上传,那么C将取代B作为新的frontCanvas显示在SurfaceView上,原来的B则转换为backCanvas。
SurfaceView模板
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class SurfaceViewTemplate extends SurfaceView
implements SurfaceHolder.Callback, Runnable {
// SurfaceHolder
private SurfaceHolder mHolder;
// 用于绘图的Canvas
private Canvas mCanvas;
// 子线程标志位
private boolean mIsDrawing;
public SurfaceViewTemplate(Context context) {
super(context);
initView();
}
public SurfaceViewTemplate(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SurfaceViewTemplate(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
//mHolder.setFormat(PixelFormat.OPAQUE);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
while (mIsDrawing) {
draw();
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
// draw sth
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
原理分析
-
SurfaceView的绘图表面的创建过程
由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们首先要将它的绘图表面创建出来。尽管SurfaceView不与它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图结构的一个结点的,也就是说,SurfaceView仍然是会参与到宿主窗口的某些执行流程中去。
SurfaceView 示例
1.画正弦函数
sin.gif思路很简单:
初始化一个Path,每次draw函数都会重新计算x,y的值,通过Path的moveTo方法,然后通过Canvas的drawPath得到曲线。
public class SinView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean mIsDrawing;
private int x = 0;
private int y = 0;
private Path mPath;
private Paint mPaint;
public SinView(Context context) {
super(context);
initView();
}
public SinView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SinView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
mPath = new Path();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(10);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
mPath.moveTo(0, 400);
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
while (mIsDrawing) {
draw();
x += 3;
y = (int) (100*Math.sin(x * Math.PI / 180) + 400);
mPath.lineTo(x, y);
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
// SurfaceView背景
mCanvas.drawColor(Color.WHITE);
mCanvas.drawPath(mPath, mPaint);
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
//使用
setContentView(new SinView(this));
很简单,在SurfaceView模板基础上增加了Path和x,y的控制!
2.按照Path Segment 加入动画绘制
这个示例的View不是继承SurfaceView
我们只看第二行第一个如何绘制的,其他都一样
public class PathView1 extends View {
//一个开源类,原理后面讲
PathAnimator mPathAnimator;
int w,h;
Path mPath;
Paint mPaint = new Paint();
public PathView1(Context context) {
super(context);
}
public PathView1(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PathView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
this.w = w;
this.h = h;
init();
super.onSizeChanged(w, h, oldw, oldh);
}
private void init(){
//画笔颜色
mPaint.setStrokeWidth(20);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.GREEN);
mPaint.setAntiAlias(true);
//要绘制的路径
Path path = new Path();
path.addCircle(w / 2, h / 2, w / 2 - 20, Path.Direction.CCW);
path.moveTo(w / 2 - 60, h / 2 + 10);
path.lineTo(w / 2 - 30, h / 2 + 50);
path.lineTo(w / 2 + 50, h / 2 - 60);
//路径动画
mPathAnimator = new PathAnimator(path);
mPathAnimator.setDuration(3000);//动画时间
mPathAnimator.startDelay(1000);
mPathAnimator.addUpdateListener(new PathAnimator.PathAnimatorUpdateListener() {
@Override
public void onAnimationUpdate(float pathPrecent, Path path) {
mPath = path;//更新当前路径
invalidate();
}
});
mPathAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
if(mPath!=null){
canvas.drawPath(mPath,mPaint);
}
}
}
PathAnimator 原理
- 通过构造方法,传入一个Path进去。
public PathAnimator(Path path){
mPath = path;
mPathMeasure = new PathMeasure(mPath,false);
/**
* 初始化路径
* 将复合路径分割保存到list.
* 记录路径长度
*/
initPath();
//路径动画,能够根据当前时间,计算出当前运动路径
initAnim();
}
具体源码请看:https://github.com/jacky1234/PathAnimator