이번 예제는, 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" 선택
위와 같이 해서 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을 해서, 이 글의 맨 위 화면처럼 나오면 성공.
전체 소스
-끝-
'프로그래밍 > 안드로이드 앱 ' 카테고리의 다른 글
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 |