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;
}
}
}