본문 바로가기

java/android

이벤트 핸들러 1. 이벤트 처리 방법

[ 안드로이드 ] 이벤트 핸들러 1. 이벤트 처리에 관한 6가지의 방법

 | 
안드로이드에서는 이벤트를 처리하는 다양한 방법이 존재한다. 6가지의 방법이 존재하며, 아래의 하나씩 알아보도록 하자. 중복된 블로그들의 자료들이 있으므로, 필자는 미리 책 [ 안드로이드 프로그래밍 ]을 보고 작성한 것이라고 말하고 싶다. 책으로만으로는 부족하여, 각 부분에 각 주를 달아 좀 더 이해가 쉬운 설명이 되도록 하려고 노력했다. 왜냐하면, 필자는 스윙을 사용해본적도, 윈도우 프로그래밍을 해본적도 없는 사람이기 때문이다. 

"You can know the name of a bird in all the languages of the world, but when you're finished, you'll know absolutely nothing whatever about the bird... So let's look at the bird and see what it's doing-- that's what counts. I learned very early the difference between knowing the name of something and knowing something. 

파인만의 이야기로 이해를 시작해보자. 
 1. 콜백 메서드 재정의
" 이벤트를 받는 가장 쉬운 방법은 이벤트 처리를 하는 방법이다. 해당 클래스를 상속받아 콜백 메서드를 재정의 하는 것이다. "

콜백 메서드
프로세스가 시스템에 자신이 등록된 것을 알려주면 특정 상황에서 시스템이 프로세스를 호출하는 것이며, 
그렇게 하기 위한 프로세스의 메서드를 콜백메서드라고 한다. 

통상적으로 우리가 시스템상의 함수를 불러 사용하는 것이 아니라, 시스템에서 프로그래머가 만들어 놓은 함수를 이벤트가 발생하였을 때 실행시키는 방식을 의미한다. 즉, 전자는 무한루프를 도는 쓰레드를 생성하여, 어떤 이벤트를 실시간으로 검사하여 제어하는 방식이다. 반대로 후자는 이벤트가 발생하였을 때 시스템에서 이런 이벤트가 발생했을 때 사용자가 지정해 놓은 메소드를 호출하는 방식이다. 아래의 그림으로 이해해 보자.

콜백 메커니즘의 순서
(1) 호출자(Caller) 는 콜백 메서드의 참조(함수 포인터)를 매개 변수로 하여 피호출 메서드(Called method) 를 호출한다.
(2) 피호출 메서드는 매개 변수로 전달된 콜백 메서드에 대한 참조를 메모리 필드와 같은 곳에 기록해 둔다.
(3) 이제 콜백을 수행할 어떤 조건(이 조건은 다양할 수 있다) 즉 이벤트가 발생하한다.
(4) 기록해 둔 콜백 메서드 참조를 이용하여 콜백 메서드를 호출하게 된다.

   
 콜백 메서드는 서로 다른 스레드에서 호출되는 것이 일반적이다. 이렇게 다중 스레드를 사용하기 때문에 비동기(asynchronous) 작업을 수행  할 때비동기 작업이 완료되었음을 알리기 위한 방법으로 콜백 메커니즘이 많이 사용되곤 한다. 

사용자와 상호작용하는 주체는 뷰가 된다. 이벤트 콜백은 뷰에서 제공된다. 커스텀 뷰에서 정의할 수 있는 콜백은 아래와 같다.
Event processingonKeyDown(int, KeyEvent)Called when a new key event occurs.
onKeyUp(int, KeyEvent)Called when a key up event occurs.
onTrackballEvent(MotionEvent)Called when a trackball motion event occurs.
onTouchEvent(MotionEvent)Called when a touch screen motion event occurs.
이벤트로 들어온 MotionEvent의 간략한 설명은 아래와 같고, 자세한 설명은 차후에 다루도록 하자.

Motion events describe movements in terms of an action code and a set of axis values. The action code specifies the state change that occurred such as a pointer going down or up. The axis values describe the position and other movement properties.
For example, when the user first touches the screen, the system delivers a touch event to the appropriate View with the action code ACTION_DOWN and a set of axis values that include the X and Y coordinates of the touch and information about the pressure, size and orientation of the contact area.

다음은 KeyEvent 에 대한 간략한 설명이다.
Each key press is described by a sequence of key events. A key press starts with a key event with ACTION_DOWN. If the key is held sufficiently long that it repeats, then the initial down is followed additional key events with ACTION_DOWN and a non-zero value for getRepeatCount(). The last key event is a ACTION_UP for the key up.

View 클래스에 정의된 콜백 메소드들을 새로운 클래스에 상속하여 오버라이딩한다.  각 메서드 처음에 부모의 메서드를 호출하여 부모에게 데이터 처리 기회를 먼저 제공하는 것이 보편적인 방법이다. 이러한 방법은 아주 간단하고 직관적이다. 특정 사건에 대해 특정 메서드를 호출하기로 프레임워크와 약속되어 있으므로 지정된 원형대로 메서드를 재정의하면 된다. 그러나 이런 방법에는 다음과 같은 단점이 존재한다.

  ① 메서드를 재정의 하기 위해서 반드시 슈퍼 클래스를 상속받아서 새로운 클래스를 만들어야 한다. 이러한 방식은 View 객체같이 여러가지 이유로 재정의할때에는 문제가 되지 않지만, Button이나 TextView 같은 위젯의 경우는 이벤트를 처리하기 위해서 새로운 클래스를 만들어야 한다. 표준 위젯을 바로 사용하지 못하고, 한단계를 더 거쳐야 한다.
  ② 프레임워크에서 제공하는 이벤트는 일반적인 이벤트에는 적합하나 모든 이벤트가 정의되어 있지 않다. 모든 이벤트에 대해서 프로그래머가 일일이 다 메서드를 정의할 수는 없다. 이벤트는 사용자가 정의할 수도 있으므로 정해진 것을 불러내는 콜백메서드는 이벤트를 처리하는 일반적인 방법이 될 수 없다.

실행 코드
package exam.eventProcess;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

public class EventProcessActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myView vm = new myView(this);
        setContentView(vm);
    }
    
    protected class myView extends View {

		public myView(Context context) {
			super(context);
			// TODO Auto-generated constructor stub
		}
    	
		public boolean onTouchEvent(MotionEvent event){
			super.onTouchEvent(event);
// event.getAction()은 이벤트가 어떤 이벤트였는지를 호출하는 메소드이다.
			if(event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(EventProcessActivity.this, "TouchEventRecieved", Toast.LENGTH_LONG).show();
				return true;
			}
			return false;
		}
    	
    } 
}

 2. 리스너 인터페이스 구현
 리스너는 특정 이벤트를 처리하는 인터페이스이다. 리스너는 View 클래스의 Inner Interface로 정의되어 있다. 그리고 각 인터페이스에는 이번트에 대응되는 이벤트를 받는 단 하나의 메서드가 선언되어 있다. 각 리스너는 아래와 같다.
interfaceView.OnAttachStateChangeListenerInterface definition for a callback to be invoked when this view is attached or detached from its window.
Public Methods
abstract voidonViewAttachedToWindow(View v)
Called when the view is attached to a window.
abstract voidonViewDetachedFromWindow(View v)
Called when the view is detached from a window.
interfaceView.OnClickListenerInterface definition for a callback to be invoked when a view is clicked. 
Public Methods
abstract voidonClick(View v)
Called when a view has been clicked.
interfaceView.OnCreateContextMenuListenerInterface definition for a callback to be invoked when the context menu for this view is being built. 
Public Methods
abstract voidonCreateContextMenu(ContextMenu menu, View v,
 ContextMenu.ContextMenuInfo menuInfo)
Called when the context menu for this view is being built.
interfaceView.OnDragListenerInterface definition for a callback to be invoked when a drag is being dispatched to this view. 
Public Methods
abstract booleanonDrag(View v, DragEvent event)
Called when a drag event is dispatched to a view.
interfaceView.OnFocusChangeListenerInterface definition for a callback to be invoked when the focus state of a view changed. 
Public Methods
abstract voidonFocusChange(View v, boolean hasFocus)
Called when the focus state of a view has changed.
interfaceView.OnGenericMotionListenerInterface definition for a callback to be invoked when a generic motion event is dispatched to this view.
Public Methods
abstract booleanonGenericMotion(View v, MotionEvent event)
Called when a generic motion event is dispatched to a view.
interfaceView.OnHoverListenerInterface definition for a callback to be invoked when a hover event is dispatched to this view. 
Public Methods
abstract booleanonHover(View v, MotionEvent event)
Called when a hover event is dispatched to a view.
interfaceView.OnKeyListenerInterface definition for a callback to be invoked when a key event is dispatched to this view. 
Public Methods
abstract booleanonKey(View v, int keyCode, KeyEvent event)
Called when a key is dispatched to a view.
interfaceView.OnLayoutChangeListenerInterface definition for a callback to be invoked when the layout bounds of a view changes due to layout processing. 
Public Methods
abstract voidonLayoutChange(View v, int left, int top, int right, int bottom, 
int oldLeft, 

int oldTop, int oldRight, int oldBottom)
Called when the focus state of a view has changed.
interfaceView.OnLongClickListenerInterface definition for a callback to be invoked when a view has been clicked and held. 
Public Methods
abstract booleanonLongClick(View v)
Called when a view has been clicked and held.
interfaceView.OnSystemUiVisibilityChangeListenerInterface definition for a callback to be invoked when the status bar changes visibility. 
Public Methods
abstract voidonSystemUiVisibilityChange(int visibility)
Called when the status bar changes visibility because of a call to setSystemUiVisibility(int).
interfaceView.OnTouchListenerInterface definition for a callback to be invoked when a touch event is dispatched to this view. 
Public Methods
abstract booleanonTouch(View v, MotionEvent event)
Called when a touch event is dispatched to a view.
 
 이 메서드를 이벤트 핸들러라고 한다. 인터페이스는 어디까지나 메서드의 껍데기만을 가진 선언체일 뿐이므로 그 자체가 이벤트를 받을 수는 없다. 다만 특정 이벤트를 처리하는 핸들러의 원형이 어떠해야 한다는 것을 강제할 뿐이다. 리스너로 이벤트를 처리하는 절차는 아래와 같다.

1. 리스너 인터페이스를 설치한 클래스를 선언하고 추상메서드를 구현한다.
2. 리스너 객체를 선언 및 생성한다.
3. 준비된 리스너 객체를 뷰의 이벤트와 연결한다. 

 
연결하는 뷰 객체의 메서드는 아래와 같다.
voidsetOnClickListener(View.OnClickListener l)
Register a callback to be invoked when this view is clicked.
voidsetOnCreateContextMenuListener(View.OnCreateContextMenuListener l)
Register a callback to be invoked when the context menu for this view is being built.
voidsetOnDragListener(View.OnDragListener l)
Register a drag event listener callback object for this View.
voidsetOnFocusChangeListener(View.OnFocusChangeListener l)
Register a callback to be invoked when focus of this view changed.
voidsetOnGenericMotionListener(View.OnGenericMotionListener l)
Register a callback to be invoked when a generic motion event is sent to this view.
voidsetOnHoverListener(View.OnHoverListener l)
Register a callback to be invoked when a hover event is sent to this view.
voidsetOnKeyListener(View.OnKeyListener l)
Register a callback to be invoked when a key is pressed in this view.
voidsetOnLongClickListener(View.OnLongClickListener l)
Register a callback to be invoked when this view is clicked and held.
voidsetOnSystemUiVisibilityChangeListener(View.OnSystemUiVisibilityChangeListener l)
Set a listener to receive callbacks when the visibility of the system bar changes.
voidsetOnTouchListener(View.OnTouchListener l)
Register a callback to be invoked when a touch event is sent to this view.

 그러나 리스너는 인터페이스 구현을 위해 별도의 클래스를 하나 더 선언해야 한다는 면에서 번거롭다. 모든 이벤트에 일일이 클래스를 만든다면 소스의 양도 많아지고 각 클래스 마다 이름을 붙이는 것도 보통일이 아니게 된다.
실행 코드
package exam.eventProcess;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.widget.SimpleAdapter.ViewBinder;

public class EventProcessActivity2 extends Activity {
    TouchListener Tl = new TouchListener();

	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View vm = new View(this);
        vm.setOnTouchListener(Tl);
        setContentView(vm);
    }
    
    protected class TouchListener implements View.OnTouchListener {

		public boolean onTouch(View v, MotionEvent event) {
			// TODO Auto-generated method stub
			if ( event.getAction()== MotionEvent.ACTION_DOWN){ 
				Toast.makeText(EventProcessActivity2.this, "터치터치", 0).show();
				return true;
			}
				return false;
		}

		    
    }
}

 3. 액티비티에 리스너 인터페이스를 구현
 인터페이스는 메서드의 정의를 강제하는 문법적 장치이다. 그것을 어느 클래스가 구현해야 하는 가에 대해서는 제약이 없다. 인터페이스는 단지 메서드의 원형만을 제공한다. 어떤 클래스든지 임의의 인터페이스를 구혀할 수 있다. 안드로이드 프로젝트는 최소한 하나의 액티비티가 존재한다. 따라서 액티비티가 리스너 인터페이스를 구현하는 것이 가능하다. 

 앞서 방법과의 유일한 차이는 뷰와 리스너를 연결할 때, 다음과 같이 변형해야 한다. 그 이유는 2번 방법이 이너클래스에서 정의한 메소드였던 것과는 다르게 이번에는 액티비티 자체에서 정의하기 때문이다.
  
vm.setOnTouchListener(Tl);  -> 
 vm.setOnTouchListener(this);  

 이러한 방법은 구조가 간단하지만 액티비티라는 큰 단위가 하위의 뷰를 위한 메서드를 제공하게 된다. 이는 구조적이지 못하다. 만약 액티비티안에 뷰가 여러개가 있다면, 이러한 구조일 경우 뷰가 액티비티에 강하게 종속되게 된다. 즉, 뷰를 다른 액티비티로 옮기게 되면 다른 구조와 다르게, 리스너 코드를 분리시켜서 옮겨 주어야 한다. 뷰와 관련된 메서드가 뷰 자신에게 포함되지 않고 부모가 구현해 주기 때문에 독립성이 떨어진다.
실행 코드
package exam.eventProcess;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.widget.SimpleAdapter.ViewBinder;

public class EventProcessActivity3 extends Activity implements View.OnTouchListener{
	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View vm = new View(this);
        vm.setOnTouchListener(this);
        setContentView(vm);
    }

	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		if( event.getAction() == MotionEvent.ACTION_DOWN) 
		{
			Toast.makeText(this, "터치터치", 0).show();
			return true;
		}
			
		return false;
	}
}

 4. 뷰가 리스너 인터페이스를 구현
 앞서 액티비티가 리스너 인터페이스를 구현 한 것과 마찬가지로, 뷰도 클래스로서 리스너 인터페이스를 구현할 수 있다. 어떤 이유든 뷰를 파생시켰다면, 클래스 선언문이 있으므로 액티비티한테 종속적이지 않게 뷰 스스로가 리스너 인터페이스를 구현할 수 있다.

 자신에게 발생하는 이벤트를 자기 자신이 처리하는 구조이다. 뷰를 재활용하기에도 유리하며, 구조적으로 깔끔하다. 그러나, 리스너 구현만을 위해서 일부러 상속을 받는 것은 역시 낭비가 된다. 따라서, 이 방법은 어떤 이유로든 뷰를 상속받았을 경우에만 사용하는 것이 바람직하다.
실행 코드
package exam.eventProcess;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.widget.SimpleAdapter.ViewBinder;

public class EventProcessActivity4 extends Activity {
	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myView vm = new myView(this);
        setContentView(vm);
    } // end onCreate

	protected class myView extends View implements View.OnTouchListener{

		public myView(Context context) {
			super(context);
			// TODO Auto-generated constructor stub
		} // myView

		public boolean onTouch(View v, MotionEvent event) {
			// TODO Auto-generated method stub
			if(event.getAction() == MotionEvent.ACTION_DOWN){
				Toast.makeText(EventProcessActivity4.this, "터치합시다", 0).show();
				return true;
			}
			return false;
		} // end onTouch
		
	} // end myView
}  // end EventPr.. class

 5. 익명 이너 클래스 사용
자바 이벤트 처리 방식의 특성상 이벤트 하나당 객체 하나를 만들어야 한다. 이벤트 처리에 필요한 알맹이는 핸들러 메서드 뿐이다. 그러나 객체지향언어에서는 함수에 해당하는 메서드가 독립적으로 존재할 수 없다. 자바는 이런 경우를 대비하여 익명 이너 클래스라는 문법을 제공한다. 기본적인 구조부터 살펴보면 아래와 같다.
new class-name ( [ argument-list ] ){ class-body }-> object iv = new object() { void method() {}};
new interface-name () { class-body }

   일반적으로 내부 클래스 대신에 익명 클래스를 사용하는 경우는 아래와 같다.
       
       1.
The class has a very short body.
       2.
Only one instance of the class is needed.
       3.
The class is used right after it is defined.
      
 4.The name of the class does not make your code any easier to understand.

   익명의 내부클래스에서 중요한 것은 클래스 선언문이 아니라 변수 선언문이므로 문장 끝은 세미클론으로 끝나야 한다. 
실행 코드
package exam.eventProcess;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.widget.SimpleAdapter.ViewBinder;

public class EventProcessActivity5 extends Activity {
	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View vm = new View(this);
        vm.setOnTouchListener(Tl);
        setContentView(vm);
    } // end onCreate
    
    private View.OnTouchListener Tl = new View.OnTouchListener() {
		
		public boolean onTouch(View v, MotionEvent event) {
			// TODO Auto-generated method stub
			if ( event.getAction() == MotionEvent.ACTION_DOWN) {
				Toast.makeText(EventProcessActivity5.this, "터치 이너", 0).show();
				return true;
			}
			return false;
		}
	};
}  // end EventPr.. class


 6. 익명 이너 클래스의 임시 객체 사용
가장 함축된 방식의 이벤트 처리 코드로서, 액티비티 내부 변수로 선언했던 Tl처럼 오직 한번만 선언하는 이벤트 핸들러 객체에게 이름조차 줄 필요는 없다. 임시객체를 생성해서 등록 메서드의 인수로 전달해 버리면 된다. 즉 아래와 같이 축약해서 사용한다.

Method ( new Interface() { } ); 
or 
Method ( new class() {}); 

결과 코드는 아래와 같다.
실행 코드
package exam.eventProcess;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.widget.SimpleAdapter.ViewBinder;

public class EventProcessActivity5 extends Activity {
	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View vm = new View(this);
        vm.setOnTouchListener(new View.OnTouchListener() {
			
			public boolean onTouch(View v, MotionEvent event) {
				// TODO Auto-generated method stub
				if ( event.getAction() == MotionEvent.ACTION_DOWN )
				{
					Toast.makeText(EventProcessActivity6.this, "추상터치", 0).show();
					return true;
				}
				return false;
			}
		});
        setContentView(vm);
    } // end onCreate
    
}  // end EventPr.. class