반응형
안녕하세요 돼지왕 왕돼지입니다.
오늘은 Surface View 에 대해 간단히 알아보겠습니다.
이 글과 코드는 김상형씨의 안드로이드 프로그래밍 정복을 정리한 내용입니다.
Surface View 가 뭐고, 왜 사용하나요?
일반 뷰는 Main Thread에서 캔버스에 그리기 수행합니다.
메인 스레드에서 그려야 하므로 속도가 빠르지 못하며( 다른 일들도 처리해야 하니.. ),
그리기를 하는 동안에는 사용자의 입력 받을 수 없습니다.
따라서 반응성이 좋지 못합니다. ( 게임류에는 쥐약입니다. 게임에서는 거의 대부분 SurfaceView 를 사용하죠.. )
그렇다고, 그리는 작업을 스레드로도 분리할 수도 없습니다.
안드로이드의 기본 정책으로, Main Thread 가 아닌 다른 스레드에서는 뷰나 캔버스를 직접 건드리지 못하기 때문이죠.
안드로이드의 기본 정책으로, Main Thread 가 아닌 다른 스레드에서는 뷰나 캔버스를 직접 건드리지 못하기 때문이죠.
믿을 수 없다. Main 에서 그려보자.
class Ball{
int x, y;
int rad;
int dx, dy;
int color;
int count;
// 볼 만들기
static Ball Create(int x, int y, int Rad){
Random Rnd = new Random();
Ball NewBall = new Ball();
NewBall.x = x;
NewBall.y = y;
NewBall.rad = Rad;
do{
NewBall.dx = Rnd.nextInt(11) - 5;
NewBall.dy = Rnd.nextInt(11) - 5;
}while (NewBall.dx ==0 || NewBall.dy == 0);
NewBall.count = 0;
NewBall.color = Color.rgb(Rnd.nextInt(256), Rnd.nextInt(256), Rnd.nextInt(256));
return NewBall;
}
// 볼 이동
void Move(int Width, int Height){
x += dx;
y += dy;
if (x <rad || x > Width - rad){
dx *= -1;
count++;
}
if (y < rad || y > Height - rad){
dy *= -1;
count++;
}
}
// 볼 그리기
void Draw(Canvas canvas){
Paint pnt = new Paint();
pnt.setAntiAlias(true);
int r;
int alpha;
// 동심원을 그린다. (부드러운 느낌의 공이 나옴)
for (r = rad, alpha = 1; r > 4; r--, alpha +=5){
pnt.setColor(Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)));
canvas.drawCircle(x, y, r, pnt);
}
}
}
class MyView extends View{
Bitmap mBack;
ArrayList<Ball> arBall = new ArrayList<Ball>();
final static int DELAY = 50;
final static int RAD = 24;
public MyView (Context context){
super(context);
mBack = BitmapFactory.decodeResource(context.getResources(), R.drawable.family);
mHandler.sendEmptyMessageDelayed(0, DELAY);
}
// Touch Event에 반응
public boolean onTouchEvent(MotionEvent event){
if (event.getAction() == MotionEvent.ACTION_DOWN){
Ball NewBall = Ball.Create((int)event.getX(), (int)event.getY(), RAD);
arBall.add(NewBall);
invalidate();
return true;
}
return false;
}
// 볼 그려주는 역할
public void onDraw(Canvas canvas){
canvas.drawBitmap(mBack, 0, 0, null);
for (int idx = 0; idx < arBall.size(); idx++){
arBall.get(idx).Draw(canvas);
}
}
// 볼 이동시키고 제거해주는 역할 + 다시 드로우
Handler mHandler = new Handler(){
public void handleMessage(Message msg){
Ball B;
for (int idx = 0; idx < arBall.size(); idx++){
B = arBall.get(idx);
B.Move(getWidth(), getHeight());
if (B.count > 4){
arBall.remove(idx);
idx--;
}
}
invalidate();
mHandler.sendEmptyMessageDelayed(0, DELAY);
}
};
}
어떤가요? 위와 같이 하면 CPU 점유율도 금방 높아지고, 말한대로 반응성도 안 좋아지죠??
금방 ANR 이 발생합니다.
SurfaceView 에 대해 그럼 먼저 자세히 알려주세요.
- SurfaceView는 canvas가 아닌 Surface를 갖고 있습니다. (가상 메모리 화면)
-> 메인 스레드가 표면(Surfcae)의 변화를 감지해서 스레드에게 그리기 허용 여부를 알려 줘야 하며, 이는 SurfaceHolder.Callback 으로 합니다.
- void surfaceCreated (SurfaceHolder holder)
: 표면이 처음 생성된 직후에 호출됩니다. 이때부터 표면에 그리기가 허용됩니다.
표면에는 한 스레드만 그리기를 수행할 수 있습니다.
void surfaceDestroyed (SurfaceHolder holder)
: 표면이 파괴되기 직전에 호출됩니다. 이게 리턴된 후에는 더 이상 그리면 안됩니다.
void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
: 표면의 색상이나 포맷이 변경되었을 때 호출. 최소한 한번 호출됩니다.
이 메서드로 전달된 인수를 통해 표면의 크기 초기화하곤 하죠.
class MyView extends SurfaceView implements SurfaceHolder.Callback
: 표면 관리주체는 SurfaceHolder. 이 객체를 통해 표면 크기, 색상 등을 관리합니다.
즉 View 는 보여주기만 할 뿐, Control 은 Holder 로 한다는 이야기지요.
즉 View 는 보여주기만 할 뿐, Control 은 Holder 로 한다는 이야기지요.
SurfaceHolder SurfaceView.getHolder()
void SurfaceHolder.addCallback (SurfaceHolder.Callback callback)
: 시스템이 표면의 변화가 발생할 때마다 콜백 메서드 호출
Canvas SurfaceHolder.lockCanvas ()
: 표면을 잠그로 표면에 대한 캔버스 제공합니다. lock 된 상태에서 이 canvas에 그림을 그리고, unlock 이 되면 화면출력이 됩니다.
void SurfaceHolder.unlockCanvasAndPost (Canvas canvas)
: 표면 비트맵에 그려진 그림을 화면으로 내보내 출력을 한다.
이번엔 SurfaceView 를 사용하여 그려보자.
class SurfView extends SurfaceView implements SurfaceHolder.Callback{
Bitmap mBack;
ArrayList<Ball> arBall = new ArrayList<Ball>();
final static int DELAY = 50;
final static int RAD = 24;
SurfaceHolder mHolder;
DrawThread mThread;
public SurfView (Context context){
super(context);
mBack = BitmapFactory.decodeResource(context.getResources(), R.drawable.family);
mHolder = getHolder();
mHolder.addCallback(this);
}
public void surfaceCreated(SurfaceHolder holder){
mThread = new DrawThread(mHolder);
mThread.start();
}
public void surfaceDestroyed(SurfaceHolder holder){
mThread.bExit = true;
for (;;){
try{
mThread.join(); // Thread 종료 기다리기
break;
}
catch (Exception e){;}
}
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
if (mThread != null){
mThread.SizeChange(width, height);
}
}
public boolean onTouchEvent(MotionEvent event){
if (event.getAction() == MotionEvent.ACTION_DOWN){
synchronized(mHolder){
Ball NewBall = Ball.Create((int)event.getX(), (int)event.getY(), RAD);
arBall.add(NewBall);
}
return true;
}
return false;
}
class DrawThread extends Thread{
boolean bExit;
int mWidth, mHeight;
SurfaceHolder mHolder;
DrawThread(SurfaceHolder Holder){
mHolder = Holder;
bExit = false;
}
public void SizeChange(int Width, int Height){
mWidth = Width;
mHeight= Height;
}
public void run(){
Canvas canvas;
Ball B;
while (bExit == false){
for (int idx = 0; idx < arBall.size(); idx++){
B = arBall.get(idx);
B.Move(mWidth, mHeight);
if (B.count > 4){
arBall.remove(idx);
idx--;
}
}
synchronized(mHolder){
canvas = mHolder.lockCanvas();
if (canvas == null) break;
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(mBack, 0, 0, null);
for (int idx = 0 ; idx < arBall.size(); idx++){
arBall.get(idx).Draw(canvas);
if (bExit) break;
}
mHolder.unlockCanvasAndPost(canvas);
}
try {Thread.sleep(MyView.DELAY);} catch(Exception e){;}
}
}
}
}
로그인 없이 추천 가능합니다. 손가락 꾸욱~
반응형
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Android/안드로이드] Tween Animation 사용해보자. (2) | 2012.02.18 |
---|---|
[Android/안드로이드] Animation Listener 를 사용해보자. (0) | 2012.02.18 |
[Android/안드로이드] MapView 사용을 위한 기본 설정. (0) | 2012.02.18 |
[Android/안드로이드] GeoCoder 사용법. (0) | 2012.02.18 |
[Android/안드로이드] GPS 관련 APIs with simple example. (0) | 2012.02.18 |
댓글