Android에서 touch기능을 구현할 때 2가지 방법이 있어
동작순서
View.OnTouchListener를 implement했을 경우()
onTouchEvent를 override했을 경우
호출방식
화면을 누르거나, 뗴거나, 닿은채로 움직이거나 할때 onTouch / onTouchEvent가 호출되.
View는 onTouchEvent라는 함수를 가지고 있어. View의 Child들은 전부 onTouchEvent를 사용할 수 있어.
우리는 View를 extend해서 onTouchEvent를 override할꺼야.
class touchEventExtendView extends View
{
@Override
public boolean onTouchEvent(MotionEvent event)
{
boolean isOnePointTouch = false;
float x = event.getX();
float y = eventgetY();
switch(event.getActionMasked())
{
// 화면을 손가락으로 터치할떄, 모든 Event의 출발
case MotionEvent.ACTION_DOWN:
// 다음에 발생할 후속 Event가 필요하다는 의미
isOnePointTouch = true;
break;
// 화면에서 손가락을 땠을경우, Event의 끝
case MotionEvent.ACTION_UP:
// click임으로, OnClickListener를 호출
perfomClick();
isOnePointTouch = true;
break;
// 화면에 손가락이 닿은 상태로 움직이고 있는 Event(움직일때마다 호출되)
case MotionEvent.ACTION_MOVE:
// false를 return한다고해서 ACTION_MOVE나 ACTION_UP Event가 안들어오는게 아니야.
// 사실상 의미없는 return값이야
isOnePointTouch = true;
break;
}
return ret;
}
}
사실 class 자체에서 View.OnTouchListener를 implement하는방식이나 특정 Activity에서 findViewById()로 특정 View를 가져와서 setOnTouchListener(this)를 호출하는 방식이나 똑같이 onTouch 메서드를 override하게끔 되어있어.
밑의 예시를 보자.
class MainActivity extens AppCompatActivity
{
.
.
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
.
.
// Touch listener 지정
findViewById(R.id.touch_screen).setOnTouchListener(this);
.
.
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
{
boolean isOnePointTouch = false;
if(view.getId() == R.id.touch_screen)
{
float x = motionEvent.getX();
float y = motionEvent.getY();
switch( motionEvent.getActionMasked() )
{
case MotionEvent.ACTION_DOWN:
isOnePointTouch = false;
break;
case MotionEvent.ACTION_UP:
// Click이 됨을 나타냄. OnClickListener를 호출
isOnePointTouch = false;
break;
case MotionEvent.ACTION_MOVE:
isOnePointTouch = true
break;
}
}
}
}
다양한 View에서 Touch event가 발생할수 있기 때문에 해당 View의 id로 구분지어서 처리해야해.
현재 MainActivity에서 여러 View Listener가 호출될 수 있기에 구분해 줘야되 각 View의 id로.
단일 View의 터치 event캡쳐
findViewById(R.id.sampleView).setOnTouchListener( new View.OnTouchListener() {...} 처럼 코드를 작성하면 이건 이미 특정 View에서만 Touch event가 작동(호출)되기 떄문에 구분지을 필요가 없어.
위의 예시 에서처럼 true를 return한다면 "후속 Event를 보내" & "현재 event 처리했어, 이번 event에 대해 추가작업 하지마" 이런 의미야.
false는? true의 반대겠지? 하지만 확 감이안오니 예를 들어볼꼐
위에서 말했듯이 setOnTouchListener가 기본적으로 지정한 Handler인onTouch가 먼저 호출되고, View의 onTouchEvent가 호출되(이건 유저가 선택)
핸들러 함수(onTouch)에 ACTION DOWN event가 들어오면
간단히 말하면
true가 리턴 : 다음에 올 event가 필요해!
false가 리턴 : 다음에 event 안와도되~
재미있게도 ACTION DOWN event에서 최종적으로 false를 return하게 되면 말 그대로 "아무것도 안할꺼야" 라는 의미가되. ACTION DOWN은 모든 event의 시작이거든, 그래서 이떄의 return값이 중요해.
public class MainActivity extends Activity
{
...
@Override
public boolean onTouchEvent(MotionEvent event)
{
int action = MotionEventCompat.getActionMasked(event);
switch(action)
{
case MotionEvent.ACTION_DOWN:
x = event.getX();
y = event.getY();
break;
case MotionEVent.ACTION_MOVE:
x = event.getX();
y = event.getY();
break;
}
return true;
}
}
발생한 event를 마스킹해서 어떤 Motion event(e.g. ACTION DOWN, ACTION MOVE..)가 발생했는지 확인해서 그에 맞는 처리를 해.
@Override
public boolean onTouchEvent(MotionEvent event)
{
// 터치가 발생된 포인트 수 (e.g. 화면에 손가락 몇개가 터치됐냐)
int pointer_cnt = event.getPointerCount();
if(pointer_cnt > 2)
{
// 터치를 2개 포인트(손가락 2개)까지만 처리함
pointer_cnt = 2;
}
int action = MotionEventCompat.getActionMasked(event);
switch(action)
{
case MotionEvent.ACTION_DOWN:
// 한개의 포인트에 대한 DOWN을 얻을때
num[0] = event.getPointerId(0); // 터치한 순간부터 부여받는 포인트 고유번호
x[0] = event.getX();
y[0] = event.getY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
// 두개 이상의 포인트에 대한 DOWN을 얻을때
for(int i = 0; i < pointer_cnt; i++)
{
num[i] = event.getPointerId(i);
x[i] = event.getX(i);
y[i] = event.getY(i);
}
break;
case MotionEvent.ACTION_MOVE:
for(int i = 0; i < pointer_cnt; i++)
{
num[i] = event.getPointerId(i);
x[i] = event.getX(i);
y[i] = event.getY(i);
}
break;
}
return true;
}
event.getPointerCount() 는 손가락 2개로 터치했다면 2를 return해줘.
2개의 포인트만 처리할 것이라 3개 이상의 터치가 인식되더라도 최근 2 포인트만 처리하도록 코드를 작성했어.
앱에서 1개의 포인트 & 2개 이상의 포인트 터치를 모두 처리해 주어야 하면 두개의 이벤트 (ACTION DOWN, ACTION POINTER DOWN) 모두 구현 해주어야해.
그럼 각각의 event에서 움직일떄는?
럭키하게 ACTION_MOVE event가 1 포인트나 2 포인트 모두 같은 ACTION_MOVE event로 들어오게되. (편하당)
event.getPointId(i)로 각 포인터에 대한 고유번호는 따로따로 저장해줘. Why?
2 포인트가 인식 되었다고 가정해보자.
num[0] = 0
x[0] = 50
y[0] = 50
num[1] = 1
x[0] = 180
y[0] = 180
첫번째 손가락을 떼면 어떻게 될까?
num[0] = 1
x[0] = 180
y[0] = 180
num[1] = 0
x[0] = 0
y[0] = 0
Index 1에 있던 배열이 0번쨰 배열로 넘어갔지?
이게 의미하는 바는 배열의 순서가 바뀌더라도 터치 상황을 문제없이 인식할수 있어 고유번호 덕분에.
그래서 다중 터치구현시 배열의 인덱스 기준이 아니라, 개별 포인트 고유 아이디값을 확인해서 처리해야해
정리:
ACTION_DOWN: 터치 포인트 1개
ACTION_POINTER_DOWN: 터치 포인트 2개 이상
다중 터치도중 하나 이상의 터치 포인트가 해제되면 좌표 데이터를 담고있는 배열의 순서가 바뀐다. 그래서 터치 포인터의 고유번호를 비교하는 방식으로 구현한다!