본문 바로가기

안드로이드

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

 

   

<목표> 

 

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

 

 

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

 

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

 

 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일 업데이트하였습니다.