"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. 콜백 메서드 재정의 | |||||||||
" 이벤트를 받는 가장 쉬운 방법은 이벤트 처리를 하는 방법이다. 해당 클래스를 상속받아 콜백 메서드를 재정의 하는 것이다. " 콜백 메서드? 프로세스가 시스템에 자신이 등록된 것을 알려주면 특정 상황에서 시스템이 프로세스를 호출하는 것이며, 그렇게 하기 위한 프로세스의 메서드를 콜백메서드라고 한다. 통상적으로 우리가 시스템상의 함수를 불러 사용하는 것이 아니라, 시스템에서 프로그래머가 만들어 놓은 함수를 이벤트가 발생하였을 때 실행시키는 방식을 의미한다. 즉, 전자는 무한루프를 도는 쓰레드를 생성하여, 어떤 이벤트를 실시간으로 검사하여 제어하는 방식이다. 반대로 후자는 이벤트가 발생하였을 때 시스템에서 이런 이벤트가 발생했을 때 사용자가 지정해 놓은 메소드를 호출하는 방식이다. 아래의 그림으로 이해해 보자. 콜백 메커니즘의 순서 ![]() 콜백 메서드는 서로 다른 스레드에서 호출되는 것이 일반적이다. 이렇게 다중 스레드를 사용하기 때문에 비동기(asynchronous) 작업을 수행 할 때비동기 작업이 완료되었음을 알리기 위한 방법으로 콜백 메커니즘이 많이 사용되곤 한다. 사용자와 상호작용하는 주체는 뷰가 된다. 이벤트 콜백은 뷰에서 제공된다. 커스텀 뷰에서 정의할 수 있는 콜백은 아래와 같다.
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. 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로 정의되어 있다. 그리고 각 인터페이스에는 이번트에 대응되는 이벤트를 받는 단 하나의 메서드가 선언되어 있다. 각 리스너는 아래와 같다.
1. 리스너 인터페이스를 설치한 클래스를 선언하고 추상메서드를 구현한다. 2. 리스너 객체를 선언 및 생성한다. 3. 준비된 리스너 객체를 뷰의 이벤트와 연결한다. 연결하는 뷰 객체의 메서드는 아래와 같다.
그러나 리스너는 인터페이스 구현을 위해 별도의 클래스를 하나 더 선언해야 한다는 면에서 번거롭다. 모든 이벤트에 일일이 클래스를 만든다면 소스의 양도 많아지고 각 클래스 마다 이름을 붙이는 것도 보통일이 아니게 된다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
실행 코드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 } 일반적으로 내부 클래스 대신에 익명 클래스를 사용하는 경우는 아래와 같다. |
실행 코드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 |