Android中SurfaceView,是不使用遊戲引擎,開發一款遊戲的常用view控件。
SurfaceView常常被用來顯示那些更新速度比較快(這個速度通常人眼無法識別)的圖像,常常被用來顯示照相機的當前效果,視頻的播放,遊戲界面的播放。
SurfaceView的創建:
繼承SurfaceView就可以使用構造方法創建了。
所要從新寫的方法也只不過一個構造方法,這裏使用的構造常常有兩種,一種使用只有一個參數的構造方法,參數爲context,如果使用這樣的構造方法,則創建SurfaceView的Activity不能加載layout文件夾中的xml文件,只能加載new出來的view如
public class GameMainActivity extends Activity {
public static GameView Game;
public static AssetManager assets;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Assets.mContentResolver=getContentResolver();
Game=new GameView(GameMainActivity.this);
setContentView(Game);
assets=getAssets();
}
}
這裏的GameView繼承自SurfaceView
如果使用兩個參數的構造方法:
Context context, AttributeSet attrs
則可以將自己View類當做Activity佈局文件中的一個空間,當佈局文件顯示時會自動調用這個構造方法,第二參數用於返回相應的指令給View。
但是這裏建議使用第一個方法構造,因爲在遊戲中的界面跳轉與外部Activity的跳轉邏輯完全不同,所以在佈局文件中加載會有一定的弊端。但是好處是這樣可以使用Android封裝好的控件(如button)。
SurfaceView有一個特點:
他並不是當Activity創建 就會可以調用的,裏面的那些控件,並不是真的控件,當Activity創建完成就可以使用了,而是當SurfaceView創建,並出現在屏幕上之後纔可以開始繪畫(即僞控件的加載)。
這時就需要一個監聽器監聽SurfaceView的創建,改變和銷燬。
使用監聽器
SurfaceHolder.Callback
這裏要實現這個接口,重寫其中的三個主要方法:
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) public void surfaceDestroyed(SurfaceHolder holder) 這三個方法代表了SurfaceView生命週期,注意只有在這三個聲明週期中纔可以進行其中的操作,在Activity中的操作要與之區分。 當創建時自動調用createed和changed方法,在home鍵,back鍵返回時調用destroyed方法,但是注意home鍵返回再回來依次調用created方法,和changed方法,但是back鍵返回再回來就會調用構造方法和created方法,changed方法。設置監聽時要先初始化SurfaceView的管家類SurfaceHolder,通過在View中調用父類SurfaceView的方法getholder就可以簡單的獲取管家。
再使用holder.addCallBack()進行設置監聽聲明週期
SurfaceView的繪畫,一般在線程中進行繪畫操作。
繪畫之前要得到View的主畫布,通過holder來實現,這時體現瞭如果線程在View類中就可以很好地得到主畫布,所以GameView通常可以實現Runnable接口。
Canvas的得到使用holder.lockCanvas()的返回值得到,得到主畫布後就可以使用canvas中的各種繪畫方法進行繪畫了。
如果想不斷地更新,就要使用循環,也就是死循環進行一遍一遍地render和update進行繪畫,與邏輯更新,這裏就是遊戲主循環。
SurfaceView的事件監聽使用OnTouchListener和MotionEvent,這裏由於不能使用Android的諸多控件,只能使用這種觸控事件。諸多組件也是畫上去的再通過座標的對比,進行事件的響應。
這裏由於遊戲代碼所涉及的類諸多,只展示一個Demo,一個畫板的實現(沒有適配不同機型)
public class MyView extends SurfaceView implements Runnable,SurfaceHolder.Callback,View.OnTouchListener {
Context main;
SurfaceHolder holder;//主holder
Canvas canvas;//主畫布
Paint p;
Thread t;
Bitmap buffer=null;//二級緩存
Path mpath;//觸控中的軌跡
float startX;
float startY;
Rect window;
Rect bufferRect;
boolean run=false;
public MyView(Context context) {
super(context);
init(context);
setOnTouchListener(this);
}
private void init(Context context){
holder=getHolder();
holder.addCallback(this);//勿忘addcallback
this.main=context;
p=new Paint();
p.setAntiAlias(true);//消除鋸齒
p.setStyle(Paint.Style.STROKE);//設置畫筆風格
p.setAlpha(255);//畫筆的不透明度
p.setStrokeWidth((float)2);//設置筆觸寬度
p.setColor(Color.WHITE);
mpath=new Path();
buffer= Bitmap.createBitmap(1440,2256, Bitmap.Config.ARGB_8888);//很重要創建二級緩存的必要方法
t=new Thread(this);
}
@Override
public void run() {
while(run){
canvas=holder.lockCanvas();
window=canvas.getClipBounds();
Canvas c=new Canvas(buffer);
c.drawRect(window,p);
p.setColor(Color.rgb(new Random().nextInt(255),new Random().nextInt(255),new Random().nextInt(255)));
c.drawPath(mpath,p);
bufferRect=new Rect(0,0,buffer.getWidth(),buffer.getHeight());
canvas.drawBitmap(buffer,bufferRect,window,new Paint());
holder.unlockCanvasAndPost(canvas);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
run=true;
Log.i("Created", "surfaceCreated: ");
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i("Changed", "surfaceChanged: ");
t=new Thread(this);
t.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
run=false;
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i("Destroyed", "surfaceDestroyed: ");
}
private void onTouchDown(MotionEvent event){
startX=event.getX();
startY=event.getY();
mpath.reset();
mpath.moveTo(startX,startY);
}
private void onTouchMove(MotionEvent event){
float touchX=event.getX();
float touchY=event.getY();
float dx=Math.abs(touchX-startX);//移動的距離
float dy =Math.abs(touchY-startX);//移動的距離
if(dx>3||dy>3){
float cX=(touchX+startX)/2;
float cY=(touchY+startY)/2;
mpath.quadTo(startX, startY, cX, cY);//繪製貝塞爾曲線
startX=touchX;
startY=touchY;//改變開始繪製的點
}//這裏巧妙地進行篩選過於短的移動
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
this.onTouchDown(event);
break;
case MotionEvent.ACTION_MOVE:
this.onTouchMove(event);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}