zoukankan      html  css  js  c++  java
  • 转:android surface简单使用Demo

    转:

    http://blog.csdn.net/listening_music/article/details/6860786

    通过之前介绍的如何自定义View, 我们知道使用它可以做一些简单的动画效果。它通过不断循环的执行View.onDraw方法,每次执行都对内部显示的图形做一些调整,我们假设 onDraw方法每秒执行20次,这样就会形成一个20帧的补间动画效果。但是现实情况是你无法简单的控制View.onDraw的执行帧数,这边说的执 行帧数是指每秒View.onDraw方法被执行多少次,这是为什么呢?首先我们知道,onDraw方法是由系统帮我们调用的,我们是通过调用View的 invalidate方法通知系统需要重新绘制View,然后它就会调用View.onDraw方法。这些都是由系统帮我们实现的,所以我们很难精确去定 义View.onDraw的执行帧数,这个就是为什么我们这边要了解SurfaceView了,它能弥补View的一些不足。

    首先我们先写一个自定义View实现动画效果,AnimateViewActivity.java:

    [java] view plain copy
     
     print?
    1. package com.android777.demo.uicontroller.graphics;  
    2.    
    3. import android.app.Activity;  
    4. import android.content.Context;  
    5. import android.graphics.Canvas;  
    6. import android.graphics.Color;  
    7. import android.graphics.Paint;  
    8. import android.os.Bundle;  
    9. import android.view.View;  
    10.    
    11. public class AnimateViewActivity extends Activity {  
    12.    
    13.     @Override  
    14.     protected void onCreate(Bundle savedInstanceState) {  
    15.         super.onCreate(savedInstanceState);  
    16.    
    17.         setContentView(new AnimateView(this));//這邊傳入的this代表這個對象,因為Activity是繼承自Content類的,因此該對象也  
    18.                                                可向上轉型為Content類型作為AnimateView的構造方法的參數  
    19.     }  
    20.    
    21.     class AnimateView extends View{  
    22.    
    23.         float radius = 10;  
    24.         Paint paint;  
    25.    
    26.         public AnimateView(Context context) {  
    27.             super(context);  
    28.             paint = new Paint();  
    29.             paint.setColor(Color.YELLOW);  
    30.             paint.setStyle(Paint.Style.STROKE);  
    31.         }  
    32.    
    33.         @Override  
    34.         protected void onDraw(Canvas canvas) {  
    35.    
    36.             canvas.translate(200, 200);  
    37.             canvas.drawCircle(0, 0, radius++, paint);            
    38.    
    39.             if(radius > 100){  
    40.                 radius = 10;  
    41.             }  
    42.    
    43.             invalidate();//通过调用这个方法让系统自动刷新视图  
    44.    
    45.         }  
    46.    
    47.     }  
    48.    
    49. }  


    运行上面的Activity,你将看到一个圆圈,它原始半径是10,然后不断的变大,直到达到100后又恢复到10,这样循环显示,视觉效果上说你将看到一个逐渐变大的圆圈。它能做的只是简单的动画效果,具有一些局限性。首先你无法控制动画的显示速度,目前它是以最快的 速度显示,但是当你要更快,获取帧数更高的动画呢? 因为View的帧数是由系统控制的,所以你没办法完成上面的操作。如果你需要编写一个游戏,它需要的帧数比较高,那么View就无能为力了,因为它被设计 出来时本来就不是用来处理一些高帧数显示的。你可以把View理解为一个经过系统优化的,可以用来高效的执行一些帧数比较低动画的对象,它具有特定的使用 场景,比如有一些帧数较低的游戏就可以使用它来完成:贪吃蛇、俄罗斯方块、棋牌类等游戏,因为这些游戏执行的帧数都很低。但是如果是一些实时类的游戏,如 射击游戏、塔防游戏、RPG游戏等就没办法使用View来做,因为它的帧数太低了,会导致动画执行不顺畅。所以我们需要一个能自己控制执行帧数的对 象,SurfaceView因此诞生了。

    什么是SurfaceView呢?

    为什么是SurfaceView呢?Surface的意思是表层,表面的意思,那么SurfaceView就是指一个在表层的View对象。为什么 说是在表层呢,这是因为它有点特殊跟其他View不一样,其他View是绘制在表层外,而它就是充当表层对象。假设你要在一个球上画画,那么球的表层就当 做你的画布对象,你画的东西会挡住它的表层,我们默认没使用SurfaceView,那么球的表层就是空白的,如果我们使用了SurfaceView,我 们可以理解为我们拿来的球本身表面就具有纹路,你是画再纹路之上的,如果你画的是半透明的,那么你将可以透过你画的东西看到球面本身的纹路。SDK的文档 说到:SurfaceView就是在Window上挖一个洞,它就是显示在这个洞里,其他的View是显示在Window上,所以View可以显式在 SurfaceView之上,你也可以添加一些层在SurfaceView之上。

    SurfaceView还有其他的特性,上面我们讲了它可以控制帧数,那它是什么控制的呢?这就需要了解它的使用机制。一般在很多游戏设计中,我们都是开辟一个后台线程计算游戏相关的数据,然后根据这些计算完的新数据再刷新视图对象,由于对View执行绘制操作只能在UI线程上, 所以当你在另外一个线程计算完数据后,你需要调用View.invalidate方法通知系统刷新View对象,所以游戏相关的数据也需要让UI线程能访 问到,这样的设计架构比较复杂,要是能让后台计算的线程能直接访问数据,然后更新View对象那改多好。我们知道View的更新只能在UI线程中,所以使 用自定义View没办法这么做,但是SurfaceView就可以了。它一个很好用的地方就是允许其他线程(不是UI线程)绘制图形(使用Canvas),根据它这个特性,你就可以控制它的帧数,你如果让这个线程1秒执行50次绘制,那么最后显示的就是50帧。

    如何使用SurfaceView?

    首先SurfaceView也是一个View,它也有自己的生命周期。因为它需要另外一个线程来执行绘制操作,所以我们可以在它生命周期的初始化阶 段开辟一个新线程,然后开始执行绘制,当生命周期的结束阶段我们插入结束绘制线程的操作。这些是由其内部一个SurfaceHolder对象完成的。 SurfaceHolder,顾名思义,它里面保存了一个队Surface对象的引用,而我们执行绘制方法就是操作这个 Surface,SurfaceHolder因为保存了对Surface的引用,所以使用它来处理Surface的生命周期,说到底 SurfaceView的生命周期其实就是Surface的生命周期,因为SurfaceHolder保存对Surface的引用,所以使用 SurfaceHolder来处理生命周期的初始化。首先我们先看看建立一个SurfaceView的大概步骤,先看看代码:

    DemoSurfaceView.java:

    [java] view plain copy
     
     print?
    1. package com.android777.demo.uicontroller.graphics;  
    2.    
    3. import android.content.Context;  
    4. import android.view.SurfaceHolder;  
    5. import android.view.SurfaceHolder.Callback;  
    6. import android.view.SurfaceView;  
    7.    
    8. public class DemoSurfaceView extends SurfaceView  implements Callback{  
    9.    
    10.     public DemoSurfaceView(Context context) {  
    11.         super(context);  
    12.    
    13.         init(); //初始化,设置生命周期回调方法  
    14.    
    15.     }  
    16.    
    17.     private void init(){  
    18.    
    19.         SurfaceHolder holder = getHolder();  
    20.         holder.addCallback(this); //设置Surface生命周期回调  
    21.    
    22.     }  
    23.    
    24.     @Override  
    25.     public void surfaceChanged(SurfaceHolder holder, int format, int width,  
    26.             int height) {  
    27.     }  
    28.    
    29.     @Override  
    30.     public void surfaceCreated(SurfaceHolder holder) {  
    31.     }  
    32.    
    33.     @Override  
    34.     public void surfaceDestroyed(SurfaceHolder holder) {  
    35.     }  
    36.    
    37. }  


    上面代码我们在SurfaceView的构造方法中执行了init初始化方法,在这个方法里,我们先获取SurfaceView里的 SurfaceHolder对象,然后通过它设置Surface的生命周期回调方法,使用DemoSurfaceView类本身作为回调方法代理类。 surfaceCreated方法,是当SurfaceView被显示时会调用的方法,所以你需要再这边开启绘制的线 程,surfaceDestroyed方法是当SurfaceView被隐藏会销毁时调用的方法,在这里你可以关闭绘制的线程。上面的例子运行后什么也不 显示,因为还没定义一个执行绘制的线程。下面我们修改下代码,使用一个线程绘制一个逐渐变大的圆圈:

    [java] view plain copy
     
     print?
    1. package com.android777.demo.uicontroller.graphics;  
    2.    
    3. import android.content.Context;  
    4. import android.graphics.Canvas;  
    5. import android.graphics.Color;  
    6. import android.graphics.Paint;  
    7. import android.view.SurfaceHolder;  
    8. import android.view.SurfaceHolder.Callback;  
    9. import android.view.SurfaceView;  
    10.    
    11. public class DemoSurfaceView extends SurfaceView  implements Callback{  
    12.    
    13.     LoopThread thread;  
    14.    
    15.     public DemoSurfaceView(Context context) {  
    16.         super(context);  
    17.    
    18.         init(); //初始化,设置生命周期回调方法  
    19.    
    20.     }  
    21.    
    22.     private void init(){  
    23.    
    24.         SurfaceHolder holder = getHolder();  
    25.         holder.addCallback(this); //设置Surface生命周期回调  
    26.         thread = new LoopThread(holder, getContext());  
    27.     }  
    28.    
    29.     @Override  
    30.     public void surfaceChanged(SurfaceHolder holder, int format, int width,  
    31.             int height) {  
    32.     }  
    33.    
    34.     @Override  
    35.     public void surfaceCreated(SurfaceHolder holder) {  
    36.         thread.isRunning = true;  
    37.         thread.start();  
    38.     }  
    39.    
    40.     @Override  
    41.     public void surfaceDestroyed(SurfaceHolder holder) {  
    42.         thread.isRunning = false;  
    43.         try {  
    44.             thread.join();  
    45.         } catch (InterruptedException e) {  
    46.             e.printStackTrace();  
    47.         }  
    48.     }  
    49.    
    50.     /** 
    51.      * 执行绘制的绘制线程 
    52.      * @author Administrator 
    53.      * 
    54.      */  
    55.     class LoopThread extends Thread{  
    56.    
    57.         SurfaceHolder surfaceHolder;  
    58.         Context context;  
    59.         boolean isRunning;  
    60.         float radius = 10f;  
    61.         Paint paint;  
    62.    
    63.         public LoopThread(SurfaceHolder surfaceHolder,Context context){  
    64.    
    65.             this.surfaceHolder = surfaceHolder;  
    66.             this.context = context;  
    67.             isRunning = false;  
    68.    
    69.             paint = new Paint();  
    70.             paint.setColor(Color.YELLOW);  
    71.             paint.setStyle(Paint.Style.STROKE);  
    72.         }  
    73.    
    74.         @Override  
    75.         public void run() {  
    76.    
    77.             Canvas c = null;  
    78.    
    79.             while(isRunning){  
    80.    
    81.                 try{  
    82.                     synchronized (surfaceHolder) {  
    83.    
    84.                         c = surfaceHolder.lockCanvas(null);  
    85.                         doDraw(c);  
    86.                         //通过它来控制帧数执行一次绘制后休息50ms  
    87.                         Thread.sleep(50);  
    88.                     }  
    89.                 } catch (InterruptedException e) {  
    90.                     e.printStackTrace();  
    91.                 } finally {  
    92.                     surfaceHolder.unlockCanvasAndPost(c);  
    93.                 }  
    94.    
    95.             }  
    96.    
    97.         }  
    98.    
    99.         public void doDraw(Canvas c){  
    100.    
    101.             //这个很重要,清屏操作,清楚掉上次绘制的残留图像  
    102.             c.drawColor(Color.BLACK);  
    103.    
    104.             c.translate(200, 200);  
    105.             c.drawCircle(0,0, radius++, paint);  
    106.    
    107.             if(radius > 100){  
    108.                 radius = 10f;  
    109.             }  
    110.    
    111.         }  
    112.    
    113.     }  
    114.    
    115. }  


    上面代码编写了一个使用SurfaceView制作的动画效果,它的效果跟上面自定义View的一样,但是这边的SurfaceView可以控制动 画的帧数。在SurfaceView中内置一个LoopThread线程,这个线程的作用就是用来绘制图形,在SurfaceView中实例化一个 LoopThread实例,一般这个操作会放在SurfaceView的构造方法中。然后通过在SurfaceView中的SurfaceHolder的 生命周期回调方法中插入一些操作,当Surface被创建时(SurfaceView显示在屏幕中时),开启LoopThread执行绘 制,LoopThread会一直刷新SurfaceView对象,当SurfaceView被隐藏时就停止改线程释放资源。这边有几个地方要注意下:

    1.因为SurfaceView允许自定义的线程操作Surface对象执行绘制方法,而你可能同时定义多个线程执行绘制,所以当你获取 SurfaceHolder中的Canvas对象时记得加同步操作,避免两个不同的线程同时操作同一个Canvas对象,当操作完成后记得调用 SurfaceHolder.unlockCanvasAndPost方法释放掉Canvas锁。

    2.在调用doDraw执行绘制时,因为SurfaceView的特点,它会保留之前绘制的图形,所以你需要先清空掉上一次绘制时留下的图形。(View则不会,它默认在调用View.onDraw方法时就自动清空掉视图里的东西)。

    3. 记得在回调方法:onSurfaceDestroyed方法里将后台执行绘制的LoopThread关闭,这里是使用join方法。这涉及到线程如何关闭 的问题,多数人建议是通过一个标志位:isRunning来判断线程是否该停止运行,如果你想关闭线程只需要将isRunning改成false即可,线 程会自动执行完run方法后退出。

    总结:

    通过上面的分析,现在大家应该会简单使用SurfaceView了,总的归纳起来SurfaceView和View不同之处有:

    1. SurfaceView允许其他线程更新视图对象(执行绘制方法)而View不允许这么做,它只允许UI线程更新视图对象。

    2. SurfaceView是放在其他最底层的视图层次中,所有其他视图层都在它上面,所以在它之上可以添加一些层,而且它不能是透明的。

    3. 它执行动画的效率比View高,而且你可以控制帧数。

    4. 因为它的定义和使用比View复杂,占用的资源也比较多,除非使用View不能完成,再用SurfaceView否则最好用View就可以。(贪吃蛇,俄罗斯方块,棋牌类这种帧数比较低的可以使用View做就好)

  • 相关阅读:
    Kinect 开发 —— 硬件设备解剖
    Kinect 开发 —— 引言
    (转)OpenCV 基本知识框架
    OpenCV —— 摄像机模型与标定
    OpenCV —— 跟踪与运动
    OpenCV —— 图像局部与分割(二)
    OpenCV —— 图像局部与部分分割(一)
    OpenCV —— 轮廓
    OpenCV —— 直方图与匹配
    OpenCV —— 图像变换
  • 原文地址:https://www.cnblogs.com/jhj117/p/5457890.html
Copyright © 2011-2022 走看看