티스토리 뷰


 
이 자료들은 팁스소프트에서 제공하는 [ 알짜배기 ] 프로그램을 이용하면 더 편리하게 볼수 있습니다.
* 알짜배기 프로그램 받기 - http://www.tipssoft.com/bulletin/tb.php/QnA/8406
* 안드로이드 강좌 목록 - http://www.tipssoft.com/bulletin/tb.php/old_bbs/501
안드로이드 시스템을 사용하는 스마트폰은 대부분 터치를 기반으로하는 시스템이기 때문에 터치를 많이
사용하는 어플리케이션이 많습니다. 그러나 개발자가 프로그래밍할 때 반드시 터치에 대해서 잘 알아야
하는 것은 아닙니다. 안드로이드에서는 많은 정형화된 컨트롤이 존재하고, 해당 컨트롤들은 용도에 맞게
터치에 대한 사항을 처리해두었기 때문입니다.
하지만 안드로이드 시스템이 제공하는 뷰에 자신이 사용하려는 기능이 제공되지 않는 경우 사용자 정의
뷰를 만들어서 사용해야하는데 이러한 경우에는 사용자의 행위( 터치 등 )를 직접 처리해주어야 합니다.
직접 터치를 처리하려면 먼저 사용자 정의 뷰에 터치 행위가 발생했을 때 시스템이 전달하는 정보를
어떻게 받아오는지와 해당 정보를 어떻게 처리할 것인지를 잘 알아야할 것입니다. 이제부터 사용자가
행한 터치에 대해서 안드로이드 시스템이 어떻게 처리해주는지에 대해서 알아보도록 하겠습니다.
먼저 사용자 정의 뷰에 대해 알고 싶으신 분은 아래의 링크를 참고하여 주시기 바랍니다.
사용자정의 뷰 생성 및 배치1 - http://www.tipssoft.com/bulletin/tb.php/FAQ/964
1. MotionEvent 클래스에 대하여
안드로이드 시스템에서는 터치에 관한 각종 행위를 정의하기 위해서 MotionEvent 클래스
제공합니다. MotionEvent 클래스는 여러가지 사용자 행위에 대해서 처리하지만 이번 강좌에서는
터치와 관련되어 가장 기본적인 아래의 세가지만 설명하도록 하겠습니다.
ACTION_DOWN : 무언가가 스크린을 눌렸음을 나타내는 상수
ACTION_MOVE : 무언가가 스크린에서 이동되고 있음을 나타내는 상수
ACTION _UP : 무언가가 스크린에서 떼어졌음을 나타내는 상수
MotionEvent 객체는 어떤 행위에 의해서 이벤트가 발생했는지에 대한 정보와 해당 터치가 발생한
좌표 정보를 여러가지 형식으로 가지고 있으며 이벤트가 발생했을 때 이벤트 처리 메소드에 매개 변수
형식으로 전달 됩니다. 따라서 이벤트 처리기에서는 이 객체가 가지고 있는 정보를 기준으로 터치에
대한 작업을 수행하면 됩니다.
터치 행위로 발생한 터치 이벤트를 처리하는 방법에는 "콜백 메소드"를 이용하는 것과 사용자 뷰에
"터치 이벤트 리스너" 등록하여 사용하는 것이 있으며 이 두가지 방법 모두에 MotionEvent 객체가
매개변수로 전달됩니다.
2. 터치 이벤트를 콜백 메소드로 처리하기
사용자 정의 뷰에 터치 행위가 일어나면 안드로이드 시스템은 터치 이벤트를 발생시켜 콜백 메소드를
호출해줍니다. 콜백 메소드는 다음과 같은 원형을 가지고 있으며 매개 변수에는 터치의 상태 정보와
좌표 정보 등을 저장한 MotionEvent 객체가 전달됩니다.
boolean onTouchEvent(MotionEvent event);
이 이벤트 콜백 메소드는 boolean 형의 반환값을 가지는데 이것은 이벤트 처리의 완료 여부를
나타내는 것입니다. true 를 반환하면 이 이벤트에 대하여 처리를 완료했다는 것이고, false 를
반환하면 이벤트 처리를 완료하지 못하였다는 것이므로 다음 처리 권한의 핸들러로 이벤트가
전달됩니다. 이 반환값에 대한 부분은 여러개의 이벤트 핸들러를 동시에 사용할 때 주의해야할
사항이며 이 글의 4번 항목에서 자세하게 다루도록 하겠습니다.
아래의 코드는 터치이벤트를 처리하는 간단한 예제입니다.
public class TipsView extends View {
... 생략 ...
public boolean onTouchEvent(MotionEvent event) {
// 상위 클래스인 View 클래스에 발생한 이벤트를 전달한다.
super.onTouchEvent(event);
if(event.getAction() == MotionEvent.ACTION_DOWN) {
// 사용자가 액티비티 영역을 눌렀을 때 처리할 루틴을 정의한다.
return true; // 이 이벤트에 대한 처리를 완료하였다.
} else if(event.getAction() == MotionEvent.ACTION_MOVE) {
// 사용자가 액티비티 영역에서 움직였을 때 처리할 루틴을 정의한다.
return true;
} else if(event.getAction() == MotionEvent.ACTION_UP) {
// 사용자가 액티비티 영역에서 눌렀던 것을 떼었을 때 처리할 루틴을 정의한다.
return true;
}
// 누르고, 움직이고, 떼는 행위 외의 이벤트는 이 핸들러에서 처리하지 않았음을 알린다.
return false;
}
... 생략 ...
}
3. 터치 이벤트를 리스너로 처리하기
리스너를 이용하여 터치 이벤트를 처리하려면 먼저 사용자 정의 뷰에 자신이 사용할 리스너를 등록
해야합니다. 터치 이벤트에서 사용하는 리스너는 View.OnTouchListener 인터페이스이며 해당
인터페이스 내부에 선언된 onTouch 메소드를 재정의하여 사용자가 원하는 이벤트 처리를 할 수
있습니다.
리스너를 사용하려면 뷰나 뷰를 사용하는 액티비티 클래스에 implements 키워드를 이용하여
인터페이스를 추가한 후 onTouch 메소드를 재정의하거나 직접 View.OnTouchListener
인터페이스를 인스턴스하여 onTouch 메소드를 재정의해야합니다. 이렇게 구현된 리스너는
뷰의 setOnTouchListener 메소드를 이용하여 등록할 수 있습니다. 이 때 하나의 리스너가 여러개의
뷰에 등록될 수 있습니다.
아래의 코드는 액티비티에 리스너를 구현하여 뷰의 터치 이벤트를 처리하는 간단한 예제입니다.
코드에서 onTouch 메소드를 살펴보면 위에서 설명한 onTouchEvent 메소드와는 달리 매개변수에
View 객체가 추가적으로 존재하는 것을 볼 수 있습니다. 이것은 하나의 리스너가 여러개의 뷰에
등록될 수 있기때문에 이벤트가 발생한 뷰 객체를 쉽게 구분할 수 있도록 매개 변수로 전달해주는
것입니다.
// Activity 클래스를 상속받아 터치 이벤트 리스너를 구현하는 클래스를 정의한다.
public class TipsTouch extends Activity implements View.OnTouchListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout
.main);
// 리소스 파일에 정의된 id_my_view 라는 ID 의 뷰를 얻어온다.
TipsView view = (TipsView) findViewById(R.id.id_my_view);
// 이 액티비티 클래스에 구현한 리스너를 뷰에 설정한다.
view.setOnTouchListener(this);
... 생략 ...
}
// View.OnTouchListener 인터페이스의 구현부인 메소드이다. 터치 이벤트를 처리한다.
public boolean onTouch(View view, MotionEvent event) {
// XML 코드로 설정한 뷰의 ID가 id_my_view 인 경우
if(view.getID() == R.id.id_my_view) {
... 생략 ...
}
if(event.getAction() == MotionEvent.ACTION_DOWN) {
// 사용자가 액티비티 영역을 눌렀을 때 처리할 루틴을 정의한다.
return true; // 이 이벤트에 대한 처리를 완료하였다.
} else if(event.getAction() == MotionEvent.ACTION_MOVE) {
// 사용자가 액티비티 영역에서 움직였을 때 처리할 루틴을 정의한다.
return true;
} else if(event.getAction() == MotionEvent.ACTION_UP) {
// 사용자가 액티비티 영역에서 눌렀던 것을 떼었을 때 처리할 루틴을 정의한다.
return true;
}
// 누르고, 움직이고, 떼는 행위 외의 이벤트는 이 핸들러에서 처리하지 않았음을 알린다.
return false;
}
... 생략 ...
}
4. 이벤트 핸들러 우선순위
하나의 액티비티에서 여러개의 사용자 정의 뷰를 생성하여 터치 기능을 일괄적으로 처리하다보면
위에서 설명한 것보다 조금 더 복잡한 개념이 사용됩니다. 이 개념을 이해하기 위해서는 액티비티
에 의해 뷰가 생성되고 관리되어진다는 점을 기억해야 합니다.
터치 이벤트를 처리하는 방법이 리스너와 콜백 메소드인 것은 분명하지만 사용자 정의 뷰가
액티비티 위에 존재하는 것이기때문에 터치 이벤트가 발생했을 때 뷰에만 이 이벤트가 전달되는
것이 아니라 액티비티에도 전달됩니다. 따라서 이벤트를 처리하는 방법과 위치가 효율성에 영향을
미치기도합니다. 즉, 어떤 방법으로 이벤트를 처리할 것인지도 중요하지만 해당 핸들러가 뷰에 위치할
것인지 아니면 액티비티에 위치할 것인지를 결정하는 것도 중요합니다.
사실 사용자 정의 뷰에 발생한 터치 이벤트를 처리할 때 대부분 뷰에서 처리하기 때문에 이 이벤트가
액티비티에 전달되는 부분은 고려하지 않아도 되지만 고급 어플리케이션을 구현한다거나 어떤 작업이
액티비티에서만 처리할 수 있거나 여러 뷰에 대하여 통합적인 처리를 해야하는 등 특수한 경우에는
이 부분을 알아두는 것이 좋습니다. 예를 들면 하나의 액티비티에 세개의 뷰가 있을 때 모든 뷰에
발생한 터치의 횟수가 연산되어야 한다면 뷰에는 자신만의 처리를 수행하는 뷰의 콜백 메소드가 있고,
액티비티에는 모든 뷰의 터치 횟수를 합산하는 하나의 콜백 메소드가 구현되면 좋을 것입니다.
뷰에서 발생한 터치 이벤트는 아래와 같은 핸들러에서 처리할 수 있으며 이 핸들러들이 모두 구현되어
있는 경우에는 아래에 명시한 순서대로 수행됩니다.
① 뷰에 등록한 리스너
② 뷰에 재정의한 콜백 메소드
③ 액티비티에 재정의한 콜백 메소드
위의 우선순위를 기반으로 단순하게 생각한다면 뷰에 터치 행위가 발생하여 ACTION_DOWN,
ACTION_MOVE, ACTION_UP 이벤트가 발생할 때 각 이벤트마다 위의 리스너들이 순차적으로
수행되어 아래의 그림처럼 총 9번의 이벤트 처리가 수행될 것이라고 예상할 수 있습니다.
하지만 예상과 달리 이와 같이 수행되는 경우는 없습니다. 왜냐하면 각 핸들러를 정의할 때 해당
핸들러가 어떤 값을 반환하느냐에 따라서 다음에 수행될 핸들러가 결정되기 때문입니다.
각 핸들러별로 어떤 값을 반환하느냐에 따라서 핸들러의 수행 순서가 어떻게 변하는지 그림으로
표시하면 아래와 같습니다.
위의 그림처럼 반환값에 따라서 다음 우선순위의 핸들러 수행이 결정되는 것은 반환값이 이벤트의
처리 완료 여부를 알려주기 때문입니다. 핸들러가 true 를 반환하면 이벤트 처리가 완료된 것으로
판단하여 다음 핸들러에게 이벤트를 넘겨주지 않고, false 를 반환하면 이벤트 처리가 완료되지 않은
것으로 판단하여 다음 핸들러에게 동일한 이벤트를 넘겨주기 때문입니다.
하지만 반환값에 따른 이벤트 처리 여부도 ACTION_DOWN 행위에 대해서만 정확하게 동작합니다.
ACTION_MOVE 나 ACTION_UP 행위의 경우에는 ACTION_DOWN 을 처리할 때 어떤 핸들러가
어떤 반환값을 가졌느냐에 따라서 이벤트의 처리가 달라지게 됩니다.
위의 그림을 보면 ACTION_DOWN 행위에서 뷰의 리스너나 뷰의 콜백 메소드가 true 를 반환하지
않으면 ACTION_MOVE 와 ACTION_UP 행위에 대한 처리도 하지 못하는 것을 볼 수 있습니다.
즉, ACTION_MOVE 와 ACTION_UP 행위에 대해서 뷰의 이벤트 핸들러가 처리를 하려면 반드시
ACTION_DOWN 행위를 처리할 때 뷰의 리스너나 뷰의 콜백 메소드가 true 를 반환해야 하는
것입니다.
이것은 뷰와 액티비티는 엄연히 다른 개체이기때문에 ACTION_DOWN 이벤트가 발생하였을 때
뷰의 이벤트 핸들러중 아무도 true 를 반환하지 않으면 뷰에서는 ACTION_DOWN 이후에 발생하는
ACTION_MOVE 나 ACTION_UP 행위에 대하여 처리하지 않기때문에 생기는 현상입니다.
즉, ACTION_DOWN 에서 뷰의 핸들러들이 직접 이벤트 처리를 완료하지 않았으니 이어서 발생할
이벤트들은 최종 이벤트 처리자인 액티비티의 콜백 메소드를 바로 호출하는 것입니다.
그러므로 터치 이벤트 핸들러에서 행위별로 반환값을 달리 주는 경우에는 ACTION_MOVE 와
ACTION_UP 이 뷰의 이벤트 핸들러에서 처리되지 않을 수도 있음을 알아두는 것이 좋습니다.
아래와 같은 예제 코드를 수행하면
리스너 DOWN -> 뷰 콜백 DOWN -> 액티비티 콜백 DOWN ->
액티비티 콜백 MOVE -> 액티비티 콜백 UP
순서대로 이벤트가 처리됩니다.
public class TipsTouch extends Activity implements OnTouchListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout
.main);
// 리소스 파일에 정의된 id_my_view 라는 ID 의 뷰를 얻어온다.
TipsView view = (TipsView) findViewById(R.id.id_my_view);
// 이 액티비티 클래스에 구현한 리스너를 뷰에 설정한다.
view.setOnTouchListener(this);
... 생략 ...
}
// 뷰의 1순위 터치 이벤트 핸들러
// View.OnTouchListener 인터페이스의 구현부인 메소드이다. 터치 이벤트를 처리한다.
public boolean onTouch(View view, MotionEvent event) {
// XML 코드로 설정한 뷰의 ID가 id_my_view 인 경우
if(view.getID() == R.id.id_my_view) {
... 생략 ...
}
if(event.getAction() == MotionEvent.ACTION_DOWN) {
// 사용자가 액티비티 영역을 눌렀을 때 처리할 루틴을 정의한다.
return false; // 이 이벤트에 대한 처리를 완료하지 않았다.
} else if(event.getAction() == MotionEvent.ACTION_MOVE) {
// 사용자가 액티비티 영역에서 움직였을 때 처리할 루틴을 정의한다.
return false;
} else if(event.getAction() == MotionEvent.ACTION_UP) {
// 사용자가 액티비티 영역에서 눌렀던 것을 떼었을 때 처리할 루틴을 정의한다.
return true;
}
// 누르고, 움직이고, 떼는 행위 외의 이벤트는 이 핸들러에서 처리하지 않았음을 알린다.
return false;
}
public class TipsView extends View {
... 생략 ...
// 뷰의 2순위 터치 이벤트 핸들러
public boolean onTouchEvent(MotionEvent event) {
// 상위 클래스인 View 클래스에 발생한 이벤트를 전달한다.
super.onTouchEvent(event);
if(event.getAction() == MotionEvent.ACTION_DOWN) {
// 사용자가 액티비티 영역을 눌렀을 때 처리할 루틴을 정의한다.
return false; // 이 이벤트에 대한 처리를 완료하지 않았다.
} else if(event.getAction() == MotionEvent.ACTION_MOVE) {
// 사용자가 액티비티 영역에서 움직였을 때 처리할 루틴을 정의한다.
return true;
} else if(event.getAction() == MotionEvent.ACTION_UP) {
// 사용자가 액티비티 영역에서 눌렀던 것을 떼었을 때 처리할 루틴을 정의한다.
return true;
}
// 누르고, 움직이고, 떼는 행위 외의 이벤트는 이 핸들러에서 처리하지 않았음을 알린다.
return false;
}
... 생략 ...
}
// 뷰의 3순위 터치 이벤트 핸들러
public boolean onTouchEvent(MotionEvent event) {
// 상위 클래스인 Activity 클래스에 발생한 이벤트를 전달한다.
super.onTouchEvent(event);
if(event.getAction() == MotionEvent.ACTION_DOWN) {
// 사용자가 액티비티 영역을 눌렀을 때 처리할 루틴을 정의한다.
return true; // 이 이벤트에 대한 처리를 완료하였다.
} else if(event.getAction() == MotionEvent.ACTION_MOVE) {
// 사용자가 액티비티 영역에서 움직였을 때 처리할 루틴을 정의한다.
return true;
} else if(event.getAction() == MotionEvent.ACTION_UP) {
// 사용자가 액티비티 영역에서 눌렀던 것을 떼었을 때 처리할 루틴을 정의한다.
return true;
}
// 누르고, 움직이고, 떼는 행위 외의 이벤트는 이 핸들러에서 처리하지 않았음을 알린다.
return false;
}
... 생략 ...
}
}
※ 이 강좌에 첨부된 프로젝트는 핸들러 우선순위를 테스트하기 위해 만들어진 것입니다.
각 핸들러별로 세개씩 이벤트의 반환값을 선택하는 체크박스가 있으며 어떤 핸들러의 이벤트에
체크가 선택되면 해당 핸들러는 true 를 반환하고 체크가 해제되어 있으면 핸들러는 false 를
반환합니다. 뷰에 터치를 하면 Toast 로 현재 이벤트 발생상황을 순차적으로 보여줍니다.

 

댓글