2012. 7. 9. 13:36

[출처 : http://blog.naver.com/khs7515?Redirect=Log&logNo=20156449363 ] 


-----------------------------------------------------------------------------------------------------


이분 블로그를 앞으로 많이 이용할듯하다. 은근히 섬세한 설명에 이유까지 잘설명됨 


--------------------------------------------------------------------------------------------------


SurfaceView는 View보다 구조상 복잡하긴 하지만 성능이 더 좋기 때문에 게임, 또는 렌즈에 비치는 모습을 휴대폰 화면에

   빠르게 보여주기 위해서 사용된다.

# 예를 들어 설명하자면 100개의 물건을 옮기는데 View는 1개씩 옮겨다 놓지만 SurfaceView는 100개를 실어서 한번에 옮기는 형태랄까

# 화면이 갱신되는 동안 모든 객체의 정보를 담아두었다가 보여주는 것을 반복한다.

# 다음 예제는 임의로 움직이는 공을 무한하게 만들어서 SurfaceView가 어느 정도까지 버틸 수 있는가를 실험해보는 것이다.

# 모토로이 기준 1,500개정도가 되면 속도가 현저히 늦어지는 것을 볼 수 있었다.


******************************************** MainActivity.java *************************************************

package test.surfaceview.test2;


import android.app.Activity;

import android.os.Bundle;


public class MainActivity extends Activity {

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(new GameView(this));

    }

}

******************************************** GameView.java *************************************************

package test.surfaceview.test2;


import android.content.Context;

import android.util.Log;

import android.view.SurfaceHolder;

import android.view.SurfaceView;


/*

 * [[ 빠른 그래픽 처리를 위한 SurfaceView 사용하기 ]]

 * 1. surfaceview 클래스를 상속받는다.

 * 2. surfaceHolder.callback인터페이스를 구현한다.

 * 3. 인터페이스의 추상메소드를 오버라이딩 한다.

 * 4. 백그라운드에서 작업할 스레드를 만들어서 SurfaceHolder 객체를 넘겨준다.

 * 5. 스레드에서는 surfaceholder 객체를 이용해서 그래픽 작업한다.

 * 6. 

 */


//생성자 1개짜리는 View만 쓸때. 2개짜리는 다른것도 쓸때

public class GameView extends SurfaceView implements SurfaceHolder.Callback{

Context context;

SurfaceHolder sHolder;

GameThread gameThread;

int width, height; //surfaceView가 차지하고 있는 실제 공간의 크기

public GameView(Context context) {

super(context);

this.context = context;

init(); //초기화

}

//초기화하는 메소드

public void init(){

//SurfaceHolder객체 얻어오기

sHolder = getHolder(); //surface가 holder를 가지고 있기 때문에 그냥 getholder하면 된다.

//콜백 객체 등록

sHolder.addCallback(this);

//스레드 객체 생성

gameThread = new GameThread(context, sHolder);

}

//뷰화면의 크기가 변화가 생겼을 때 호출되는 메소드(화면의 폭과 높이 정보가 인자로 들어온다.)

public void surfaceChanged(SurfaceHolder holder, int format, int width,

int height) {

Log.e("*********", "surfaceChanged()");

this.width = width;

this.height = height;

gameThread.setScreenSize(width, height);

Log.e("*********", "width:"+width+" / height:"+height);

}

//뷰가 처음 만들어 질 때 호출되는 메소드

public void surfaceCreated(SurfaceHolder holder) {

Log.e("*********", "surfaceCreated()");

try {

//스레드를 시작시킨다.

gameThread.start();

} catch (Exception e) {

Log.e("********", "스레드 시작 시 에러 발생! 스레드를 다시 생성");

//에러 발생하면 재시작하기

restartThread();

}

}

//뷰가 파괴될 때 호출되는 메소드

public void surfaceDestroyed(SurfaceHolder holder) {

Log.e("*********", "surfaceDestroyed()");

//에러 없이 스레드를 안전하게 종료하기 위해

boolean tryJoin = true;

gameThread.stopForever(); //종료시 에러를 잡기 위한 핵심!

while(tryJoin){//join이 성공할때까지

try{

gameThread.join();

tryJoin=false;

}catch (Exception e) {

}

}

}

//스레드를 재시작하는 메소드

public void restartThread(){

//스레드 정지

gameThread.stopForever();

//스레드 비우고

gameThread = null;

//객체 다시 생성

gameThread = new GameThread(context, sHolder);

//화면의 폭과 높이 전달

gameThread.setScreenSize(width, height);

//스레드 시작

gameThread.start();

}

//스레드 일시정지하는 메소드

public void pauseThread(){

gameThread.pauseNResume(true);

}//일시 정지된 스레드를 재시작하는 메소드

public void resumeThread(){

gameThread.pauseNResume(false);

}

}


******************************************** GameThread.java *************************************************

package test.surfaceview.test2;


import java.util.ArrayList;

import java.util.Random;


import android.content.Context;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Paint.Style;

import android.util.Log;

import android.view.SurfaceHolder;


public class GameThread extends Thread{


Context context;

SurfaceHolder sHolder; //핵심개체이며 화면의 구성에 대한 정보를 모두 가지고 있다.

boolean isRun = true;

boolean isWait = false;

Canvas canvas; //화면에 무언가를 그리기 위한 캔바스 객체(sHolder가 가지고 있다)

int width, height;

//랜덤한 수를 발생시키기 위해

Random ran = new Random();

//색을 저장할 배열

Paint[] paint = new Paint[5];

//원 객체를 저장할 배열

ArrayList<Circle> list = new ArrayList<Circle>();

//생성자

public GameThread(Context context, SurfaceHolder sHolder){

this.context = context;

this.sHolder = sHolder;

//원을 그릴 색 만들어서 배열에 저장하기

initPaint();

}

//화면의 폭과 높이를 전달바든ㄴ다

public void setScreenSize(int width, int height){

this.width = width;

this.height = height;

Log.e("*********", "width:"+width+" / height:"+height);

}

//빠르게 돌아가는 스레드에서 메소드를 실행하거나 멤버필드가 교환되는 작업을 하면 

//스레드 작업이 깨질수있기 때문에 동기화가 필요하다.

//스레드의 일시정지 혹은 재시작 하는 메소드

public void pauseNResume(boolean isWait){

synchronized (this) {

this.isWait = isWait;

notify();

}

}

//스레드 완전 정지하는 메소드

public void stopForever(){

synchronized (this) {

this.isRun = isRun;

notify();

}

}

//스레드 본체인 run메소드에서 그리는 작업을 해준다.

@Override

public void run() {

while(isRun){ //isRun의 초기 값이 true 이므로 기본적으로 무한 루프이다.

//Canvas객체 얻어오기

canvas = sHolder.lockCanvas();//화면정보를 다 담을때까지 화면을 잠궈놓는다.

//화면에 그리는 작업을 한다.

try{

//동기화 블럭에서 작업을 해야한다.

  synchronized (sHolder) { //그리기위한 모든 작업을 하는 곳

  //canvas 객체를 이용해서 반복적인 그리기 작업을 한다.

  canvas.drawColor(Color.WHITE);

  //반복문을 돌면서 원을 그린다

  for(int i=0 ; i < list.size() ; i++){

  //해당 인덱스의 원객체를 얻어온다.

  Circle cir = list.get(i);

  canvas.drawCircle(cir.x, cir.y, //원의 위치

  5,  //반지름

  paint[cir.color]); //원의 색

  }

  canvas.drawText("공의수:"+list.size(), 0, 20, paint[0]);

  //원 만들기(10번 돌때 한번의 확률로 원을 만들기 위해)

  //if(ran.nextInt(5)==0)

  makeCircle();

  //원 움직이기

  moveCircle();

}

}finally{

if(canvas!=null)//실제로 화면을 그리는 곳(while을 돌면서 화면을 덧그리기 때문에 invalidate가 필요하지 않다)

//잠근 화면을 풀고 작업한canvas 객체를 전달해서 화면을 그린다.

sHolder.unlockCanvasAndPost(canvas);

}

//스레드를 일시 정지 하기 위해 

if(isWait){ //isWait의 초기값은 false 이다

try {

synchronized (this) {

wait(); //notify할때까지 기다린다.(일시정지)

}

} catch (Exception e) {}

}//if

}//while

}//run

//페인트 객체를 생성해서 배열에 담는 메소드

public void initPaint(){

//paint 객체 생성해서 배열에 담기

Paint p1 = new Paint();

p1.setColor(Color.DKGRAY);

p1.setStyle(Style.FILL); // 원을 채우도록 

Paint p2 = new Paint();

p2.setColor(Color.RED);

p2.setStyle(Style.FILL); // 원을 채우도록

Paint p3 = new Paint();

p3.setColor(Color.GREEN);

p3.setStyle(Style.FILL); // 원을 채우도록

Paint p4 = new Paint();

p4.setColor(Color.BLUE);

p4.setStyle(Style.FILL); // 원을 채우도록

Paint p5 = new Paint();

p5.setColor(Color.YELLOW);

p5.setStyle(Style.FILL); // 원을 채우도록

//배열에 담는다.

paint[0]=p1;

paint[1]=p2;

paint[2]=p3;

paint[3]=p4;

paint[4]=p5;

}

//circle 객체를 만드는 메소드

public void makeCircle(){

//원의 위치

int x = ran.nextInt(width); //화면의 폭 안의 랜덤한 x지점

int y = ran.nextInt(height); //화면의 높이 안의 랜덤한 y지점

//원의 초기 속도

int speedX = ran.nextInt(10)-5;

int speedY = ran.nextInt(10)-5;

//원의 색

int color = ran.nextInt(5);

//Circle 객체를 생성해서 배열에 담는다

Circle cir = new Circle(x, y, color, speedX, speedY);

list.add(cir);

}

//circle객체를 움직이는 메소드

public void moveCircle(){

//반복문 돌면서 원 객체 움직이기

for(Circle cir : list){

cir.x += cir.speedX;

cir.y += cir.speedY;

//화면을 벗어나지 못하도록 처리

if(cir.x <= 0 || cir.x >= width){

//속도의 부호를 바꾸어서 반대방향으로 움직인다.

cir.speedX *= -1;

//바뀐 속도로 한번 움직여 준다.

cir.x += cir.speedX;

}

if(cir.y <= 0 || cir.y >= height){

//속도의 부호를 바꾸어서 반대방향으로 움직인다.

cir.speedY *= -1;

//바뀐 속도로 한번 움직여 준다.

cir.y += cir.speedY;

}

}

}

}


******************************************** Circle.java *************************************************

package test.surfaceview.test2;


public class Circle {

int x, y;

int color;

int speedX, speedY; //속도

public Circle(int x, int y, int color, int speedX, int speedY) {

super();

this.x = x;

this.y = y;

this.color = color;

this.speedX = speedX;

this.speedY = speedY;

}


}

Posted by k1rha