SurfaceView



SurfaceView  

2012-04-01 15:49:32|  分类:Android基础|  标签:android基础  android控件  |举报|字号 订阅

17,SurfaceView:执行效率高。SurfaceView可以直接访 问一个画布(Canvas),SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用使用的。Android图形系统中的一个重要概念是 Surface,View及其子类都要画在Surface上。每个Surface创建一个Canvas对象,用来管理View在Surface上的绘图操 作。
在使用SurfaceView开发时需要注意,使用它绘图时,一般都是出现在顶层。使用时还需要对其进行创建、销毁,情况改变时进行监视,这 就是实现SurfaceHolder.Callback接口,如果要对被绘制的画布进行剪裁。控制其大小时都需要使用SurfaceHolder来完成处 理。在程序中,SurfaceHolder对象需要使用getHolder方法来获得,同时还需要addCallback方法来添加“回调函数”。
1)surfaceChanged:在Surface的大小发生改变时激发。
2)surfaceCreated:创建Surface时激发。
3)SurfaceDestroyed:销毁Surface时激发。
4)addCallback:给SurfaceView添加一个回调函数
5)lockCanvas:锁定画布,绘图之前必须锁定画布才能得到当前的画布对象
6)unlockCanvasAndPost:
7)removeCallback:从SurfaceView中移除回调函数。

SurfaceView和View的明显不同之处在于,SurfaceView不需要通过线程来更新视图。

public class GameSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable

    SurfaceHolder mSurfaceHolder=this.getHolder();

    mSurfaceHolder.addCallback(this);

   public void surfaceCreated(SurfaceHolder holder)
           {
            new Thread(this).start();
           }

   public void run()
          {
            while (mbLoop)
                 {
                   try {
                         Thread.sleep(200);
                         }
                   catch (Exception e) {}
                         synchronized(mSurfaceHolder)
                        {
                         Draw();  
                        }
                  }
          }

   public void Draw()
         {
          Canvas canvas=mSurfaceHolder.lockCanvas();
          ...
         }

 

//=====================================

SurfaceView是View的子类,他和View最本质的区别在于,使用SurfaceView可以在一个新起的单独线程中重新绘制画面而View则必须在UI的主线程中更新画面:

根据游戏特点,View和SurfaceView分布应用于以下情况:

1,被动更新画面的:这类游戏往往都是由用户来触发页面更新的,是一种简单的交互,比如棋类游戏,这种游戏用View就可以了。

2,主动更新画面的:比如背景的持续更新,或者一个人在一直跑到等。这些动作需要一个单独的线程不停的绘制人的状态,为了避免阻塞UI主线程,所以需要SurfaceView来控制。

在实际使用SurfaceView时,应注意以下几点:

1,使用后台线程进行绘图的代价是额外的内存消耗,所以使用SurfaceView的时候仍然要保持谨慎。

2,SurfaceView 是提供给需要直接画像素而不是使用窗体部件的应用使用的。也就是说,我们无法在SurfaceView中直接使用TextView等页面组件。但是可以利 用Surface的纵深排序的特性,使其他页面组件看来其就像是显示在SurfaceView之上。

 

SurfaceView提供了一个专用的绘画Surface,并且他可以将Surface放到屏幕的准确位置,我们则可以控制Surface的格式和大小。

Surface 是纵深排序的(Z-ordered),这表明他总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个区域内的Surface可 可见,可见区域外的部分不可见。Surface的排版显示受到视图层级关系的影响,他的兄弟视图结点会在顶端显示。这意味着Surface的内容会被他的 兄弟视图遮挡。利用这一特性,我们可以用来在其上放置遮盖物,例如,文本和按钮等控件。但是,如果Surface上面有透明控件,那么他的每次变化都会引 起框架重新计算他和顶层控件的透明效果,这会影响性能。

每个Surface都会创建一个Canvas对象,用来管理View在Surface上的绘图操作,他支持使用所有标准Canvas方法进行绘图,同时也支持万全的OpenGLES库。

Surface会在SurfaceView显示之后被创建,在SurfaceView隐藏之前被销毁。

 

要访问Surface必须通过SurfaceHolder接口,使用SurfaceView.getHolder方法可以SurfaceHolder接口对象。

SurfaceHolder接口的几个常用方法如下:

1,addCallback(SurfaceHolder.Callback callback):为当前的SurfaceView设置一个回调对象,SurfaceView将会在Surface改变时调用该回调。

2,getSurface() 获得一个可直接访问的Surface对象

3,lockCanvas() 锁定Surface并返回一个Canvas对象,其大小为整个View

4,unlockCanvasAndPost(Canvas canvas):释放指定Surface对象上的锁,并把Canvas对象发送到SurfaceView进行画面更新。

5,lockCanvas(Rect dirty):锁定Surface并返回一个Canvas对象,其大小为指定矩形,只对失效区域进行重绘,可以提高速度。


需要注意的是,以上方法(addCallbacl除外)必须在Surface对象被创建之后和销毁之前执行,否则将不能获取正确的Surface和Canvas对象,返回值为null。

 

为了保证可以正确的操作Canvas对象,我们对Canvas的任何操作都必须要在Surface被创建之后和销毁之前执行。此时,就需要用到SurfaceHolder.Callback接口,Surface会在自己状态发生变化时通知该接口。

SurfaceHolder.Callback接口的3个方法如下:

1)surfaceChanged(SurfaceHolder holder,int format,int width,int height) 当Surface的状态(大小和格式)发生变化的时候调用该函数,例如竖屏改为横屏时。在surface创建后该函数至少会被调用一次。

2)surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。

3)surfaceDestroyed(SurfaceHolder holder):当Surface被销毁前调用该函数,该函数被调用后就不能继续使用Surface了,一班在该函数中来清理使用的资源。

 

构建SurfaceView应用的一般步骤:

1)创建一个新类继承子SurfaceView,并实现SurfaceHolder.Callback接口。

2)创建一个可以启动新线程的类或函数,例如一个继承自Thread的子类或者实现Runnable接口的类,该类做为完成绘图任务的线程类。

3)在surfaceCreated回调函数中启动绘图线程,开始绘制画面

4)在surfaceDestroyed中,终止绘图线程,释放资源

5)为了增加绘图线程的可控性,往往还会添加启动线程和终止线程的方法,供外部调用。

 

1)创建一个继承SurfaceView的类---MySurfaceView,并实现SurfaceHolder.Callback接口

在 创建SurfaceView子类时需要注意,如果该子类不会被应用于layout.xml配置文件中,则子类只需要实现 SurfaceView(Context context)构造即可;如果该子类需要被应用于layout.xml配置文件中,则还必须实现SurfaceView(Context context,AttributeSet attrs)构造函数,并且该函数必须具有public访问修饰符。

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {

     private SurfaceHolder holder;

     private boolean hasSurface;

     public MySurfaceView(Context context) {

         super(context);

         init();

         }

     public MySurfaceView(Context context,AttributeSet attrs) {

         super(context,attrs);

         init();

         }

     public MySurfaceView(Context context,AttributeSet attrs,int DefStyle){

         super(context,attrs,defStyle);

         init();

        }

     private void init(){

         holder=getHolder();

         holder.addCallback(this);

         hasSurface=false;

         }

     public void surfaceCreated(SurfaceHolder holder){

         hasSurface=true;

         }

     public void surfaceDestroyed(SurfaceHolder holder){

         hasSurface=false;

        }

     public void surfaceChanged(SurfaceHolder holder,int format,int w,int h){

        }

    }

}

 

2)创建一个可以启动新线程的类或函数,例如一个继承自Thread的子类或者实现Runnable接口的类,该类做为完成绘图任务的线程类。

 

class MySurfaceViewThread extends Thread{

       private boolean done;

       MySurfaceViewThread(){

             super();

             done=false;

             }

      @Override

       public void run(){

           //重复绘图,直到线程停止

           while(!done) {

               Canvas canvas=holder.lockCanvas();

               holder.unlockCanvasAndPost(canvas);

               }

           }

     public void requestExit(){

           //把这个线程标记为完成,并合并到主线程中

           done=true;

           try {

                 join();

                }catch (InterruptedException ex) {

                }

          }

     public void onWindowResize(int w,int h){

           //超过护理可用的屏幕尺寸的改变

           }

    }

 

3,在surfaceCreated回调函数中启动绘图 线程,开始绘制画面

首先在MySurfaceView类中添加一个私有变量,保存对于线程的引用。

private MySurfaceViewThread mySurfaceViewThread;

然后在surfaceCreated回调函数中启动线程

public void surfaceCreated(SurfaceHolder holder){

     hasSurface=true;

     if(mySurfaceViewThread!=null) {

     mySurfaceViewThread.start();

     }

}

 

4),在surfaceDestroyed中,停止绘图线程,释放资源

public void surfaceDestroyed(SurfaceHolder holder){

      hasSurface=false;

      if (mySurfaceViewThread!=null){

          mySurfaceViewThread.requestExit();

          mySurfaceViewThread=null;

      }

 

5)为了增加绘图线程的可控性,往往还会添加启动线程和终止线程的方法,供外部调用。

大多数情况下,绘图线程还需要外部干预,比如人为的停止绘画和重新开始等,所以还需要把线程的控制方法提供给外部调用。因此,还需要增加以下两个方法:

public void start() {

     if (mySurfaceViewThread==null) {

     mySurfaceViewThread=new MySurfaceViewThread();

     if (hasSurface==true){

         mySurfaceViewThread.start();

         }

     }

}

public void stop(){

      if (mySurfaceViewThread!=null){

          mySurfaceViewThread.requestExit();

          mySurfaceViewThread=null;

          }

}

以上代码描述了使用SurfaceView的大致框架和步骤,在实际应用时还要根据实际情况作调整,例如可以使用Runnable或TimerTask代替线程类;如果该绘图过程没有其他因素参与,则可以省略最后一步。

 

2,使用SurfaceView绘制正弦曲线

1)创建新的Android项目,名称为SinCurve,并选择创建SinCurveActivity,包名为com.example。该类同时实现了onClickListener接口。

2)修改布局文件main.xml,代码如下:

<LinearLayout ... orientation="vertical" layout_width="match_parent" layout_height="match_parent">

    <RelativeLayout layout_width="match_parent"           layout_height="match_parent">

         <com.example.SinCurveSurfaceView android:id="@+id/mySurfaceView"           layout_width="match_parent" layout_height="match_parent" />

          <Button android:id="@+id/btnStart" layout_height="wrap_content"

            layout_width="wrap_content" layout_alignParentBottom="true" />

          <Button android:id="@+id/btnStop" layout_toRightOf="@id/btnStart" />

    </RelativeLayout>

</LinearLayout>

注意:在main.xml中,两个按钮与SurfaceView处于同级的节点,但是利用Surface的纵深排序的特性,他们看起来就像是处于SurfaceView之上。

 

3)新建名为SinCurveSurfaceView的类,包名为com.example。参照前面的代码,代码如下:

public class SinCurveSurfaceView extends SurfaceView implements  

      SurfaceHolder.Callback{

      private SurfaceHolder holder;

      private SinCurveThread myThread;

      private boolean hasSurface;

 

      private int Y_axis[];//保存正弦波的Y轴上的点

      private int currentX;//当前绘制到的X轴上的点

      private int centerY;//Y轴中心线

      private int oldX;//上一个X点

      private int oldY;//上一个Y点

 

      public SinCurveSurfaceView(Context context) {

              super(context);

              init();

              }

       public SinCurveSurfaceView(Context context,AttributeSet attrs){

              super(context,attrs);

              init();

              }

       public SinCurveSurfaceView(Context context,AttributeSet attrs,int defStyle){

             super(context,attrs,defStyle);

             init();

             }

       private void init(){

            holder=getHolder;

            holder.addCallback(this);

            hasSurface=false;

            }

        public void start(){

            if(myThread==null){

                myThread=new SinCurveThread();

                if (hasSurface==true) myThread.start();

            }

       }

       public void stop(){

            if (myThread!=null){

                myThread.requestExit();

                myThread=null;

                }

         }

       public void surfaceCreated(SurfaceHolder holder){

            centerY=(getHeight()-getTop())/2;

            Y_axis= new int[getWidth()];

            for (int i=1;i<Y_axis.length;i++){

                  Y_axis[i-1]=centerY-(int)(100*Math.sin(i*2*Math.PI/180));

                  }

            oldY=centerY;

            hasSurface=true;

            if(myThread!=null) myThread.start();

           }           

       public void surfaceDestroyed(SurfaceHolder holder){

            hasSurface=false;

            stop();

            }

       public void surfaceChanged(SurfaceHolder holder,int format,int w,int h){

           if (myThread!=null) myThread.onWindowsResize(w,h);

           }

class SinCurveThread extends Thread {

           private boolean done;

           SinCurveThread() {

                   super();

                   done=false;

           }

           @Override

           public void run(){

                  while (!done) {

                 Canvas canvas=holder.lockCanvas(new Rect

                           (oldX,0,oldX+currentX,getHeight()));

                 DrawSin(canvas);

                 holder.unlockCanvasAndPost(canvas);

                 clearDraw();

                 }

         }

        private void DrawSin(Canvas canvas){

                 if (currentX==0) oldX=0;

                        Paint mPaint=new Paint();

                        mPaint.setColor(Color.GREEN);

                        mPaint.setStrokenWdith(2);

 

                        int y;

                        for (int i=oldX+1;i<currentX;i++){

                                y=Y_axis[i-1];

                                canvas.drawLine(oldX,oldY,i,y,mPaint);

                                oldX=i;

                                oldY=y;

                                }

                       }

 

           private void ClearDraw() {

                   currentX++;

                   if (currentX==Y_axis.length-1){

                        Canvas canvas=holder.lockCanvas(null);

                        canvas.drawColor(Color.BLACK);

                        holder.unlockCanvasAndPost(canvas);

                        currentX=0;

                        oldY=centerY;

                        }

 

            }

           public void requestExit(){

                 done=true;

                 try {

                 join();

                 }catch(InterruptedException ex){

             }

         }

         public void onWindowResize(int w,int h){

              //处理可用的屏幕尺寸的改变

             }

         }

}

 

5)在Activity创建时,为开始和停止按钮编写Click事件代码

         @Override

          public void onClick(View v) {

          switch (v.getId()) {

          case R.id.btnStart:

          sinCurveView.start();

          break;

          case R.id.btnStop:

          sinCurveView.stop();

          break;

          default:

          break;

          }

     }

}


周末看《 精通Android游戏开发》(Pro Android Games),里面讲到游戏的框架,其中一个重要的概念surfaceview,觉得不是很理解,于是花了一点时间研究了下,写下自己的心得。

surface,这个单词的意思是浮在表面的,那么surfaceview就是浮在表面的view了。如果真的这样解释,估计有人要拍砖了。然而,话虽不能这么说,取这个名儿,多少还是有点关系的。surface是一个可见区域。

我们在屏幕上看到的这些view,在屏幕上看到的就是画面,在内存中就是一块内存区。绘图的时候,就是显示的硬件如显卡将内存区的这块图形数据绘制到屏幕上。所以,从内存的角度去看这些东西,会比较好理解。

surface是surfaceview中的一个可见部分。我们知道,我们看到的屏幕上的图形,是二维的,我们看到的就是长和宽,其实,在内部实际上是三维的,另一个维度,就是层layer。我们用visio绘图,都会看到这种情况,一个图形会将另个图形遮住,是因为这个图形在上层。如果有同AutoCAD的经验,对这个更容易理解。我们看到的图形实际上是很多图形一层层的叠加在一起的,这些图形元素完全不可见,或者部分可见部分不可见,或者完全可见。

这样看来,surface就可以这样理解:它是内存中一块区域,它是surfaceview可见不那个部分,绘图操作作用于它,然后它就会被显卡之类的显示控制器绘制到屏幕上。

surface是个啥,大概已经有了些概念了。因为它对应了一个内存区,大家都知道,内存区的对象是有生命周期的,可以动态的申请创建和销毁,当然也可能会更新。于是,就有了作用于这个内存区的操作,这些操作就是surfaceCreated/Changed/Destroyed。三个操作放在一起,就是callback,
所以在很多例子里看到,会有callback。

callback,是回调,意思是自己能干一些活,不过自己不去主动做,而是到别人那里去登记一下,别人需要的时候,就会叫我去做。就这个例子而言,可以打这个比方,某建筑工人队伍A,能盖房子,能装修,也能拆房子。可是他自己不去主动的做这些事情,而是去向开发商B去登记这三种能力,当然了,他把这三种能力打了包,叫做A的能力。啥时候,B有活干了,就叫A去做,或者是盖房子,或者是装修,或者是拆建筑。在这个例子中,能力包就是callback.

说了这个例子,其实就解释了 surface相关的一些东西,callback已经说过了,下面来说说其他的。假设surface就是一栋房子,那么surface拥有surfaceHolder,谁呢?在这个例子中好比建筑队A。盖房子对应的就是surfaceCreated, 拆房子就对应了surfaceDestroyed,装修就对应了surfaceChanged.

surface有生存期,好比房子有生存期,在建造以后就存在,在拆了之后就没有了。装修必须发生在这之间。同样的,surface的change必须发生在created和destroyed之间。

surfaceview知道surface的holder是谁,在surfaceview生成的时候,会调用getHolder得到holder,然后holder会调用addCallback将三个callback函数注册。

holder拥有对于surface的控制权。在很多程序中,会在surfaceCreated的函数实现中创建另一个线程。所以在这里有两个线程,一个是UI线程,另一个负责画图的线程。画图线程由UI线程调用surfaceCreated的时候创建,在surfaceDestroyed调用的时候放回到线程池。在这中间,画图线程负责图形的绘制。

在这种模型下,UI线程和画图线程各司其职,前者主要负责和用户的交互,而后者,在负责绘制图形。这样,绘制图形的时候如果时间较长,不会阻塞用户的输入。

我们知道,线程共享内存数据,所以, surface对于两个线程是共享的。所以,为了避免在画图的时候,UI线程也对surface进行操作,在画图前,需要对surface加锁。这个工作是有holder干的,holder会先锁住surface中的一块holder.lockCanvas,我们叫canvas,然后,在上面绘画,画完之后,会解锁unlockCanvasAndPost。

在 应用中,画图可以是一次性的,也可以是由定时器触发的定时的画。实现的都是runnable类中的run方法。关于runnable类,这个Java中定义的,并不是android独有的,可以参考Java的referrence.

这个模型最大的好处就是,画图不依赖于UI线程,不会阻塞UI线程。
而单纯的view是依赖于UI线程画图的。对于完全依赖于用户的输入进行图像显示的更新的,用view是可以的,但是如果能够自动的进行绘图,而不需等待用户的输入,surfaceview无疑是更好的选择。

有图有真相,请看图

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值