SurfaceView 教程 GIF 制作

来源:互联网 发布:勒索病毒 端口 注册表 编辑:程序博客网 时间:2024/04/29 10:51

 在本教程中,介绍OPhone中提供的SurfaceView以及如何通过SurfaceView来编写一个显示GIF动画的View 。

SurfaceView介绍
      通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制)。但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的。
 
       如果需要在另外的线程绘制界面、需要迅速的更新界面或则渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了。SurfaceView中包含一个Surface对象,而Surface是可以在后台线程中绘制的。Surface属于
 
       OPhone底层显示系统,关于这方面的介绍请参考附录中的资料[1]。SurfaceView的性质决定了其比较适合一些场景:需要界面迅速更新、对帧率要求较高的情况。使用SurfaceView需要注意以下几点情况:
  • SurfaceView和SurfaceHolder.Callback函数都从当前SurfaceView窗口线程中调用(一般而言就是程序的主线程)。有关资源状态要注意和绘制线程之间的同步。
  •  在绘制线程中必须先合法的获取Surface才能开始绘制内容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间的状态为合法的,另外在Surface类型为SURFACE_TYPE_PUSH_BUFFERS时候是不合法的。
  •  额外的绘制线程会消耗系统的资源,在使用SurfaceView的时候要注意这点。
 
使用SurfaceView
        只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
  •  surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
  •  surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
  •  surfaceDestroyed(SurfaceHolder holder):当Surface被摧毁前会调用该函数,该函数被调用后就不能继续使用Surface了,一般在该函数中来清理使用的资源。
 
       通过SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas(Rect dirty)函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect dirty)函数来指定一个dirty区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
当在Canvas中绘制完成后,调用函数unlockCanvasAndPost(Canvas canvas)来通知系统Surface已经绘制完成,这样系统会把绘制完的内容显示出来。为了充分利用不同平台的资源,发挥平台的最优效果可以通过SurfaceHolder的setType函数来设置绘制的类型,目前接收如下的参数:
  • SURFACE_TYPE_NORMAL:用RAM缓存原生数据的普通Surface
  • SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬件加速的Surface
  • SURFACE_TYPE_GPU:适用于GPU加速的Surface
  • SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供,在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。
 
         目前OPhone还不支持GIF动画图片的显示,这里就通过一个SurfaceView来展示如何定制一个支持GIF动画的View,同时从该示例(注释)中也可以看出如何使用SurfaceView。
 
 
         首先创建一个GifView继承在SurfaceView,代码如下:
view plaincopy to clipboardprint?
  1. /**  
  2.  * 通过继承SurfaceView并实现Callback接口来实现一个GifView。  
  3.  *  
  4.  */  
  5. class GifView extends SurfaceView implements Callback {   
  6.     //GifThread是用来绘制的后台线程,一般使用SurfaceView都会使用一个   
  7.     //后台线程来做绘制的工作   
  8.     private GifThread mGifThread;   
  9.     //GifDecoder是一个Gif图片格式的解析器,用来解析Gif图片的每帧数据和显示时间。   
  10.     private GifDecoder mGifDecoder;   
  11.     
  12.     /**  
  13.      * 构造函数,读者可以自己实现另外的构造函数以方便在xml layout中使用GifView。  
  14.      * @param context  
  15.      * @param gifId 需要显示的Gif图片id,放置在Raw目录下的资源文件  
  16.      */  
  17.        public GifView(Context context,int gifId) {   
  18.            super(context);   
  19.            //获取Gif图片数据   
  20.            InputStream is = context.getResources().openRawResource(gifId);   
  21.            //解析Gif图片数据   
  22.            mGifDecoder = new GifDecoder();   
  23.            mGifDecoder.read(is);   
  24.            try {   
  25.                is.close();   
  26.            } catch (IOException e) {   
  27.               e.printStackTrace();   
  28.            }   
  29.            is = null;   
  30.            //获取SurfaceHolder   
  31.            SurfaceHolder holder = getHolder();   
  32.            //如果有必要可以设置合适的SurfaceType   
  33.            //holder.setType(SurfaceHolder.SURFACE_TYPE_HARDWARE);   
  34.            //设置回调函数   
  35.            holder.addCallback(this);   
  36.            //创建Gif绘制线程   
  37.            mGifThread = new GifThread(holder,this);   
  38.               
  39.        }   
  40.           
  41.        public void surfaceChanged(SurfaceHolder holder, int format, int width,   
  42.               int height) {   
  43.            //Do nothing   
  44.        }   
  45.     
  46.        public void surfaceCreated(SurfaceHolder holder) {   
  47.            //当Surface创建成功后,启动绘制线程   
  48.            mGifThread.setRunning(true);   
  49.            mGifThread.start();   
  50.        }   
  51.     
  52.        public void surfaceDestroyed(SurfaceHolder holder) {   
  53.            //当Surface即将摧毁的时候,停止绘制线程   
  54.            boolean retry = true;   
  55.            mGifThread.setRunning(false);   
  56.            while (retry) {   
  57.               try {   
  58.                   mGifThread.join();   
  59.                   retry = false;   
  60.               } catch (InterruptedException e) {   
  61.                   // we will try it again and again...   
  62.               }   
  63.            }   
  64.     
  65.        }   
  66.        
  67. }   
  68.     
  69.     
  70. 在代码中都有详细的注释,这里就不再解释,下面是GifThread代码:   
  71.     
  72. /**  
  73.  * 负责绘制Surface的线程  
  74.  */  
  75. class GifThread extends Thread {   
  76.     private SurfaceHolder mSurfaceHolder;   
  77.     private boolean mRunning;   
  78.        
  79.     private int mCurrentFrame;//当前绘制的帧数   
  80.     private long mLastTime;//上一帧绘制的时间   
  81.     private int mFrameCount;//总帧数   
  82.        
  83.     private Bitmap[] mBitmap;//每帧的图片数据   
  84.     private int[] mDelay;//每帧的显示时间   
  85.     /**  
  86.      * 构造函数,初始化相关的数据  
  87.      * @param surfaceHolder  
  88.      * @param gifView  
  89.      */  
  90.     public GifThread(SurfaceHolder surfaceHolder, GifView gifView) {   
  91.        mSurfaceHolder = surfaceHolder;   
  92.        GifDecoder mGifDecoder = gifView.mGifDecoder;   
  93.        mLastTime = System.currentTimeMillis();   
  94.        mFrameCount = mGifDecoder.getFrameCount();   
  95.        mBitmap = new Bitmap[mFrameCount];   
  96.        mDelay = new int[mFrameCount];   
  97.        for (int i = 0; i < mFrameCount; i++) {   
  98.               mBitmap[i] = mGifDecoder.getFrame(i);   
  99.               mDelay[i] = mGifDecoder.getDelay(i);   
  100.            }   
  101.        mGifDecoder = null;   
  102.     }   
  103.        
  104.     /**  
  105.      * 设置运行状态,如果设置为false,则线程退出绘制。  
  106.      */  
  107.     public void setRunning(boolean running) {   
  108.        mRunning = running;   
  109.     }   
  110.     /**  
  111.      * 绘制当前帧  
  112.      */  
  113.     private void doDraw(Canvas canvas) {   
  114.        mLastTime = System.currentTimeMillis();;   
  115.        canvas.drawBitmap(mBitmap[mCurrentFrame], new Matrix(), null);   
  116.        mCurrentFrame ++;   
  117.     }   
  118.     @Override  
  119. public void run() {   
  120.     Canvas c;   
  121.     int delay = 0;   
  122.     while (mRunning) {   
  123.         c = null;   
  124.         if(mCurrentFrame != 0) {   
  125.            delay = mDelay[mCurrentFrame-1];   
  126.        }    
  127.        if(mCurrentFrame == mFrameCount) {   
  128.            mCurrentFrame = 0;   
  129.        }   
  130.           
  131.        long currentTime = System.currentTimeMillis();   
  132.        long t = currentTime - mLastTime;   
  133.        //如果到下一帧绘制时间开始绘制下一帧   
  134.        if(t >= delay) {   
  135.            try {   
  136.                //获取Canvas来绘制界面   
  137.            c = mSurfaceHolder.lockCanvas(null);   
  138.            //通过mSurfaceHolder来同步绘制操作   
  139.                 synchronized (mSurfaceHolder) {   
  140.                    doDraw(c);   
  141.                 }   
  142.            } finally {   
  143.             // 在finally中执行该操作,这样当上面的代码抛出异常的时候   
  144.             //不会导致Surface出去不一致的状态。   
  145.             if (c != null) {   
  146.                mSurfaceHolder.unlockCanvasAndPost(c);   
  147.             }   
  148.         }   
  149.       }   
  150.     }   
  151.  }   
  152.     
  153. }   
  154.    
 
      最后创建一个Activity来测试下GifView:
 
view plaincopy to clipboardprint?
  1. public class SurfaceActivity extends Activity {   
  2.     /** Called when the activity is first created. */  
  3.     @Override  
  4.     public void onCreate(Bundle savedInstanceState) {   
  5.         super.onCreate(savedInstanceState);   
  6.         setContentView(new GifView(this,R.raw.pic));   
  7. }   
  8. }   
  9.    
         程序截图:
GifView运行过程中的两幅截图
原创粉丝点击