터치 이벤트 종류 - teochi ibenteu jonglyu

터치 이벤트 자체를 처리하는 것은 게임이나 그림판과 같은 앱에서는 많이 쓰이지만 서버와 통신을 주고받는 앱에서는 view 에 정의되어 있는 이벤트를 더 자주 사용한다.

터치 이벤트

앱의 화면에서 발생하는 사용자 이벤트는 터치이다. 앱은 사용자가 터치를 했는지, 스와이프를 했는지 인식하고 그에 알맞게 동작하도록 구현된다.

유저의 터치 이벤트를 처리하고 싶다면 액티비티에 이벤트 콜백 함수인 onTouchEvent() 를 선언하면 된다.

class MainActivity : AppCompatActivity() { override fun onTouchEvent(event: MotionEvent?): Boolean { return super.onTouchEvent(event) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }

이렇게 onTouchEvent() 를 재정의해서 선언해 두면 사용자가 액티비티의 화면을 터치하는 순간 onTouchEvent() 함수가 자동으로 호출된다. 이 함수에 전달되는 매개변수는 MotionEvnet 이며 이 객체에 터치의 종류와 발생 지점이 담긴다.

더 자세한 내용은 안드로이드 일반동작 인식에 대한 문서 를 참고하자.

터치이벤트 종류

터치이벤트에는 3가지가 있다.

  • ACTION_DOWN: 화면을 손가락으로 누르는 순간의 이벤트
  • ACTION_UP: 화면에서 손가락을 떼는 순간의 이벤트
  • ACTION_MOVE: 화면을 손가락으로 누른 채로 이동하는 순간의 이벤트

화면을 눌렀다가 때면 onTouchEvent() 함수는 ACTION_DOWN, ACTION_UP 이벤트 두개가 발생하므로 두번 호출된다.

매개변수로 넘어오는 event 의 action 프로퍼티가 이벤트 정보를 담고있으므로, 이를 참조해서 각 이벤트마다 반응을 다르게 만들 수 있다.

override fun onTouchEvent(event: MotionEvent?): Boolean { when(event?.action) { MotionEvent.ACTION_DOWN -> { Log.d("info","Touch down event") } MotionEvent.ACTION_UP -> { Log.d("info","Touch up event") } } return super.onTouchEvent(event) }

터치 이벤트 발생 좌표

터치 이벤트 발생 좌표 역시 확인할 수 있다.

  • x : 이벤트가 발생한 view에서의 x 좌표
  • y : 이벤트가 발생한 view에서의 y 좌표
  • rawX : 화면의 x 좌표
  • rawY : 화면의 y 좌표
override fun onTouchEvent(event: MotionEvent?): Boolean { when(event?.action) { MotionEvent.ACTION_DOWN -> { Log.d("Touch down event at ${event.x} , ${event.y} ... raw (${event.rawX} , ${event.rawY}") } return super.onTouchEvent(event) }

onTouchEvent 함수를 액티비티 수준에서 재정의했다면 rawx 와 x 간에 차이가 없겠지만, 특정 view 에서 정의했다면 둘 사이에 차이가 발생한다.

1. 안드로이드의 이벤트 처리 방식

GUI(Graphic User Interface)를 제공하는 프로그램은 화면과 사용자간의 상호작용에 대해 처리하는 방식을 내부적으로 정의하고 있다. 이 상호작용을 이벤트라고 부르는데, 이벤트에는 터치 이벤트(Touch Event), 클릭 이벤트(Click Event), 키 입력(Key Event) 등이 있다.

크게 안드로이드에서 이벤트를 처리하는 방식은 2가지로 나뉜다.

1) 델리게이션 모델(Delegation Model)

  • 레이아웃에 정의된 뷰 객체에 발생하는 이벤트를 처리하는 모델. 위임모델 이라고도 한다.

기본적인 구조는 이벤트 소스(뷰 객체)와 이벤트 핸들러(이벤트 처리방식이 정의된 객체)를 리스너로 연결하는 구조이다. 이 구조는 이벤트를 뷰 객체에 전달한 후 이후의 처리과정을 뷰 객체에 위임한다. 이렇게 하면 각각의 뷰마다 이벤트 처리 루틴이 할당되어 이벤트 루프에서 받아 처리할 때보다 과정이 간소화되고 코드가 더욱 객체지향적이 된다.

2) 하이어라키 모델(Hierachy Model)

  • 액티비티에 발생하는 이벤트를 처리하는 모델

델리게이션 모델처럼 리스너를 사용하는 구조가 아니다. 특정 액티비티 내에서 터치 이벤트 등이 발생하면 자동으로 호출되는 콜백 메소드만 재정의 해주면 된다.

2. 이벤트 종류

속성설명
터치 이벤트 화면을 손가락으로 터치
키 이벤트 키패트, 하드웨어 버튼을 누름
제스처 이벤트 터치 이벤트 도중 일정 패턴의 움직임
포커스 뷰 객체가 포커스를 받거나 잃음
화면 방향 변경 화변 방향(가로,세로)가 변경

안드로이드에서 자주 사용하는 이벤트는 터치 이벤트, 키 이벤트이며 이벤트가 발생되면 다음의 메소드가 자동으로 호출된다.

boolean onTouchEvent(MotionEvent event) boolean onKey(View v, int keyCode, KeyEvent event) boolean onKeyUp(int keyCode, KeyEvent event) boolean onKeyDown(int keyCode, KeyEvent event)

특정 뷰 객체에서 위 메소드들을 사용하려면 뷰를 상속하는 클래스를 따로 만들어 메소드를 재정의하는 방법, 혹은 리스너 객체를 설정하는 방식이 있다. 따라서 특정 뷰 객체에서 위 메소드들을 사용하려면 델리게이션 모델로 리스너를 달아주는 것이 간편한 사용법이다.

View.OnTouchListener : boolean onTouch(View v, MotionEvent event) View.OnKeyListener : boolean onKey(View v, int keyCode, KeyEvent event) View.OnClickListener : void OnClick(View v) View.OnFocusChangeListener : void onFocusChange(View v, boolean hasFocus)

실제로 예제를 만들어보자.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="//schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <View android:id="@+id/view1" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ffff0000"/> <View android:id="@+id/view2" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ff00ff00"/> <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="match_parent" /> </ScrollView> </LinearLayout>

뷰 2개와 텍스트뷰 하나를 만들었다. 각각의 뷰는 리스너 객체를 달아 터치 이벤트를 처리하기 위함이고 텍스트뷰는 우리가 이벤트 처리를 눈으로 확인하기 위해 만든 것이다.

package io.swnomad.sampleevent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; public class MainActivity extends AppCompatActivity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); View view1 = findViewById(R.id.view1); View view2 = findViewById(R.id.view2); //view1 객체에 터치 이벤트 발생 시 처리하는 코드 view1.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); // 액션 정보 획득 float curX = event.getX(); // X좌표 float curY = event.getY(); // Y좌표 if(action==MotionEvent.ACTION_DOWN){// 화면 누르기 textView.append("[view1] 손가락 눌림: "+curX+", "+curY+"\n"); }else if(action==MotionEvent.ACTION_MOVE){// 움직임 textView.append("[view1] 손가락 움직임: "+curX+", "+curY+"\n"); }else if(action==MotionEvent.ACTION_UP){// 화면에서 손 떼기 textView.append("[view1] 손가락 떼짐: "+curX+", "+curY+"\n"); } return true; } }); //view2 객체에 터치 이벤트 발생 시 처리하는 코드 view2.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); // 액션 정보 획득 float curX = event.getX(); // X좌표 float curY = event.getY(); // Y좌표 if(action==MotionEvent.ACTION_DOWN){// 화면 누르기 textView.append("[view2] 손가락 눌림: "+curX+", "+curY+"\n"); }else if(action==MotionEvent.ACTION_MOVE){// 움직임 textView.append("[view2] 손가락 움직임: "+curX+", "+curY+"\n"); }else if(action==MotionEvent.ACTION_UP){// 화면에서 손 떼기 textView.append("[view2] 손가락 떼짐: "+curX+", "+curY+"\n"); } return true; } }); } }

3. 제스처(Gesture) 이벤트

터치 이벤트 중에 발생하는 일정한 패턴을 가지는 움직임을 제스처(Gesture)라고 한다. 대표적으로 화면 스크롤 등의 움직임이 있다. 제스처 이벤트는 터치 이벤트를 받은 후 추가적인 확인을 거쳐 만들어지며, 일반적인 터치 이벤트보다 간단하게 처리할 수 있다.

다음은 제스처 이벤트를 처리할 수 있는 메소드들이다.

메소드이벤트 유형
onDown() 화면이 눌림
onShowPress() 화면이 눌렸다 떼어짐
onSingleTapUp() 화면이 한 손가락으로 눌렸다 떼어짐
onSingleTapConfirmed() 화면이 한 손가락으로 눌림
onDoubleTap() 화면이 두 손가락으로 눌림
onScroll() 화면이 눌린 채 일정 속도와 방향으로 움직임
onFling() 화면이 눌린 채 가속도를 붙여 손가락을 움직였다 뗌
onLongPress() 화면이 오랫동안 눌림

제스처 이벤트를 처리하려면 GestureDetector 객체를 만들고 여기에 터치 이벤트를 전달해주면 된다. 그러면 제스처 디텍터가 제스처가 일어나면 이를 감지하고 적당한 메소드를 호출하여 이벤트를 처리해준다.

예제를 보면서 이해해보자.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="//schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <View android:id="@+id/view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="3" android:background="#ff0000ff"/> <ScrollView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="match_parent" /> </ScrollView> </LinearLayout>package io.swnomad.sampleevent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { View view; TextView textView; GestureDetector detector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); //GestureDetector 객체 생성하고 메소드 재정의 detector = new GestureDetector(this, new GestureDetector.OnGestureListener() { @Override public boolean onDown(MotionEvent e) { textView.append("\nonDown() 호출됨"); return true; } @Override public void onShowPress(MotionEvent e) { textView.append("\nonShowPress() 호출됨"); } @Override public boolean onSingleTapUp(MotionEvent e) { textView.append("\nonSingleTapUp() 호출됨"); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { textView.append("\nonScroll 호출됨 : (" + distanceX+", "+distanceY+")"); return true; } @Override public void onLongPress(MotionEvent e) { textView.append("\nonLongPress() 호출됨"); } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { textView.append("\nonFling() 호출됨 : (" + velocityX + ", " + velocityY+")"); return true; } }); view = findViewById(R.id.view); view.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { detector.onTouchEvent(event); //GestureDetector 객체의 onTouchEvent() 메소드 호출하면서 MotionEvent 객체 전달 return true; } }); } }

제스처 이벤트 중에서 대표적인 것이 스크롤(Scroll)과 플링(Fling)이다. 스크롤은 일정한 속도의 스크롤이고 플링은 빠른 속도의 스크롤을 의미한다. 그리고 각각의 제스쳐가 발생했을 때 호출되는 메소드에는 터치의 위치와 속도가 각각 전달된다.

4. 키(Key) 이벤트

키 입력 이벤트는 아래의 메소드들을 재정의하여 처리할 수 있다.

boolean onKey(View v, int keyCode, KeyEvent event) // onKeyListener 리스너를 설정할 때 사용 boolean onKeyDown(int keyCode, KeyEvent event) //액티비티 내에서 메소드를 재정의하여 사용

메소드로 전달되는 파라미터 중에서 keyCode 는 키를 구별할 때 사용한다. 그리고 KeyEvent는 키 입력에 대한 정보를 가지고 있는 객체이다.

아래 링크를 따라가면 모든 keyCode에 대한 내용이 나와있다.

keyCode

예제로 시스템의 '뒤로가기' 버튼이 눌렸을 때 이벤트를 처리하는 앱을 만들어보자.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="//schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="뒤로가기 버튼을 눌러보세요" android:textSize="24dp" android:textStyle="bold" android:layout_marginTop="350dp" android:layout_gravity="center_horizontal"/> </LinearLayout>package io.swnomad.sampleevent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.KeyEvent; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 시스템 [BACK] 버튼이 눌린 경우 토스트 메시지 보여주기 if(keyCode==KeyEvent.KEYCODE_BACK){ Toast.makeText(this, "뒤로가기 버튼이 눌렸습니다", Toast.LENGTH_LONG).show(); } return false; } }

5. 포커스(Focus) 이벤트

포커스란 화면상의 어떠한 뷰 객체가 키 입력 이벤트를 받는지를 나타내는 것이다. 예를 들면 붙여넣기를 한다던지, 키보드로 텍스트 입력을 한다던지 할 때 현재 포커스를 받고있는 뷰에서 그 입력이 일어난다는 것이다. 입력상자가 포커스를 받고 있으면 커서가 깜빡이는 것 또한 예시가 된다. 뷰 객체는 포커스를 받거나 잃거나 하는 변화가 일어나면 onFocusChange() 메소드가 호출된다. 예제를 통해 알아보자.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="//schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="텍스트를 입력하세요" android:textSize="20dp" android:layout_margin="20dp"/> <EditText android:id="@+id/editText2" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="텍스트를 입력하세요" android:textSize="20dp" android:layout_margin="20dp"/> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:textStyle="bold" android:layout_margin="20dp"/> </LinearLayout>

입력상자를 2개 만들고 텍스트뷰를 하나 만든다. 텍스트뷰에는 두 입력상자 중 어떠한 입력상자가 포커스를 받고있는지 나타내기 위한 것이다.

package io.swnomad.samplefocus; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends AppCompatActivity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.textView); EditText editText1 = findViewById(R.id.editText1); EditText editText2 = findViewById(R.id.editText2); editText1.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(hasFocus){ textView.setText("현재 포커스 : 입력상자1"); } } }); editText2.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if(hasFocus){ textView.setText("현재 포커스 : 입력상자2"); } } }); } }


6. 단말 방향 전환 이벤트

안드로이드에서 휴대폰의 방향을 가로/세로 전환할 때는 단말 방향 전환 이벤트가 발생한다. 이 때 화면 레이아웃이 달리 보여야 하므로 메모리에서 액티비티를 없앤 후 새로 만든다.

단말의 방향이 세로일 때는 /res/layout 폴더의 xml 파일이, 단말의 방향이 가로일 때는 /res/layout-land 폴더의 xml 파일이 자동으로 사용된다. 따라서 각각의 폴더에 xml 레이아웃 파일을 만들어야한다. 만약 /res/layout-land 폴더가 존재하지 않으면 단말의 방향이 가로일때와 세로일 때 모두 /res/layout 폴더의 xml 파일이 사용된다.

먼저 예제 앱을 만들기 위해 각각의 폴더를 만들고 activity_main.xml 파일을 각각 만든다. /res/layout/activity_main.xml 파일의 TextView의 text는 "세로방향", /res/layout-land/activity_main.xml 파일의 TextView의 text는 "가로방향"으로 설정한다. 그리고 MainActivity.java 파일에는 다음 코드를 입력해준다.

package io.swnomad.sampleorientation; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toast.makeText(this, "onCreate() 호출됨", Toast.LENGTH_LONG).show(); } }

onCreate() 메소드의 토스트 메시지는 액티비티 객체가 새로 메모리에 만들어지는 것을 확인하기 위함이다.

앱을 실행하면 다음과 같은 화면을 확인할 수 있다.


그런데 단말의 방향이 달라져도 액티비티가 다를 필요가 없는 경우에는 굳이 액티비티를 다시 만들 필요가 없다. 이 경우에는 매니페스트 파일의 <activity\> 태그에 다음의 속성을 추가한다.

android:configChanges="orientation|screenSize|keyboardHidden"

이 속성이 설정되면 시스템은 단말의 방향전환이 일어날 때 액티비티를 다시 만들지 않고 애플리케이션쪽으로 단말 방향 전환 이벤트가 발생했다고 알려주기만 한다. 그리고 onConfigurationChanged() 메소드를 자동으로 호출한다. 이 메소드안에서 개발자가 직접 처리하는 코드를 작성하면 된다.

위에서 만든 앱에서 MainActivity.java 클래스의 코드만 다음과 같이 바꿔준다.

package io.swnomad.sampleorientation; import android.content.res.Configuration; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toast.makeText(this, "onCreate() 호출됨", Toast.LENGTH_LONG).show(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){ Toast.makeText(this, "가로방향", Toast.LENGTH_LONG).show(); }else if(newConfig.orientation==Configuration.ORIENTATION_PORTRAIT){ Toast.makeText(this, "세로방향", Toast.LENGTH_LONG).show(); } } }

이제는 액티비티가 다시 만들어지지 않기때문에 onCreate() 메소드가 새로 호출되지 않는다.


두 화면의 텍스트가 모두 "세로방향" 인걸로 미루어보아 액티비티를 새로 만들지 않은 것을 알 수 있다. 이 때에는 onConfigurationChanged() 메소드 안에서 레이아웃을 각각 따로 설정해주는 코드를 추가하면 된다.

setContentView(R.layout.activity_main);

이렇게 하면 단말의 방향이 세로일 때는 layout 폴더의 activity_main.xml 파일이, 가로일 때는 layout-land 폴더의 activity_main.xml 파일이 사용된다.

Tip. 화면의 방향을 고정시키고 싶다면?

매니페스트 파일의 <activity\> 태그에 screenOrientation 속성을 설정하면 된다.

android:screenOrientation="landscape" android:screenOrientation="portrait"

Toplist

최신 우편물

태그