마음대로 바꿔요~
[Intro]
저번에 GestureDetector에 대해서 포스팅을 하면서 마지막에
GestureDetector에 단점이 존재 한다고 했었습니다.
살짝 치명적인 단점이 있는데,
그건 바로 이벤트 종료 시점을 알 수 없다는 것입니다.
이벤트가 시작될 때는 무조건 onDown 이벤트가 발생이 됩니다.
한 번 터치 하면 onSingleTap관련, onShowPress 같은 이벤트가 발생이 되고,
길게 누르면 onLongPress, 두 번 터치하면 onDoubleTap관련 이벤트가 발생이 됩니다.
onFling도 마지막 손을 떼는 순간 발생하죠.
위와 같은 이벤트들은 이벤트가 발생 한 후에 손가락이 떨어졌는지 검사할 필요가 없습니다.
한 번 터치나 두 번 터치하는 동작은 손가락이 떨어진 다음에 발생 하고,
플링 동작도 손가락이 화면에서 떨어지며 발생 합니다.
LongPress 동작도 손가락을 오래 눌러야 하는 동작이니 떼는것과 무관하죠.
가장 문제가 되는건 onScroll 이벤트입니다.
[Case Study]
그렇다면 왜 문제가 되는지 잠깐만 생각해 봅시다.
사실 이미지 재탕 입니다...
터치를 하여 빨간 사각형을 이동시키는 프로그램을 만들고자 합니다.
손가락을 계속 화면에 붙이고 있을 땐, 사각형이 계속 따라다니는 프로그램인데,
GestureDetector를 사용하면 onScroll 이벤트로 쉽게 만들 수 있습니다.
(한번 만들어 보세요~)
하지만 여기에다가 손을 뗐을때 다시 원래 위치로 돌아가는 기능을 추가 하고자 한다면...
SimpleOnGestureListener 만으로 가능 할까요?
답은 "안됩니다"
스크롤 이벤트는 플링이 아닌 이상
onDown - onScroll 반복 - onScroll로 끝나는 흐름을 가지고 있습니다.
onScroll 이벤트는 ACTION_DOWN과 ACTION_MOVE 동작만 감지하기 때문에
ACTION_UP 동작은 캐치 할 수 없습니다.
그렇기 때문에 만약 onScroll 이벤트가 발생 후
마지막으로 손을 떼었을 때 onScroll 이벤트로 끝이 났다면,
이게 손이 떨어졌는지 안떨어졌는지 알 방법이 없습니다.
그럼 어떻게 감지해야 할까요?
1. 오랫동안 onScroll이 호출 되지 않았다면 스크롤 끝난거 아닌가요?
뭐 그 말도 일리는 있습니다만, onScroll은 터치 좌표가 변해야 호출 되기 때문에
스크롤 후에 손 안떼고 가만히 있으면 onScroll 이벤트가 호출 되지 않습니다.
하지만 손을 떼지는 않았으니... 이 방법은 아닌것 같네요.
2. 터치 스크롤링 하면 보통 플링 동작으로 끝나지 않나요?
보통 많은 사람들이 그렇게 생각 할 수도 있지만, 그렇지 않습니다.
동작에 따라 안 일어 날 수도 있어요!
그렇다면 방법은 ACTION_UP 이벤트를 감지 하는 수 밖에 없겠군요...
[AdvancedGestureDetectorWrapper]
public class AdvancedGestureDetectorWrapper {
// Interfaces
public static interface OnFinishedListener {
public abstract void onFinished(MotionEvent e);
}
// Classes
public static class AdvancedOnGestureListener
extends GestureDetector.SimpleOnGestureListener
implements OnFinishedListener {
@Override
public void onFinished(MotionEvent e) {}
}
// Fields
private GestureDetector mDetector;
private AdvancedOnGestureListener mListener;
// Constructors
public AdvancedGestureDetectorWrapper(
Context context, AdvancedOnGestureListener listener) {
mListener = listener;
mDetector = new GestureDetector(context, mListener);
}
// Methods
public boolean onTouchEvent(MotionEvent ev) {
boolean onTouchEvent = mDetector.onTouchEvent(ev);
if(ev.getAction() == MotionEvent.ACTION_UP) {
mListener.onFinished(ev);
}
return onTouchEvent;
}
}
사용법은 보통 GestureDetector와 다르지 않습니다.
private final class AdvancedGestureListener
extends AdvancedGestureDetectorWrapper.AdvancedOnGestureListener {
// Implementation
}
private AdvancedGestureDetectorWrapper mGestureDetector;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mGestureDetector = new AdvancedGestureDetectorWrapper(
this, new AdvancedGestureListener());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
이름이 너무 길어졌습니다.
그리고 직접 GestureDetector를 extends하지 않고 필드로 가지고 있기 때문에
이름에 Wrapper라고 붙여서 더 길어졌군요.
여튼 중요한 부분은 Wrapper의 onTouchEvent 메소드 부분입니다.
기본 원리는 기본 GestureDetector에게도 MotionEvent를 전달 하고,
별도로 한번 더 MotionEvent를 검사하는 원리입니다.
코드상으로 보면 ACTION_UP일 때 무조건 onFinished 이벤트를 호출 하게 끔 되어있습니다.
아주 간단한 원리죠!
이런 원리를 이용하면 개발자가 원하는 다른 동작들도 만들어서 사용 할 수 있습니다.
기존의 제스쳐와 새로 만든 제스쳐를 같이 사용 할 수 있는 것이죠.
(그런 의미에서 Advanced라고 이름을 붙였답니다.)
사실 구현은 어떻게 하든지 상관은 없습니다.
전 최대한 기존 GestureDetector와 비슷하게 만들기 위해서 조금 복잡하게 만들었지만,
사실 뜯어보면 별거 없습니다.
원리를 간파하신 분이라면 꼭 새로이 클래스를 만들지 않고서
바로 Activity의 onTouchEvent 메소드에 직접 때려 넣어도 되겠거니 생각 하셨을겁니다.
네... 맞습니다. 그냥 GetureDetector에 한번 넣고 그 뒤에 또 검사하면 됩니다.
Wrapper와 Listener를 따로 만들 필요 없이 하나로 묶어서 만들 수도 있고,
여러가지 방법으로 자신의 코딩 스타일에 맞추어 개발 하시면 되겠습니다.
[Outro]
Java이기 때문에 기존의 클래스를 마음대로 바꿔버릴 수 있어서 참 좋은것 같습니다.
이런식으로 자신만의 라이브러리를 하나하나 늘려 나가면
나중에 매우 유용하게 쓸 수 있을 것입니다.
'Programming > Android' 카테고리의 다른 글
Parcelable을 사용한 오브젝트 전달 (Object serialization using Parcelable) (0) | 2013.12.26 |
---|---|
sendBroadcast와 startActivity의 차이 (0) | 2013.11.13 |
안드로이드의 Touch Event 디스패치 단계 (0) | 2013.01.31 |
onTouch() 와 onTouchEvent() 가 호출되는 순서 (0) | 2013.01.29 |
BluetoothSocket의 연결 오류 (2) | 2012.08.24 |