本篇我们来说说 SurfaceView 视图框架。
一、提出问题
SurfaceView 继承自 View ,但其实和 View 已经有了很大的区别,可以说这一层的继承,跨的有点大!来看这样一个情景:
在前一篇 视图框架系列1/3——View视图框架 中我们已经知道,利用 View 可以完成动画效果的实现,现在我们来实现这样一个需求——我们绘制一个不断做跑步动作的人,但是位置不变(在 MyView 类中通过子线程不断通知重复重绘右边两个不同状态的小兵即可),动画效果是实现了。但是如果我要求响应事件,点击屏幕让他喊 “军情、军情”,按方向键让他向相应的方向跑。这么做?按照原理来说是:给 MyView 重写 onTouchEvent() 响应触屏事件、重写 onKeyDown() 方法响应方向键。但是别忘了——View框架的重绘是不断重新执行 onDraw() 方法,而 onDraw() 是在 UI 线程中执行的,此时 UI 线程正在不断执行 onDraw() 方法,很容易导致 UI 线程阻塞而不能响应触屏和按键。虽说当线程间断休眠时间够长即相邻两个 onDraw() 之间间断时间足够长时可以响应事件,但是我们怎么可以让我们的程序处于一个不可控的状态呢?现在用 View 便不再能够满足我们的需求。
有比较才能显出区别:看完 视图框架系列1/3——View视图框架 ,在结合上边的情景,不难总结出这样的结论:View 可以实现动画效果,但是仅仅局限于两种:
★(1)“被动更新”的动画,“被动”是指这个动画(“视图”更为准确)不主动更新,它的更新依赖于 onTouchEvent、onKeyDown 等事件来触发 onDraw 的重绘;
★(2)只显示一个动画而不接受事件响应,很好理解,比如我在页面上显示一个闪动的星星,这个星星只是展示作用,不接受任何事件。
★另外:View 视图绘制的效率比较低,因为每一次重绘实际上是重新执行以便 onDraw 方法,onDraw 方法是重新绘制整个画布,而在 View 中,canvas 就是整个视图。
而此时的窘境,正是 SurfaceView 视图大显身手的时候!SurfaceView 继承于 View ,所以同样拥有触屏监听、按钮监听等方法,但是请注意,SurfaceView 看名字就和 Surface 脱不了干系,Surface 是 Android 中一个很重要的类,有必要了解一下。每个 View 在和屏幕绑定时都会关联一个对应的 Surface,你可以把 Surface 理解成一块屏幕缓存。但从源码可以看出 SurfaceView 还有一个 Surface 类型的成员变量,所以 SurfaceView 就拥有了两个内存区。 这里就该说 SurfaceView 的双缓冲机制了。
二、双缓冲技术
三、SurfaceView的使用
public class MySurfaceView extends SurfaceView implements Callback {
private SurfaceHolder holder;
private Paint paint;
public MySurfaceView(Context context) {
this(context, null);
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
holder = getHolder();
holder.addCallback(this); //给Holder声明Callback回调
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setTextSize(50);
}
/* Callback方法之一,SurfaceView创建时回调 */
@Override
public void surfaceCreated(SurfaceHolder arg0) {
myDraw();
}
/* Callback方法之一,SurfaceView改变时回调 */
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) { }
/* Callback方法之一,SurfaceView销毁时回调 */
@Override
public void surfaceDestroyed(SurfaceHolder arg0) { }
/* 我自己的绘制方法 */
private void myDraw() {
String text = "Holle-World-!";
Canvas canvas = holder.lockCanvas(); //获取画布
canvas.drawColor(Color.WHITE); //刷屏
canvas.drawText(text, 0, 50, paint); //绘制内容
holder.unlockCanvasAndPost(canvas); //提交对画布所做的操作
}
}
四、刷屏
String text = "Holle-World-!";
private int positionX = 0, positionY = 0; //初始化X,Y为0
private void myDraw() {
Canvas canvas = holder.lockCanvas(); //获取画布
//这里先不画颜色(刷屏)
canvas.drawText(text, positionX, positionY + 50, paint); //Y轴方向+50是因为绘制Text时指定的是左下角位置,+50用来校正位置
holder.unlockCanvasAndPost(canvas); //提交对画布所做的操作
}
添加触屏响应:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
positionX = (int)event.getX();
positionY = (int)event.getY();
myDraw();
break;
default:
break;
}
return super.onTouchEvent(event);
}
运行一下,多点几下就会出现这个样子:
看这个结果图,说明触屏动作及时响应了,否则也不会出现触点位置的字符,但是并没有抹掉旧状态,就是因为我们没有“刷屏”。接下来我们在 canvas.drawText() 之前加上:
canvas.drawColor(Color.WHITE); //刷屏
再运行:
这就是“刷屏”(娘的!别再瞎想你的朋友圈刷屏了)。这里就是区别:View类本身提供的两种重绘方法 invalidate()、postInvalidate() 内部已经封装了刷屏的操作(具体是什么操作,最好看源码,我没看,不敢妄言),所以每次重绘之后都看不到之前的历史记录;但是用 SurfaceView 时我们用lockCanvas () 获取到的是同一个画布,绘图用的是自定义绘图方法 myDraw(),系统没有帮我们刷新画布,也,没有提供给我们一张新画布,所以在进行下一次绘画时必定能看到上一次的痕迹,这时候我们就要自己动手“刷屏":
五、在 SurfaceView 中添加线程
public class MySurfaceView extends SurfaceView implements Runnable, Callback {
private SurfaceHolder holder;
private Paint paint;
private String text = "Holle!";
private Canvas canvas;
private boolean flag;
private Thread thread;
private int positionX = 0, positionY = 0;
private int screenW, screenH;
private int speedX = 10, speedY = 13;
public MySurfaceView(Context context) {
this(context, null);
}
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
holder = getHolder();
holder.addCallback(this);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setTextSize(50);
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
screenW = getWidth();
screenH = getHeight(); //看——>说明4
flag = true;
thread = new Thread(this);
thread.start();
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
flag = false;
}
private void myDraw() {
try { //看——>说明2
canvas = holder.lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.WHITE);
canvas.drawText(text, positionX, positionY + 50, paint);
}
} catch (Exception e) {
// TODO: handle exception
} finally {
holder.unlockCanvasAndPost(canvas); //看——>说明3
}
}
/**
* 模拟一个逻辑
*/
private void myLogic() {
if (positionX < 0 || positionX > screenW - 100) {
speedX = -speedX;
}
if (positionY < 0 || positionY > screenH) {
speedY = -speedY;
}
positionX += speedX;
positionY += speedY;
}
@Override
public void run() {
while (flag) { //看——>说明1
long start = System.currentTimeMillis(); //看——>说明5
myDraw();
myLogic();
long end = System.currentTimeMillis();
try {
if (end - start < 100) {
Thread.sleep(100 - (end - start));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}