본문 바로가기

프로그래밍/안드로이드

[안드로이드] 터치 입력, 위젯의 이벤트처리

1. 터치 입력


boolean onTouchEvent (MotionEvent event)       // 콜백메서드

boolean onTouch (View v, MotionEvent event)   // 리스너 인터페이스


전에도 말했듯이, 이벤트를 처리하는 방법 중에 

콜백 메서드를 정의 하는 방법도 있고 리스너 인터페이스를 구현하는 방법도 있다고 했다. 

 [ 콜백 메서드 , 리스너 인터페이스 복습 ]  

    즉, 이 둘의 차이는 뷰(View)를 가졌는가 아닌가로 나누는데 (인수를 보면 알 수 있다)


콜백 메서드는 이벤트 정보만을 받는데 비해  ( 인수로 이벤트를를 받는다. )

콜백은 특정 뷰 클래스의 소속이므로 이벤트 발생 대상이 정해져 있지만

리스너는 여러 대상에 대해 등록가능하므로 이벤트 대상인 v를 전달 받는다.

리스너는 여러 위젯에 의해 공유될 수 있으므로 대상 뷰가 누구인지 전달받아야 한다.

리스너는 특정 이벤트를 처리하는 "인터페이스"이다. 이벤트 핸들러라고도 한다.


흔히 사용되는 View 클래스 내부 인터페이스로 onTouchListener가 선언된다.

이 인터페이스는 onTouch라는 추상메서드를 포함한다. 이러한 메서드를 이벤트 핸들러라고 한다.


인수중에 MotionEvent 객체는 두 경우 모두 동일하다. MotionEvent.getAction 메서드를 사용해서

사용자가 화면에 대고 무슨 짓을 했는지에 대한 정보를 전달한다.


동작 

설명 

ACTION_DOWN 

화면을 눌렀다. 

ACTION_MOVE 

누른 채로 움직였다. 

ACTION_UP 

화면에서 손가락을 뗐다.

public class Exercise extends Activity {

private MyView vw;


// 정점 하나에 대한 정보를 가지는 클래스
public class Vertex{
Vertex(float ax, float ay, boolean ad){
x = ax;
y = ay;
draw = ad;
}
float x;
float y;
boolean draw; // true면 계속 연결해서 그리고, false면 떨어진 다른 곡선이 시작.
}

ArrayList<Vertex> arVertex;

public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
vw = new MyView(this);
setContentView(vw);

arVertex = new ArrayList<Vertex>(); // Vertex를 저장하는 컬렉션 arVertex
}

protected class MyView extends View{
// 뷰 클래스를 상속받음


Paint mPaint;

public MyView(Context context){ // 생성자
super(context);

// Paint 객체 미리 초기화
mPaint = new Paint();
mPaint.setColor(Color.BLACK); // 선 색깔 검정색
mPaint.setStrokeWidth(3); // 선 굵기를 3으로 설정
mPaint.setAntiAlias(true); // 안티 알리아스 적용(선을 매끄럽게)
}

public void onDraw(Canvas canvas){ // 그림출력 위해 onDraw() 메서드 overriding
canvas.drawColor(Color.LTGRAY); // 일단 배경화면은 회색바탕으로

// 정점을 순회하면서 선분으로 잇는다.
for (int i=0; i<arVertex.size(); i++){
if(arVertex.get(i).draw) {
canvas.drawLine(arVertex.get(i - 1).x, arVertex.get(i - 1).y,
arVertex.get(i).x, arVertex.get(i).y, mPaint);
}
}
}

// 터치 이동 시마다 정점을 추가한다.
public boolean onTouchEvent(MotionEvent event){
if(event.getAction() == MotionEvent.ACTION_DOWN){
arVertex.add(new Vertex(event.getX(), event.getY(), false));
return true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE){
arVertex.add(new Vertex(event.getX(), event.getY(), true));
invalidate();
return true;
}
return false;
}
}
}

(※ vertex는 정점(꼭짓점)을 뜻하는 영어단어이다.)


1. 그리기를 수행하는 View, onDraw 메서드를 재정희하기 위해서 View를 상속받고 있다.

onDraw 메서드에서 캔버스에 출력한다.


2. 캔버스는 점, 선, 원, 사각형 등 기본 도형을 그리는 메서드를 제공한다.

예제에서는 선을 그리는 drawLine을 사용하고 있는 것을 볼 수 있다.


3. onTouchEvent 콜백 메서드에서 사용자가 뷰를 터치하거나 이동할 때 발생하는 터치 이벤트를 처리하여 곡선을 그린다.


4. onTouchEvent에서 draw 멤버가 false이면 새로운 곡선이 시작되고 draw 멤버가 true이면이전 좌표에서 현재 좌표까지 선이 그어진다.


새로 추가된 선을 화면에 출력하려면 invalidate를 호출하여 무효화한다. 

이 메서드에 의해 간접적으로 onDraw가 호출된다.



2. 위젯의 이벤트처리


우리가 가장 흔히 사용하는 "버튼(Button)"에 대한 이벤트 처리를 해보자.

버튼의 경우 "클릭"이 가장 흔하게 발생하는 이벤트이다.


버튼은 텍스트뷰의 차일드 위젯(child widget)이며, Button 클래스를 사용하는 것이 보통이므로

상속받지 않고도 이벤트를 처리할 수 있어야 한다. 


따라서 콜백 메서드는 정의되어 있지 않고 반드시 리스너로 이벤트를 받아야 한다.

그렇기 때문에 View.OnClickListener 인터페이스를 구현하고 다음 메서드에 클릭 처리코드를 작성한다.


View.OnClickListene 인터페이스를 구현하고 다음 메서드에 클릭 처리 코드를 작성한다.


void onClick (View v)


[리스너로 처리하는 방법] : 여러 위젯에 의해 공유될 수 있으므로 "대상 뷰"가 누구인지 전달받아야 한다. (아래 예제에서는 텍스트뷰가 대상 뷰이다.)

<레이아웃은 하단에 .xml 로 첨부>

위젯 두 개(오렌지와 사과)에 의해 리스너가 "공유"되고 있으므로, 어떤 뷰가 전달되는 것인지 알아야 한다. 대신 TextView는 두 번 조사한다.


여기서 코드가 눈에 보이지 않는다면 다시 공부해야 할 필요가 있다. (사실 내가 그렇다.)

우리는 리스너 인터페이스 구현을 위해서 3가지 과정을 거친다고 이야기했다.


1. 리스너 구현 클래스를 선언 public void on Touch (view v)

2. 리스너 객체를 선언(여기서는 임시 리스너 객체) setOnclickListener(new Button.OnClickListener()

3. 리스너를 등록 Button btn(Apple or Orange) = (Button)findsViewById(R.id.oragne or apple)


여기서는 "익명 내부 클래스"의 "임시 객체" 사용을 통해서 버튼에 클릭리스너를 "달아주었다".


하지만 버튼이 Grape, Banana, Peach 등 계속 추가되야 한다면 코드를 반복해서 작성해야 한다.

따라서 한 객체를 두 번 사용하려면 익명 클래스의 임시 객체를 생성하는 방법은 불편하다.


좀 더 간편한 방법으로 코드를 작성해보자


[콜백메서드로 처리하는 방법] : 특정 뷰의 소속, 이벤트 발생 대상이 정해져 있으며 인스턴스가 여러 개이더라도 this 키워드로 이벤트 발생 객체를 조사할 수 있다.


다른 과일이 추가되더라도 switch문의 case만 늘려주면 되기 때문에 간편하다. 핸들러가 하나로 통합되었다는 면에서 바람직하지만 최상위의 액티비티를 리스너로 사용한다는 점은 다소 부담이 된다. 아래에는 가장 정석적인 방법이다.



mClickListener 멤버는 View.OnClickListener 인터페이스를 구현하는 익명 클래스 타입으로 선언되었으며

onClick 메서드는 앞 예제와 동일하다.


SDK 1.6 부터 클릭 이벤트만 예외적으로 XML문서에서 onClick 속성으로 지정할 수 있다.


xml에 있는 apple과 orange에

adroid:onClick="mOnClick"만 추가하면 이렇게 간단한 형태로 코드 작성이 가능하다.

따라서 위 예제에서는 mOnClick에 대한 메서드만 정의했다.


3. 롱 클릭(Long Click)


void setOnLongClickListener (View.OnLongClickListener 1)

boolean onLongClick (View v)



mOnClick 메서드는 버튼을 한 번 누르면 +1, -1씩 증감되고

mLongClickListener 메서드는 버튼을 길게 누르면 특정값(0, 100)으로 변한다.

대신 감소버튼은 0으로 리셋 후, true를 리턴하므로 롱 클릭처리는 여기서 끝난다.

반면 증가버튼은 false를 리턴하므로 아직 이벤트 처리가 끝나지 않은 것으로 간주, 클릭이벤트가

추가로 발생하여 카운트가 101이 된다.



<위젯의 이벤트처리.xml>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
>
<TextView
android:id="@+id/fruit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffff00"
android:textSize="40sp"
android:text="과일"/>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">

<Button
android:id="@+id/apple"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Apple"/>

<Button
android:id="@+id/orange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Oragne"/>
</LinearLayout>

</LinearLayout>




<위젯의 이벤트처리.java>

public class Exercise extends Activity {
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise);
Button btnApple = (Button)findViewById(R.id.apple);
btnApple.setOnClickListener(new Button.OnClickListener(){
public void onClick (View v) {
TextView textFruit = (TextView) findViewById(R.id.fruit);
textFruit.setText("Apple");
}
});

Button btnOrange = (Button)findViewById(R.id.orange);
btnOrange.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v) {
TextView textFruit = (TextView) findViewById(R.id.fruit);
textFruit.setText("Oragne");
}
});

}
}



<정석적인 위젯 리스너 처리방법.java>

public class Exercise extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise);

findViewById(R.id.apple).setOnClickListener(mClickListener);
findViewById(R.id.orange).setOnClickListener(mClickListener);
}

Button.OnClickListener mClickListener = new View.OnClickListener(){
public void onClick(View v){
TextView textFruit=(TextView)findViewById(R.id.fruit);
switch (v.getId()) {
case R.id.apple:
textFruit.setText("Apple");
break;
case R.id.orange:
textFruit.setText("Orange");
break;
}
}
};
}



<롱클릭.xml>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
>
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="40sp"
android:text="0"/>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">

<Button
android:id="@+id/decrease"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="감소"/>

<Button
android:id="@+id/increase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="증가"/>
</LinearLayout>

</LinearLayout>


<롱클릭.java>

package lkcompany.exercise;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class Exercise extends Activity {
int mCount = 0;
TextView mTextCount;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exercise);

mTextCount = (TextView)findViewById(R.id.count);
findViewById(R.id.decrease).setOnLongClickListener(mLongClickListener);
findViewById(R.id.increase).setOnLongClickListener(mLongClickListener);
}

public void mOnClick(View v){
switch(v.getId()){
case R.id.decrease:
mCount--;
mTextCount.setText("" + mCount);
break;
case R.id.increase:
mCount++;
mTextCount.setText("" + mCount);
break;
}
}

View.OnLongClickListener mLongClickListener = new View.OnLongClickListener(){
public boolean onLongClick(View v){
switch (v.getId()) {
case R.id.decrease:
mCount = 0;
mTextCount.setText("" + mCount);
return true;
case R.id.increase:
mCount = 100;
mTextCount.setText("" + mCount);
break;
}
return false;
}
};
}