도래울

SurfaceView, 간단한 예제로 이해하기^^ 본문

개발/Android

SurfaceView, 간단한 예제로 이해하기^^

도래울 2016. 2. 5. 11:33

SurfaceView라는 좋은 놈을 알게 되어서, 그에 대해 정리해보고자 한다.
(소스코드를 보고싶은 사람은 첨부해둘테니 다운로드 받아서 보시길 ^^
프로젝트를 통째로 첨부했으니, 이클립스에서 다운로드 받으신 파일을 그대로 돌려보시면 됩니다.)

군더더기없이 알맹이만으로 구성된 간단한 프로그램이기 때문에, 
웹상에 있는 다른 예제들보다도 쉬울 거라고 생각한다.

프로그램은 크게 두가지 파일로 구성되어 있다.
ImgMove.java, GraphicsVies.java

ImgMove.java는 메인 엑티비티로, GraphicsView를 자신의 ContentView로 설정하게 된다.
그 소스코드는 다음과 같다.
===========================================
ImgMove.java
===========================================
package org.com;

import android.app.Activity;
import android.os.Bundle;

public class ImgMove extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new GraphicsView(this));
    }
}
===========================================
여기서는 큰 무리가 없을거라고 생각한다.
그럼 이제 GraphicsView.java로 넘어가보자.

이 클래스는 SurfaceView 클래스를 extends 한 것이다. 
보통 쓰레드를 내부클래스로 하나 만들어놓고 사용하는 경우가 많은데,
필자는 GraphicsView를 Runnable을 implements하여서 사용하였다.
(간단히 애니메이션 효과만 내고 싶은거라면 이게 더 편해보인다.)
그럼 이제 메인 코드로 넘어가보자.
=====================================================================
public class GraphicsView extends SurfaceView implements SurfaceHolder.Callback, Runnable{
/* 여기서 주목할 점은 바로 SurfaceView를 extends했고, SurfaceHolder.Callback을 implements했다는 점!!
 * SurfaceHolder.Callback 은 surface의 변화에 대한 정보를 얻어오기 위해서 반드시 implements해야 하는 인터페이스이다.
 * SurfaceView 와 SurfaceHolder.Callback가 함께 사용될 경우, 잡혀있는 Surface는 created되는 사이사이마다만 사용 가능하다.
 * 그렇기 때문에 exclusive한 성질을 띄게 되고, synchronized(동기화)될 수 있는 것이다. 이 점에서 SurfaceView가 View에 비해
 * 사용이 편리하다는 것인 듯 싶다.
 */
    private Display display;
    private int width;
    private int height;
    private Bitmap img;
    private Bitmap src;
    private Paint paint;
    private SurfaceHolder holder; // surface의 동기화를 위해 화면을 잡아주고 있을 홀더
    private Thread thread;  // surface에 그려주는 작업을 해줄 thread

    int x = 0;
    int y = 0;
    
    public GraphicsView(Context context){
        super(context);
        display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        width = display.getWidth();
        height = display.getHeight();
        paint = new Paint();
        src = BitmapFactory.decodeResource(this.getResources(), R.drawable.van);
        
        holder = getHolder(); // 홀더를 만들고,
        holder.addCallback(this); // 홀더에 이 SurfaceView에 대한 콜백 인터페이스를 제공해준다.
/*
 *  여기서 잠시, SurfaceHolder가 뭐하는 놈인지는 알고 넘어가는 편이 속시원할 것 같다.
 *  SurfaceHolder란, 화면의 디스플레이에 대한 권한을 가지고 있는 객체에 대한 인터페이스이다.
 *  그렇기 때문에, SurfaceHolder는 프로그래머로 하여금 surface size, format를 제어할 수 있게 해주고,
 *  surface의 픽셀을 수정하고, 변화를 모니터할 수 있게 해준다.
 */
        setFocusable(true); 
    }

/*  
 *  SurfaceHolder.Callback을 implements했다면 반드시 surfaceCreated, surfaceChanged, surfaceDestroyed를 구현해주어야 한다.
 *  안그러면 에러가 날테니까??
 */

public void surfaceCreated(SurfaceHolder holder){ 
// surface가 첫번째로 생성되자마자 바로 호출되는 메소드
        thread = new Thread(this); // 나중에 밑에서 구현될 스레드를 시작하는 지점.
        thread.start();
    }
    
    public void surfaceChanged(SurfaceHolder holder, int a, int b, int c){
// surface에 포멧이나 사이즈 등 어떠한 변화가 생기면 바로 호출되는 메소드
        
    }
    
    public void surfaceDestroyed(SurfaceHolder holder){
// surface가 종료되기 직전 바로 호출되는 메소드
        
    }

/*
 *  run()와 doDraw()는 이제 그리기 작업에 필요한 작업들만 해주면 된다.
 */
public void run(){
        int flag = 0;
        int flagCnt = 0;
        
        while(true){
            Canvas c = null; // 그림을 그릴 캔버스
            
            try{
                c = holder.lockCanvas(null); // 해당 캔버스에 이제 그림그리기를 시작하기 위해 lock을 얻어왔다.
                synchronized(holder){ 
// 동기화를 위해 반드시 필요한 synchronized. holder가 잡고 있는 surface에 대해서 비동기식이 아닌 동기식으로 업데이트가 진행되도록 해준다.
                    if(flag == 0 && y >= 0 && y < src.getHeight() - height){
                        doDraw(c); // 캔버스에 그림을 그립시다아~~
                        x++;
                        y++;
                        flagCnt++;
                        
                        if(flagCnt == src.getHeight() - height){
                            flag = 1;
                        }
                    }else{
                        doDraw(c);
                        x--;
                        y--;
                        flagCnt--;
                        
                        if(flagCnt == 0){
                            flag = 0;
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                if(c != null){
                    holder.unlockCanvasAndPost(c);
                    // surface의 픽셀들에 대한 수정작업을 마무리 한다. unlockCanvasAndPost가 호출되고 난 뒤에 수정된 픽셀들이 스크린에 반영이 된다.
                }
            }
        }
    }

    public void doDraw(Canvas c){
        img = Bitmap.createBitmap(src, x, y, width, height);
        c.drawBitmap(img, 0, 0, paint);
    }    

이 소스코드가 도움이 되었으면 한다.
다음은 외부 블로그에서 SurfaceView를 사용하기 위해 필요한 과정들에 대해 정리해져 있는 내용을 펌해온 것이다.
소스를 보고 공부를 했으면, 그를 바탕으로 마무리로 정리해본다는 마음으로 읽어보면 도움이 될 것이다.

출처: http://samse.tistory.com/entry/Android-Graphics-1

On a SurfaceView

SurfaceView View구 조내에서 surface 그리는 목적으로 고안된 특별한 subclass이 다목 적은 애 플리케이션의 두 번째 스 레드에서 별 도로 그 려서 시 스템의 View구 조에서 그 리기 타 임까지 기 다리지 않 도록 하 게 하 기 위 해 고 안되었다두 번째 thread에 서 참 조하는 SurfaceView 자신만의 Canvas 그리기를 수행한다.

1.     SurfaceView extend 새로운 클래스를 선언한다.

2.     SurfaceHolder.callback implement한 다 콜백 subclass 하부의 Surface 대한 정보와 함께통지하는데 생성변 경 또 는 제 거되었을  통지하게 된다 이벤트를 통해서 언제 그리기를 시작해야 하는지 알수 있고새 로운 surface속 성에 맞 게 조 정하고언 제 그 리기를 종 료하고 테 스트를 죽 일지등을알 수 있 다. SurfaceView에 서 두 번째 Thread 정의하여 모든 drawing procedure 수행하는 것이좋다.

3.     SurfaceHolder 생성한다. Surface 객 체를 직 접 처 리하지 않 고 SurfaceHolder 통해서 하는 것이좋다. SurfaceView 초기화되면 getHolder() SurfaceHolder 얻는다.

4.     addCallback() 호출하여 SurfaceHolder.Callback으 로부터 이 벤트를 수 신하도록 한 다. SurfaceHolder.Callback SurfaceView클 래스내에서 override하 여 addCallback()한 다.

5.     두번째 thread에 서 Surface Canvas draw하 려면 반 드시 SurfaceHandler 전달해야 하며lockCanvas() canvas 얻어야 한다이 렇게 해 야 SurfaceHolder 제공해준 Canvas 얻게 된다.

6.     Canvas drawing 한번 수행하면 canvas object 인자로 unlockCanvasAndPost() 호출해야 한다. lockCanvad() unlockCanvasAndPost() draw 반드시 쌍으로 호출되어야 한다.



Comments