프로그래밍/안드로이드 앱

006. Example04_SurfaceView

산을좋아한라쯔 2013. 11. 12. 20:08
반응형

이번 예제는, View를 빠르게 표출할 수 있는 SurfaceView에 대한 예제이다.

 

 

 

 

기본적인 View는, 시스템의 스케줄링에 따라 해당 차례가 왔을 때 자동으로 onDraw메서드가 호출되면서 수행된다. 따라서, 우선순위가 높은 다른 시스템 Job이 수행될 때는 느리게 View가 표출될 수가 있다. 따라서, View를 좀 더 빠르게 의도한대로 표출하고자 할 때는, 별도의 Thread에서 수동으로 View의 onDraw()를 호출해줘야 한다.

 

다음 예제는, MainThread에서 SurfaceView를 상속받은 MainView를 호출하는 방법을 보여주는 예제이다.

 

1. 신규 프로젝트 생성

"File - New - Other... - Android Application Project"한 후, Application Name = AndroidEx4_SurfaceView로 하고 나머지는 디폴트값으로 해서 신규 프로젝트 생성

 

 

 

 

2. activity_main.xml 레이아웃파일 수정

activity_main.xml탭을 선택하고, 안드로이드폰 윈도우가 보이게 Graphical Layout 탭을 선택한 후,

    • "Hello Word" TextView를 삭제
    • 안드로이드폰 윈도우에서 마우스 우클릭. "Change Layout"해서 Layout을 "Linear Layout(Vertical)"로 선택
    • 오른편 Properties창의 Layout Parameters에서 Width = fill_parent  Height=fill_parent

 

 

 

위와 같이 한 후, activity_main.xml탭을 선택해서, 아래와 같은 내용이 되도록 코딩

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <com.example.androidex4_surfaceview.MainView
        android:id="@+id/main_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

 

3. MainThread 작성

MainView의 onDraw를 호출하는 Thread클래스를 만들도록 하겠다. MainView는 바로 이어서 만들 것인데, SurfaceView를 상속해서 만들 것이고, onDraw메서드에 안드로이드 폰 화면에 표출되는 내용이 코딩된다.

 

MainThread를 만들자.

src/com.example.androidex4_surfaceview를 선택하고 마우스 우클릭. "New - Class" 선택

  • Name = MainThread
  • Superclass = java.lang.Thread (Browse버튼을 누르고 Thread로 search) 
  •  

     

     

    위와 같이 해서 Finish버튼을 누르면 아래와 같이 MainThread클래스가 자동생성될 것이다.

     

     

     

     

    아래와 같은 내용이 되도록 MainThread클래스를 코딩한다.

      • (아직 작성안된 MainView에 대해 코딩에러로 뜨겠지만 일단 무시
      • mainview.onDraw(c)에 대해서 직접 사용할 수 없다고 Eclipse에디터에서 에러처리하는 것은 무시.
        • 무시해도 되는 이유는 MainView의 생성자에 setWillNotDraw(false) 해줬기 때문
        • 무시하는 방법은 에러난곳에 마우스커서를 위치시켜 나타나는 팝업메시지창에서 "Disable Check in This file only" 선택 

     

     

     

    package com.example.androidex4_canvas;

    import android.graphics.Canvas;
    import android.util.Log;
    import android.view.SurfaceHolder;


    public class MainThread extends Thread {
      private SurfaceHolder surfaceHolder;
      private MainView mainView;
      private boolean isRunnable = false;
     
      public MainThread(SurfaceHolder holder, MainView view){
        this.surfaceHolder = holder;
        this.mainView = view;
      }
     
      public SurfaceHolder getSurfaceHolder(){
        return this.surfaceHolder;
      }
     
      public void setRunnable(boolean run){
        isRunnable = run;
      }
     
      @Override
      public void run(){
        try{
          Canvas c;
          while(isRunnable){
            c = null;
            try{         
              c = surfaceHolder.lockCanvas(null);
              synchronized(surfaceHolder){
                try{
                  Thread.sleep(1);
                  mainView.onDraw(c);
                }catch(Exception ex1){
                  Log.e("Error", ex1.toString());
                }          
              }        
            }finally{
              if(c != null)
                surfaceHolder.unlockCanvasAndPost(c);
            }
          }
        }catch(Exception ex2){
          Log.e("Error",ex2.toString());
        }
      }

    }

     

    run()메서드를 보면 isRunnable==true인 경우에만 mainView.onDraw가 호출됨을 알 수 있다. 이 isRunnable은 MainView가 생성될 때 true로 설정된다.

    mainView.onDraw에 Canvas객체를 같이 보내주는데, 이 Canvas객체는 MainThread가 생성될 때 넘겨준 SurfaceHolder의 lockCanvas()메서드로 뽑아낸다. 그리고, onDraw가 되는 것이 다른 Thread와 중첩되지 않게하기 위해 synchronized(surfaceholder)해준다.

    onDraw()에 의해 Canvas가 사용되고 난 후에는, surfaceholder.unlockCanvasAndPost()를 호출해서 Canvas객체를 unlock시켜줘야 한다.

    4. MainView 작성

    src/com.example.androidex4_surfaceview를 선택하고 마우스 우클릭. "New - Class" 선택

      • Name = MainView
      • Superclass = android.view.SurfaceView (Browse버튼을 누르고 SurfaceView로 search)
      • Interface = android.view.SurfaceHolder.Callback (Add버튼을 누르고 Callback으로 Search)

    위와 같이 해서 Finish버튼을 누르면 아래와 같이 MainView클래스가 자동생성될 것이다.

    아래와 같은 내용이 되도록 MainView클래스를 코딩한다.

     

    package com.example.androidex4_surfaceview;


    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.os.Handler;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.SurfaceHolder;
    import android.view.SurfaceHolder.Callback;
    import android.view.SurfaceView;

     

    public class MainView extends SurfaceView implements Callback {
      private MainActivity mainActivity;
      private MainThread mainThread;
      Handler handler;
      Context context;
      boolean isCls = false;

     

      public MainView(Context c, AttributeSet a){
        super(c,a);
        getHolder().addCallback(this);
        mainThread = new MainThread(getHolder(),this);
        setFocusable(true);
        context = c;   

      

         setWillNotDraw(false);
      }
     
      public void init(int width, int height, MainActivity activity){
        this.mainActivity = activity;
        isCls = true;
      }
     
      //override method of SurfaceView
      @Override
      public void onDraw(Canvas canvas){
        if(isCls == false) return;
       
        canvas.drawColor(Color.GRAY);
       
        Paint paint = new Paint();
       
        int w = canvas.getWidth();
        int h = canvas.getHeight();
       
        //draw circle
        paint.setColor(Color.RED);
        canvas.drawCircle(w/2, h/4, h/10, paint);
       
        //draw rectengle
        paint.setColor(Color.GREEN);
        int len = h/10;
        canvas.drawRect(w/2 - len, h/2, w/2 + len, h/2 + len, paint);
      }
     
      @Override
      public boolean onTouchEvent(MotionEvent event){
        return true;
      }
     
     
      @Override
      public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
        // TODO Auto-generated method stub
      }

      @Override
      public void surfaceCreated(SurfaceHolder arg0) {
        mainThread.setRunnable(true);
       
        try{
          if(mainThread.getState() == Thread.State.TERMINATED){
            mainThread = new MainThread(getHolder(), this);
            msinThread.setRunnable(true);
            setFocusable(true);
            mainThread.start();
          }else{
            mainThred.start();
          }    
        }catch(Exception ex){
          Log.i("MainView","ex:"+ex.toString());
        }
      }

      @Override
      public void surfaceDestroyed(SurfaceHolder arg0) {
        boolean retry = true;
        mainThread.setRunnable(false);
       
        while(retry){
          try{
            mainThread.join();
            retry = false;
          }catch(Exception ex){
            Log.i("MainView","surfaceDestroyed ex"+ex.toString());
          }
        }
      }

    }

     

     

    5. MainActivity 수정

    이제 마지막으로 MainActivity클래스를 아래와 같이 되도록 코딩한다.

      • getWindowManager().getDefaultDisplay().getWidth() 메서드가 Deprecicated되었다고 나오지만 여기서는 그냥 경고 무시

    package com.example.androidex4_surfaceview;

    import android.os.Bundle;
    import android.app.Activity;
    import android.graphics.Point;
    import android.view.Menu;
    import android.view.Window;
    import android.view.WindowManager;

    public class MainActivity extends Activity {

      MainView mainView;
     
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
           
        setContentView(R.layout.activity_main);
       
        int w = getWindowManager().getDefaultDisplay().getWidth();
        int h = getWindowManager().getDefaultDisplay().getHeight();
           
        mainView = (MainView)findViewById(R.id.main_view);
        mainView.init(w, h, this);
      }

      @Override
      public boolean onCreateOptionsMenu(Menu menu) {   
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
      }

    }

     

    6. 테스트

    Run을 해서, 이 글의 맨 위 화면처럼 나오면 성공.

     

    전체 소스  

    AndroidEx4_SurfaceView.zip

     

     

    -끝-

     

     

    AndroidEx4_SurfaceView.zip
    0.62MB
    반응형

    '프로그래밍 > 안드로이드 앱 ' 카테고리의 다른 글

    008. Example - ListActivity  (0) 2013.12.16
    007. Game - 두더지잡기  (0) 2013.12.16
    005. Example03_Canvas  (0) 2013.11.12
    004. Example02_Activity와 화면이동  (0) 2013.11.11
    003. Example01_Simple  (0) 2013.11.11