View存在如下缺陷:
- View缺乏双缓冲机制
- 程序需要更新View上的图像时,程序必须重绘View上显示的整张图片
- 新线程无法直接更新View组件
由于上述缺陷,通过自定义View实现绘图尤其是游戏中的绘图时性能并不好。Android提供了一个SurfaceView来代替View
SurfaceView一般会于SurfaceHolder结合使用,SurfaceHolder用于向与之关联的SurfaceView上绘图,调用SurfaceView的getHolder()方法即可获取SurfaceView关联的SurfaceHolder
SurfaceHolder提供了如下方法来获取Canvas对象:
- Canvas lockCanvas():锁定整个SurfaceView对象,获取该Surface上的Canvas。
- Canvas lockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域,获取该Surface上的Canvas
第二种方法用于获取指定区域的Canvas,只对Rect所指定区域进行更新,这样可以提供画面更新速度
通过lockCanvas()获取指定了SurfaceView上的Canvas之后,接下来程序就可以调用Canvas进行绘图了,Canvas绘图完成后通过如下方法释放绘图、提交所绘制图形:
unlockCanvasAndPost(canvas);
需要注意:调用上述方法后,该方法之前所绘制图形还处于缓冲之中,下一次lockCanvas方法锁定的区域可能会“遮挡”它
View绘图必须在当前UI线程中进行——这也是前面程序需要更新View组件时总要采用Handler处理的原因;而SurfaceView绘图则是由SurfaceHolder来完成的
对于View组件,如果程序需要花较长时间来更新绘图,那么主UI线程将会被阻塞,无法响应用户的动作;而SurfaceViewHolder则会启用新的线程去更新SurfaceView的绘制,因此不会阻塞主UI线程
如果程序需要采用较多定时器控制的动画元素,则可考虑使用SurfaceView,而不是View
示例程序:
main.xml
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
>
< LinearLayout android:orientation = "horizontal"
android:layout_width = "fill_parent"
android:layout_height = "wrap_content"
android:gravity = "center"
>
< Button android:id = "@+id/sin"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "string/sin"
/>
< Button android:id = "@+id/cos"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "string/cos"
/>
</ LinearLayout >
< SurfaceView android:id = "@+id/show"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:gravity = "center"
/>
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
>
< LinearLayout android:orientation = "horizontal"
android:layout_width = "fill_parent"
android:layout_height = "wrap_content"
android:gravity = "center"
>
< Button android:id = "@+id/sin"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "string/sin"
/>
< Button android:id = "@+id/cos"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = "string/cos"
/>
</ LinearLayout >
< SurfaceView android:id = "@+id/show"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:gravity = "center"
/>
</
LinearLayout
>
ShowWave.java
public
class
ShowWave
extends
ActionBarActivity {
private SurfaceHolder holder ;
private Paint paint ;
final int HEIGHT = 320;
final int WIDTH = 320;
final int X_OFFSET = 5;
private int cx = X_OFFSET ;
int centerY = HEIGHT / 2;
Timer timer = new Timer();
TimerTask task = null ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout. main );
final SurfaceView surface = (SurfaceView)
findViewById(R.id. show );
holder = surface.getHolder();
paint = new Paint();
paint .setColor(Color. GREEN );
paint .setStrokeWidth(3);
Button sin = (Button) findViewById(R.id. sin );
Button cos = (Button) findViewById(R.id. cos );
OnClickListener listener = ( new OnClickListener()
{
@Override
public void onClick( final View source)
{
drawBack( holder );
cx = X_OFFSET ;
if ( task != null )
{
task .cancel();
}
task = new TimerTask()
{
public void run()
{
int cy = source.getId() == R.id. sin ? centerY
-( int ) (100 * Math.sin(( cx - 5) * 2 * Math. PI /150))
: centerY - ( int ) (100 * Math.cos(( cx -5)*2*Math. PI /150));
Canvas canvas = holder .lockCanvas( new Rect( cx
, cy-2, cx + 2, cy+2));
canvas.drawPoint( cx , cy, paint );
cx ++;
if ( cx > WIDTH )
{
task .cancel();
task = null ;
}
holder .unlockCanvasAndPost(canvas);
}
};
timer .schedule( task , 0,30);
}
});
sin.setOnClickListener(listener);
cos.setOnClickListener(listener);
holder .addCallback( new Callback()
{
@Override
public void surfaceChanged(SurfaceHolder holder
, int format, int width, int height)
{
drawBack(holder);
}
@Override
public void surfaceCreated( final SurfaceHolder myHolder)
{}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
timer .cancel();
}
});
}
private void drawBack(SurfaceHolder holder)
{
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color. WHITE );
Paint p = new Paint();
p.setColor(Color. BLACK );
p.setStrokeWidth(2);
canvas.drawLine( X_OFFSET , centerY , WIDTH , centerY , p);
canvas.drawLine( X_OFFSET , 40, X_OFFSET , HEIGHT , p);
holder.unlockCanvasAndPost(canvas);
holder.lockCanvas( new Rect(0,0,0,0));
holder.unlockCanvasAndPost(canvas);
}
private SurfaceHolder holder ;
private Paint paint ;
final int HEIGHT = 320;
final int WIDTH = 320;
final int X_OFFSET = 5;
private int cx = X_OFFSET ;
int centerY = HEIGHT / 2;
Timer timer = new Timer();
TimerTask task = null ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
setContentView(R.layout. main );
final SurfaceView surface = (SurfaceView)
findViewById(R.id. show );
holder = surface.getHolder();
paint = new Paint();
paint .setColor(Color. GREEN );
paint .setStrokeWidth(3);
Button sin = (Button) findViewById(R.id. sin );
Button cos = (Button) findViewById(R.id. cos );
OnClickListener listener = ( new OnClickListener()
{
@Override
public void onClick( final View source)
{
drawBack( holder );
cx = X_OFFSET ;
if ( task != null )
{
task .cancel();
}
task = new TimerTask()
{
public void run()
{
int cy = source.getId() == R.id. sin ? centerY
-( int ) (100 * Math.sin(( cx - 5) * 2 * Math. PI /150))
: centerY - ( int ) (100 * Math.cos(( cx -5)*2*Math. PI /150));
Canvas canvas = holder .lockCanvas( new Rect( cx
, cy-2, cx + 2, cy+2));
canvas.drawPoint( cx , cy, paint );
cx ++;
if ( cx > WIDTH )
{
task .cancel();
task = null ;
}
holder .unlockCanvasAndPost(canvas);
}
};
timer .schedule( task , 0,30);
}
});
sin.setOnClickListener(listener);
cos.setOnClickListener(listener);
holder .addCallback( new Callback()
{
@Override
public void surfaceChanged(SurfaceHolder holder
, int format, int width, int height)
{
drawBack(holder);
}
@Override
public void surfaceCreated( final SurfaceHolder myHolder)
{}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
timer .cancel();
}
});
}
private void drawBack(SurfaceHolder holder)
{
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color. WHITE );
Paint p = new Paint();
p.setColor(Color. BLACK );
p.setStrokeWidth(2);
canvas.drawLine( X_OFFSET , centerY , WIDTH , centerY , p);
canvas.drawLine( X_OFFSET , 40, X_OFFSET , HEIGHT , p);
holder.unlockCanvasAndPost(canvas);
holder.lockCanvas( new Rect(0,0,0,0));
holder.unlockCanvasAndPost(canvas);
}
}
程序每次绘制正弦、余弦波上的当前点时,无须重绘整个画面,SurfaceHolder只要锁定当前绘制点的小范围即可