前言
在写 SurfaceView之前,先来记录下 自定义View,然后再引出 SurfaceView;
1. 自定义View
针对于自定义View,我们需要知道以下几点:
- 自定义View 一般用来实现一些动画效果,是通过不断执行 View.onDraw()方法,比如 onDraw()方法每秒执行20次,会形成一个20帧的补间动画,这里涉及到帧数的概念;
- 帧数:就是每秒onDraw()方法执行的次数;
- onDraw()方法是系统帮我们调用,我们通过调用系统的 invalidate()方法 通知系统需要重新绘制 View,然后它就会调用 View.onDraw()方法,这些都是系统帮我们调用的;
基于以上,我们很难准确定义 onDraw()方法的执行帧数,这个时候就需要引入 SurfaceView,来弥补 自定义View的一些不足之处,下边先来看一下我用 自定义View实现的一个动画效果,效果是从中间扩散到最大的圆,效果图及代码如下:
图片.png
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/5/7 13:25
* Version 1.0
* Params:
* Description: 自定义View - 实现动画效果
*/
public class AnimateViewActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 這邊傳入的this代表這個對象,因為Activity是繼承自Content類的,因此該對象也
// 可向上轉型為Content類型作為AnimateView的構造方法的參數
setContentView(new AnimateView(this));
}
class AnimateView extends View {
float radius = 10;
Paint paint;
public AnimateView(Context context) {
super(context);
paint = new Paint();
paint.setColor(Color.GREEN); // 圆圈颜色
paint.setStyle(Paint.Style.STROKE); // 线条模式 文字、图像只显示边界
paint.setStrokeWidth((float) 10.0); // 宽度
}
@Override
protected void onDraw(Canvas canvas) {
canvas.translate(200, 200);
canvas.drawCircle(0, 0, radius++, paint);
if(radius > 100){
radius = 10;
}
invalidate();//通过调用这个方法让系统自动刷新视图
}
}
}
2. 对自定义 View实现的动画效果总结
- 因为自定义View的帧数是有系统控制的,所以采用自定义View 实现的动画不能控制 动画执行的速度;
- 可以把自定义View理解为:经过系统优化、可以高效执行的帧数比较低的动画效果,也就是说它有其具体的使用场景,比如一些帧数比较低的游戏:贪吃蛇、棋牌类、俄罗斯方块等
这个时候我们就需要一些帧数比较快的、可以自己控制帧数的对象,因此SurfaceView就横空出世了;
3. SurfaceView介绍
- 可以控制帧数;
- 自定义VIew的更新UI,只能放在主线程中,SurfaceView可以让其他线程更新UI,根据这个特性,就可以控制它的帧数,如果让这个线程1秒执行50次,最后就是显示50帧
4. SurfaceView具体步骤如下
1>:SurfaceView也是一个View,它有自己的生命周期,surfaceCreate()、surfaceChange()、surfaceDestroy()这3个方法;
2>:可以在自定义DemoSurfaceView的构造方法中开一个 LoopThread线程,然后进行绘制自定义View;
3>:在生命周期结束时,也就是 surfaceDestroy()方法中结束线程;
其实上边这些都是由 其SurfaceHolder对象完成的。SurfaceHolder保存了Surface对象的引用SurfaceHolder生命周期就是 Surface的生命周期,使用SurfaceHolder处理生命周期的初始化;
具体代码如下:
/**
* Email: 2185134304@qq.com
* Created by Novate 2018/5/7 13:40
* Version 1.0
* Params:
* Description: 自定义的SurfaceView - 用于实现动画效果
*/
public class DemoSurfaceView extends SurfaceView implements Callback{
LoopThread thread;
public DemoSurfaceView(Context context) {
super(context);
//初始化,设置生命周期回调方法
init();
}
public DemoSurfaceView(Context context,AttributeSet attrs){
super(context , attrs);
//初始化,设置生命周期回调方法
init();
}
private void init(){
SurfaceHolder holder = getHolder();
//设置Surface生命周期回调
holder.addCallback(this);
// LoopThread线程用于绘制图形
thread = new LoopThread(holder, getContext());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread.isRunning = true;
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 注意3:通过标志位关闭LoopThread线程
thread.isRunning = false;
try {
// 关闭LoopThread线程,这里使用join()方法
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 执行绘制的绘制线程
*/
class LoopThread extends Thread{
SurfaceHolder surfaceHolder;
Context context;
boolean isRunning;
float radius = 10f;
Paint paint;
public LoopThread(SurfaceHolder surfaceHolder,Context context){
this.surfaceHolder = surfaceHolder;
this.context = context;
isRunning = false;
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth((float) 10.0); // 宽度
}
@Override
public void run() {
Canvas c = null;
while(isRunning){
try{
// 注意1:同步锁为了防止多个线程同时操作同一个Canvas的c
synchronized (surfaceHolder) {
c = surfaceHolder.lockCanvas(null);
doDraw(c);
//通过它来控制帧数执行一次绘制后休息50ms
Thread.sleep(50);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
public void doDraw(Canvas c){
// 注意2:SurfaceView特点:会保留之前绘制的图像,这里需要首先清空上一次绘制的图形
// 自定义View不用手动清理,自定义View在调用onDraw()方法就已经自动清除掉视图中的东西
c.drawColor(Color.BLACK);
c.translate(200, 200);
c.drawCircle(0,0, radius++, paint);
if(radius > 100){
radius = 10f;
}
}
}
}
上边代码需要注意:
1>:为防止多个线程同时操作 同一个 Canvas对象,需要添加同步锁synchronize,并且在操作完成后需要释放Canvas锁;
2>:在调用 doDraw()执行绘制时候,因为 SurfaceView的特点,它会保留之前绘制的图形,需要先清空上一次绘制时留下的图形;
3>:在surfaceDestroy()方法中关闭线程就ok;
5. View与SurfaceView的区别?
1>:自定义View只能在主线程中 更新UI,不能再子线程中更新,而SurfaceView可以在任何线程中更新UI;
2>:SurfaceView可以控制帧数,执行动画效率比View的高;
3>:SurfaceView是放在最底层,可以在它上边添加一些层,而且不能是透明的;
4>:SurfaceView定义和使用比自定义View复杂,占用资源比较多,一般情况用自定义View就行,实在不行,最后再考虑SurfaceView;
具体代码已上传至github:
https://github.com/shuai999/SurfaceViewDemo.git
网友评论