[안드로이드] 터치화면, 제스처 기능을 이용한 터치 인식

2010.04.30 16:47

 

   

<목표> 

 

[안드로이드] 터치화면, 제스처 기능을 이용한 터치 인식

 

 

 오늘은 터치화면을 이용한 제스처 기능에 대해서 알아보겠습니다. 제스처 기능은 말그래로 "동작, 움직임" 을 감지합니다. 하지만 핸드폰 자체의 센서를 이용한 움직임은 아닙니다. 즉, 핸드폰을 흔들거나, 기울이거나를 해서 얻는 이벤트를 이용해서 어떠한 기능을 수행하는 기능이 아닙니다. 제스처 기능은 화면 내의 터치를 감지하여, 어떠한 이벤트가 들어왔는지를 분석하여 어떤 기능을 수행할 수 있도록 합니다. 간단한 예로는 화면을 넘기는 것을 예로 들 수 있습니다. 흔히 메인 화면은 여러 개의 화면으로 구성되어 있습니다. 왼쪽에서 오른쪽으로 화면을 넘기기 위해서 손가락을 이용해서 왼쪽에서 오른쪽으로 드래그하는 동작을 취합니다. 이런 방식으로 손가락 모션을 인식하는 것이 제스처 기능입니다.

 

 아래의 예제 코드는 왼쪽에서 오른쪽으로 드래그했을 때, 오른쪽에서 왼쪽으로 드래그를 했을 때, 아래에서 위로, 위에서 아래로 손가락으로 드래그했을 시에 이벤트를 발생시킬 수 있도록 하는 기능을 담고 있습니다. 이벤트가 발생하면 간단하게 토스트를 띄워서 제대로 동작하고 있는지를 확인하도록 되어있습니다. 그리고 부가적으로, 한번 클릭했을 때, 길게 클릭했을 때 등의 기능도 포함되어 있습니다.

 

 Android & Amir 홈페이지에서 가져왔으며, 한 두가지 오류를 찾아 수정한 것입니다.

       

[ 제스처 결과 화면 ]

(오른쪽 방향으로 Swipe하였을 때)

       

  STEP 1 Java Source Code 

       

  예제는 간단한 한 화면만 있습니다. 상위에는 현재 마우스 클릭(손가락 터치)이 어떠한 방식으로 되고 있는지 출력하는 텍스트 박스가 있으며, 하위에는 마우스 제스처(손가락 터치 모션) 를 테스트해볼 수 있는 화면이 구성되어 있습니다. 레이아웃은 Xml을 통해 구현하지 않았고, 바로 코드를 이용해서 추가하도록 하였습니다. 눈여겨 봐야할 것은, 제스처를 인식하는 방법입니다.

   

   

  예제 코드 Touch UI Gesture (제스처 기능) 
import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.GestureDetector.OnGestureListener;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class GestureActivity extends Activity implements OnGestureListener {

	private LinearLayout main;
	private TextView viewA;

	private static final int SWIPE_MIN_DISTANCE = 120;
	private static final int SWIPE_MAX_OFF_PATH = 250;
	private static final int SWIPE_THRESHOLD_VELOCITY = 200;

	private GestureDetector gestureScanner;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		gestureScanner = new GestureDetector(this);
		main = new LinearLayout(this);
		main.setBackgroundColor(Color.GRAY);
		main.setLayoutParams(new LinearLayout.LayoutParams(320, 480));

		viewA = new TextView(this);
		viewA.setBackgroundColor(Color.WHITE);
		viewA.setTextColor(Color.BLACK);
		viewA.setTextSize(30);
		viewA.setGravity(Gravity.CENTER);
		viewA.setLayoutParams(new LinearLayout.LayoutParams(320, 80));
		main.addView(viewA);
		setContentView(main);
	}

	@Override
	public boolean onTouchEvent(MotionEvent me) {
		return gestureScanner.onTouchEvent(me);
	}

	public boolean onDown(MotionEvent e) {
		viewA.setText("-" + "DOWN" + "-");
		return true;
	}

	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
		try {
			if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
				return false;

			// right to left swipe
			if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
				Toast.makeText(getApplicationContext(), "Left Swipe", Toast.LENGTH_SHORT).show();
			}
			// left to right swipe
			else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
				Toast.makeText(getApplicationContext(), "Right Swipe", Toast.LENGTH_SHORT).show();
			}
			// down to up swipe
			else if (e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
				Toast.makeText(getApplicationContext(), "Swipe up", Toast.LENGTH_SHORT).show();
			}
			// up to down swipe
			else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
				Toast.makeText(getApplicationContext(), "Swipe down", Toast.LENGTH_SHORT).show();
			}
		} catch (Exception e) {

		}
		return true;
	}

	public void onLongPress(MotionEvent e) {
		Toast mToast = Toast.makeText(getApplicationContext(), "Long Press", Toast.LENGTH_SHORT);
		mToast.show();
	}

	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
		viewA.setText("-" + "SCROLL" + "-");
		return true;
	}

	public void onShowPress(MotionEvent e) {
		viewA.setText("-" + "SHOW PRESS" + "-");
	}

	public boolean onSingleTapUp(MotionEvent e) {
		Toast mToast = Toast.makeText(getApplicationContext(), "Single Tap", Toast.LENGTH_SHORT);
		mToast.show();
		return true;
	}

}

 

  Swipe(휘두르다, 강타)라는 용어를 썼는데, 한국어로 어떻게 변경을 해야할지 몰라 그대로 두었습니다. 손가락을 이용하여 여러 개로 구성되어 있는 메인 화면을 넘기는 방법을 생각하시면 가장 간단할 것 같습니다.

 

 코드의 핵심은 GestureDetector 에 있습니다. 엑티비티에서 OnGestureListener 를 상속받으면, GestureDetector를 사용할 수 있습니다. GestureDetector를 엑티비티에 등록시켜면서 생성합니다. 그런 다음 onTouchEvent를 통해 GestureDetector 객체의 onTouchEvent로 등록시켜줍니다. 이렇게 하면 기본 세팅은 마무리가 됩니다. 그런 다음에 자신이 하고 싶은 기능을 상속받아 사용할 수 있습니다. 위에서 보시면, onDown, onLongPress, onScroll, onShowPress, onSingleTapUp, onFling라는 것을 상속받아 구현되어 있는 것을 확인할 수 있습니다. 함수명에서 보시듯 어떠한 터치 동작을 하고 있는 중인지에 따라 각각이 호출되고 있습니다. 다른 것은 직관적으로 아실 수 있을 거라 생각이 듭니다만, 복잡한 듯 보이는 onFling은 조금 생각을 해보아야 합니다. 아래의 세가지 변수를 먼저 알아봅시다. 이것만 이해되신다면 아주 간단한 구조로 되어 있다는 것을 알게 되실 겁니다.

 

private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;

 

  터치 이벤트를 통해서 onFling이벤트로 들어옵니다. onFling으로 들어오는 인자를 보시면, 모션 이벤트가 2개가 들어오고, float 형의 velocity(속도)가 들어옵니다. 벌써 이미 느낌이 오실 겁니다. 첫 번째 인자는 터치를 시작한 그 순간의 이벤트이며, 두 번째 인자는 터치를 땐 그 시점의 이벤트 입니다. 세 번째 인자는 X축으로의 속도이며, 네 번째 인자는 Y 축으로의 인자입니다. 즉 이 네 가지의 이벤트를 가지고, 어떤 방향으로의 Swipe동작인지 구분할 수가 있습니다.

 

  네 가지 방향으로의 Swipe동작을 어떻게 구분하는가 하는 것은 거리 측정과, 속도를 통해서 이루어집니다. SWIPE_MIN_DISTANSE 인자는 Swipe를 인식하는 가장 작은 거리를 뜻합니다. 설정된 거리 이상이 되어야 Swipe동작으로 인식한다는 것이죠. SWIPE_MAX_OFF_PATH는 인식하는 최대 거리라고 보시면 됩니다. 그 이상의 거리를 Scrolling하면 제외하겠다는 의미를 가지고 있습니다. 마지막 SWIPE_THRESHOLD_VELOCITY는 속도입니다. 각 방향으로 임의의 속도 이상으로 터치동작이 이루어져야 Swipe로 인식하겠다는 것이지요.

 

 이제 위의 코드를 살펴보시면, 어떤 구조로 되어 있는지 한 눈에 들어오실 겁니다. 첫 좌표와 끝 좌표를 비교하여 거리를 비교하여 적정 수준의 거리와 속도를 가지면 Swipe로 인식하며 토스트를 띄웁니다. 이러한 방식을 이용하여 화면 전환 등의 자신이 하고 싶은 동작을 넣으시면 됩니다.

 

    

  STEP 2 Xml Code

         

   Xml 코드를 제외하고 코드로 바로 레이아웃을 설정하였습니다. onCreate에 있는 추가동작을 xml로 구현하셔도 무방합니다.

             

  STEP 3 AndroidManifest.xml Code

 

 안드로이드 자체에서 제공되는 서비스기 때문에 메니페스트는 손댈 필요가 없습니다.

 

 

 

<마무리>  터치화면, 제스처 기능을 이용한 터치 인식

   

  일반적으로 다이나믹한 UI를 사용자에게 제공하기 위해서는 터치 기능을 이용하여 다양한 서비스를 제공해야합니다. 제스처(Gesture)를 인식하기 위해서는 이벤트 핸들러를 통해서 현재 어떠한 모션을 취하고 있는지를 확인하는 절차가 필요합니다. onGestureListener를 Activity로 상속받고, GestureDetector를 생성하여, 터치이벤트로 등록시키며, 자신이 원하는 동작을 가져올 수 있도록, 터치 이벤트의 동작 지점, 거리, 터치 속도 등을 고려하여 설계를 하시면 됩니다. 간단한 Swipe기능에 대한 예제를 알아보고, 제스처가 어떠한 방식으로 등록되고 사용되는지에 대해서 알아보았습니다. 이것 외에도 안드로이드에서 제공하는 기본 제스처, GestureLibrary 등이 있는 것으로 생각됩니다만, 아직는 그것을 쓸 필요가 없었던 관계로 추후에 기회가 된다면 다시 포스팅 하겠습니다.    

     

     

[ 참고자료 ]

       

   


Posted by 
맥박맥박의 개발 일지

 

  • 2013년 7월 26일 업데이트하였습니다.

맥박 안드로이드 , , , , , ,

  1. Blog Icon
    라견

    좋은 정보 감사합니다. 이해가 잘 되네요.

  2. 이해가 되셨다니 저도 기쁩니다.
    즐거운 하루 되세요~^^

  3. Blog Icon
    ㅎㅇ

    감사합니다. 저도 이해가 잘 되었습니다 ㅎ
    다만, 몇가지 궁금한점이 있는데요
    onShowPress 메소드의 의미를 모르겠네요
    답변좀^^;

  4. onShowPress는 onLongPress보다 조금 더 짧게 눌렸을 때 나타나는 이벤트입니다. ^^
    테스트는 안해봤지만, 레퍼런스를 참고하였습니다.

    좋은 하루 되세요~!

  5. Blog Icon
    간지폭풍

    좋은 포스팅에 감사드립니다. 사막에서 오아시스를 찾은 기분입니다.

  6. 감사합니다. ~!!!
    간지폭풍님 간지가 철철 넘치시네요~

    힘을 얻습니다. ^^ 즐거운 하루 되세요~!!!

  7. Blog Icon
    웅바라지

    우선 감사합니다 근데 제스쳐가 스크롤 뷰 위에서는 안되더라고요 스크롤뷰를 확장하면 된다고 하는데 어떻게 하는지 ㅠ 아시면은 좀 알려주세요 ㅠ

  8. 스크롤 뷰는 아무래도, 스크롤 때문에 이벤트가 먹힐 것 같군요.
    저도 해보지 않아서 대답을 못해드리겠네요.ㅠㅠ

    구글링한 결과 해당 문제에 대해 답이 있어 링크 걸어드립니다. ^^;

    http://chan180.tistory.com/entry/Android-ScrollView-%EC%97%90%EC%84%9C-Gesture-%EA%B0%80%EB%8A%A5%ED%95%98%EA%B2%8C-%ED%95%98%EA%B8%B02

    참고하세요~!!!

  9. 아... 플리킹 동작을 판단하는 API가 없는 줄 알고 onTouchEvent에서 직접 만들었는데, Gesture가 있었네요.. 좋은 글 감사합니다~

  10. ^^ 감사합니다.
    좋은 하루 되세요~!!!

  11. 좋은 정보 감사합니다 긍대 티스토리에 소스코드 어떻게 저렇게 이뿌게 쓰나요?

  12. 도움이 되셨다니 저도 기분이 좋습니다. ^^
    저는 MS Office를 이용하여 포스팅을 한답니다.
    자바 이클립스에서 소스를 복사하여 MS Office에 붙이면 소스코드 색깔 그대로 나타납니다.

    소스코드 뷰어도 좋겠지만, 위에처럼하는게 더 와닿아서 저렇게 해보았답니다. ^^

  13. Blog Icon
    레오폴드

    정말 명료하게 설명해 주시네요.
    프로젝트에 제스쳐 기능이 포함되어 있었는데.
    이 내용 한방으로 감을 잡았습니다.
    고맙습니다. :)

  14. ^^ 감사합니다.
    프로젝트 잘 진행되시길 기원합니다. ^^

    즐겁고 재미난 하루 되세요~!

  15. Blog Icon
    kay

    와우 ! 쉽게 정리 참 잘해주셨네요 ^^ 유용하게 사용하겠습니다 감사합니다 !!

  16. Blog Icon
    spai006

    왜 코드를 퍼가서 사용을 하셨는데 commentary 를 안하셨나요?

  17. 안녕하세요. 코드를 가져왔다고 명시되어 있습니다.
    아래에 링크까지 걸어두었구요. ^^;
    코드를 가져왔다고 명시되어 있는 곳에 바로 링크를 추가하겠습니다.
    조언 감사합니다~!!! 좋은 하루 되세요~!!!

  18. Blog Icon
    예제소스

    위에 예제를 이용하여..

    완성된 예제소스 없나요??ㅠㅠ

    test000@hanmail.net 로 보내주시면 ㄳ하겠습니다.ㅠㅠ

  19. 소스 코드 잘 썻습니다.

    감사합니다.

  20. Blog Icon
    김바름

    혹시 완성된 예제소스 없나요? ㅠㅜ

    안드로이드 SDK를 다뤄본 적이 없어 어떻게 해야할지 막막하네요...

    만약 소스가 있다면

    kimbareum@gmail.com

    으로 꼭 부탁드릴게요

  21. Blog Icon
    andyWhole

    감사합니다 정리 잘 하고 갑니다.

  22. Blog Icon
    김톱

    책이나 검색으로 해결 못한걸 한번에 해결했내요
    감사합니다 ^^

  23. Blog Icon
    홍홍

    좋은 글 감사합니다~
    죄송하지만 혹시 완성된 소스를 받을 수 있을까요?
    프로젝트에 필요한 부분인데 초보라 공부하는데 사용하고 싶어서요ㅠㅠ 부탁드립니다
    dmswl2546@naver.com

  24. 수고해서 작성하신 귀한 글 잘 보았습니다.
    감사합니다.

  25. Blog Icon
    박현제

    좋은 글 잘 읽었습니다.

    궁금한게 있는데 안드로이드 폰 런처와 같이 제스처를 통해서 화면전환 자연스럽게 하는 법 없나요?