SurfaceView和View的明顯不同在於Surface不需要通過線程來更新視圖,但在繪製之前必須使用lockCanvas方法鎖定畫布,並得 到畫布,然後繪製,完成後用unlockCanvasAndPost方法解鎖畫布。SurfaceView類的事件處理和View一樣。
首先來看一個簡單的架構。
繪製介面類:
package com.example.bonusball;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;public class CanvasView extends SurfaceView implements SurfaceHolder.Callback{private SurfaceHolder myHolder;private Paint ballPaint; // Paint used to draw the cannonballprivate int screenWidth; // width of the screenprivate int screenHeight; // height of the screenprivate int ballRadius;private CanvasThread myThread;//控制迴圈 private boolean isLoop;public CanvasView(Context context) {super(context); // TODO Auto-generated constructor stubmyHolder=this.getHolder();myHolder.addCallback(this);ballPaint=new Paint();ballPaint.setColor(Color.BLUE);isLoop = true;}public void fireBall(float startX,float startY){System.out.println("Fire");}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {// TODO Auto-generated method stub}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);screenWidth = w; // store the widthscreenHeight = h; // store the heightballRadius=w/10;}@Overridepublic void surfaceCreated(SurfaceHolder holder) {// TODO Auto-generated method stubmyThread = new CanvasThread();System.out.println("SurfaceCreated!");myThread.start(); }@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// TODO Auto-generated method stub// 停止迴圈 isLoop = false;}public void drawGameElements(Canvas canvas){canvas.drawCircle(100, 100,ballRadius,ballPaint);}private class CanvasThread extends Thread{@Overridepublic void run(){while(true){synchronized( myHolder ){Canvas canvas = myHolder.lockCanvas(null);//擷取畫布drawGameElements(canvas);myHolder.unlockCanvasAndPost(canvas);//解鎖畫布,提交畫好的映像//System.out.println("run");}}}}}
事件處理 類:
package com.example.bonusball;import android.os.Bundle;import android.app.Activity;import android.view.Menu;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.view.GestureDetector;import android.view.GestureDetector.SimpleOnGestureListener;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.widget.Toast;public class BallActivity extends Activity {private GestureDetector myGestureDetector;//監聽手勢private CanvasView myCanvas; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); myCanvas=new CanvasView(this); setContentView(myCanvas); myGestureDetector = new GestureDetector(this, new MyGestureListener()); } @Override public boolean onTouchEvent(MotionEvent event) { return myGestureDetector.onTouchEvent(event); } private class MyGestureListener extends SimpleOnGestureListener { public boolean onDown(MotionEvent e1) { Toast.makeText(getApplicationContext(), "onDown", Toast.LENGTH_SHORT).show(); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { System.out.println("Fling");return true; } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_ball, menu); return true; }}
解釋幾個 概念 :
callback介面:
只要繼承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調用後該函數至少會被調用一次。
SurfaceHolder 類:
它是一個用於控制surface的介面,它提供了控制surface 的大小,格式,上面的像素,即監視其改變的。
SurfaceView的getHolder()函數可以擷取SurfaceHolder對象,Surface
就在SurfaceHolder對象內。雖然Surface儲存了當前視窗的像素資料,但是在使用過程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或 則Canvas lockCanvas()函數來擷取Canvas對象,通過在Canvas上繪製內容來修改Surface中的資料。如果Surface不可編輯或則尚未 建立調用該函數會返回null,在 unlockCanvas() 和 lockCanvas()中Surface的內容是不緩衝的,所以需要完全重繪Surface的內容,為了提高效率只重繪變化的部分則可以調用
lockCanvas(Rect rect)函數來指定一個rect地區,這樣該地區外的內容會緩衝起來。在調用lockCanvas函數擷取Canvas後,SurfaceView會獲 取Surface的一個同步鎖直到調用unlockCanvasAndPost(Canvas canvas)函數才釋放該鎖,這裡的同步機制保證在Surface繪製過程中不會被改變(被摧毀、修改)。
最後來看一個複雜一些的例子 ,結合了之前的手勢操作。
實現的效果是:在螢幕上進行Fling操作,在手滑動的方向上會產生一個運動的小球 ,速度 就是手勢滑動的速度。
球的大小隨機 ,顏色隨機 ,碰到邊界會反彈。
運行結果:
代碼清單:
小球類,主要是設定幾個屬性,還有幾個set,get方法。
package com.example.bonusball;public class Ball {private float posX;private float posY;private float velocityX;private float velocityY;private float radius;private int color;public Ball(int rgb,float r,float pX,float pY,float vX,float vY){this.color=rgb;this.radius=r;this.posX=pX;this.posY=pY;this.velocityX=vX;this.velocityY=vY;}public float getRadius(){return radius;}public int getColor(){return color;}public float getX(){return posX;}public float getY(){return posY;}public float getVX(){return velocityX;}public float getVY(){return velocityY;}public void setPosX(float newX){this.posX=newX;}public void setPosY(float newY){this.posY=newY;}public void setVX(float newVX){this.velocityX=newVX;}public void setVY(float newVY){this.velocityY=newVY;}}
主控類:
主要負責觸控時間的監聽。
package com.example.bonusball;import android.os.Bundle;import android.app.Activity;import android.view.Menu;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.view.GestureDetector;import android.view.GestureDetector.SimpleOnGestureListener;import android.view.MotionEvent;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.widget.Toast;public class BallActivity extends Activity {private GestureDetector myGestureDetector;//監聽手勢private CanvasView myCanvas; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); myCanvas=new CanvasView(this); setContentView(myCanvas); myGestureDetector = new GestureDetector(this, new MyGestureListener()); } @Override public boolean onTouchEvent(MotionEvent event) { return myGestureDetector.onTouchEvent(event); } private class MyGestureListener extends SimpleOnGestureListener { public boolean onDown(MotionEvent e1) { // Toast.makeText(getApplicationContext(), "onDown", Toast.LENGTH_SHORT).show(); //myCanvas.fireBall(e1.getRawX(),e1.getRawY(),0,0); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { System.out.println("Fling"); final int FLING_MIN_DISTANCE = 100, FLING_MIN_VELOCITY = 200; if (Math.abs(e1.getX() - e2.getX()) > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY) myCanvas.fireBall(e1.getRawX(),e1.getRawY(),velocityX/4,velocityY/4); return true; } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_ball, menu); return true; }}
畫板類:
負責圖形的繪製。
package com.example.bonusball;import java.util.ArrayList;import java.util.List;import java.util.Random;import java.util.concurrent.CopyOnWriteArrayList;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;public class CanvasView extends SurfaceView implements SurfaceHolder.Callback{private SurfaceHolder myHolder;private Paint ballPaint; // Paint used to draw the cannonballprivate int screenWidth; // width of the screenprivate int screenHeight; // height of the screenprivate int maxBallRadius;private CanvasThread myThread;private List<Ball> ballList;private Paint backgroundPaint;private Random mRandom;//控制迴圈private boolean isLoop;public CanvasView(Context context) {super(context); // TODO Auto-generated constructor stubmyHolder=this.getHolder();myHolder.addCallback(this);ballPaint=new Paint();backgroundPaint = new Paint();backgroundPaint.setColor(Color.BLACK);isLoop = true;ballList=new CopyOnWriteArrayList<Ball>();mRandom=new Random();}public void fireBall(float startX,float startY,float velocityX,float velocityY){int ranColor = 0xff000000 | mRandom.nextInt(0x00ffffff);float randomRadius=mRandom.nextInt(maxBallRadius);float tmpRadius=maxBallRadius/5.0>randomRadius?maxBallRadius:randomRadius;ballList.add(new Ball(ranColor,tmpRadius,startX,startY,velocityX,velocityY));System.out.println("Fire");}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {// TODO Auto-generated method stub}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);screenWidth = w; // store the widthscreenHeight = h; // store the heightmaxBallRadius=w/10;}@Overridepublic void surfaceCreated(SurfaceHolder holder) {// TODO Auto-generated method stubmyThread = new CanvasThread();System.out.println("SurfaceCreated!");myThread.start(); }@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {// TODO Auto-generated method stub// 停止迴圈isLoop = false;}public void drawGameElements(Canvas canvas){canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), backgroundPaint);for(Ball b:ballList){ballPaint.setColor(b.getColor());canvas.drawCircle(b.getX(),b.getY(),b.getRadius(),ballPaint);}}private void updatePositions(double elapsedTimeMS) {// TODO Auto-generated method stubfloat interval = (float) (elapsedTimeMS / 1000.0); for(Ball b:ballList){b.setPosX(b.getX()+b.getVX()*interval);b.setPosY(b.getY()+b.getVY()*interval);if (b.getX() + b.getRadius()> screenWidth ){b.setVX(-1*b.getVX());//邊界修複b.setPosX(screenWidth-b.getRadius());}if(b.getX() - b.getRadius() < 0){b.setVX(-1*b.getVX());b.setPosX(b.getRadius());}if (b.getY() + b.getRadius()> screenHeight){b.setVY(-1*b.getVY());b.setPosY(screenHeight-b.getRadius());}if(b.getY() - b.getRadius() < 0){b.setVY(-1*b.getVY());b.setPosY(b.getRadius());}}}private class CanvasThread extends Thread{@Overridepublic void run(){Canvas canvas=null;long previousFrameTime = System.currentTimeMillis(); while(isLoop){try{canvas = myHolder.lockCanvas(null);//擷取畫布synchronized( myHolder ){canvas.drawColor(Color.BLACK);long currentTime = System.currentTimeMillis();double elapsedTimeMS = currentTime - previousFrameTime;updatePositions(elapsedTimeMS); // update game statedrawGameElements(canvas);previousFrameTime = currentTime; // update previous time//System.out.println("run");}}finally{if (canvas != null) myHolder.unlockCanvasAndPost(canvas);//解鎖畫布,提交畫好的映像} // end finally}}}}
未解決問題一枚
理論上surfaceview是 帶雙緩衝的,但實際運行起來,映像還是有閃爍。
嘗試寫一個新的進程來處理映像的繪製,未果。