[Do it! 안드로이드 앱 프로그래밍 #5] 이벤트 처리 이해하기
Android

[Do it! 안드로이드 앱 프로그래밍 #5] 이벤트 처리 이해하기

05 이벤트 처리 이해하기

5-1 이벤트 처리 방식

손가락으로 화면을 터치하면 터치 이벤트(Touch Event)가 발생한다. 실제 버튼이나 소프트 키패드를 누르면 키 이벤트(Key Event)가 발생한다. 터치 이벤트는 생각보다 복잡하기 때문에 쉽게 처리할 수 있도록 클릭 이벤트(Click Event)를 별도로 제공한다.

 

첫째 마당에서는 소스 코드에서 정의한 onButton1Clicked 메서드를 버튼의 onClick 속성에 추가했었다. 이번에는 XML이 아니라 소스 코드에서 클릭 이벤트를 처리하도록 setOnClickListener 메서드를 이용한다. 이와 같은 이벤트 처리 방식을 위임 모델(Delegation Model)이라고 부른다.

 

① 뷰를 상속하여 새로운 클래스를 정의할 때 메서드를 재정의 해 이벤트를 처리하는 방법

boolean onTouchEvent(MotionEvent event)

boolean onKeyDown(int KeyCode, KeyEvent event)

boolean onKeyUp(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)

 

속성 설명
터치 이벤트 화면을 손가락으로 누를 때 발생하는 이벤트
키 이벤트 키패드나 하드웨어 버튼을 누를 때 발생하는 이벤트
제스처 이벤트 터치 이벤트 중에서 스크롤과 같이 일정 패턴으로 구분되는 이벤트
포커스 뷰마다 순서대로 주어지는 포커스
화면 방향 변경 화면의 방향이 가로와 세로로 바뀜에 따라 발생하는 이벤트

 

 

5-2 터치 이벤트 처리하기

 

▼ 터치 이벤트 처리하기

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 view = findViewById(R.id.view);
        view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                int action = motionEvent.getAction();

                float curX = motionEvent.getX();
                float curY = motionEvent.getY();

                if(action == MotionEvent.ACTION_DOWN){
                    println("손가락 눌림: " + curX + ", " + curY);
                }else if(action == MotionEvent.ACTION_MOVE){
                    println("손가락 움직임" + curX + ", " + curY);
                }else if(action == MotionEvent.ACTION_UP){
                    println("손가락 뗌: " + curX + ", " + curY);
                }

                return true;
            }
        });
    }

    public void println(String data){
        textView.append(data + "\n");
    }
}

터치 이벤트 처리하기
터치 이벤트 처리하기

터치 이벤트를 처리하는 순서를 정리해 보겠다.

① XML 화면 위쪽에 위치한 뷰(view)를 findViewById 메서드로 찾아 참조한다.

② setOnTouchListener 메서드를 호출하여 리스너를 등록한다.
③ setOnTouchListener 메서드의 파라미터로 OnTouchListener 객체를 생성하면서 전달한다.
④ onTouch 메서드를 재정의한다.

 

onTouch 메서드로 MotionEvent 객체가 파라미터로 전달된다. 이 객체에는 액션 정보와 터치한 곳의 좌표 등이 들어있다. 액션 정보는 getAction 메서드로 확인할 수 있고, 정수 자료형의 값이 반환되는데 그 값과 MotionEvent 클래스에 정의된 상숫값을 비교하면 상태를 알 수 있다.

MotionEvent.ACTION_DOWN 손가락이 눌렸을 때
MotionEvent.ACTION_MOVE 손가락이 눌린 상태로 움직일 때
MotionEvent.ACTION_UP 손가락이 떼졌을 때


*onTouch 메서드의 리턴 값으로 true를 돌려주는 이유가 궁금해서 검색을 좀 해보았다. 부족한 내 영어 실력으로 번역해 본 결과는 이렇다.

 

return true: 이 이벤트에 관심이 있으니 이후에 호출될 ACTION_MOVE와 ACTION_UP과 같은 터치 이벤트는 여기서 처리하겠다.

return false: 이후에 호출될 이벤트는 여기서 받지 않고 다음 뷰로 전달해 처리하도록 하겠다.

 

https://stackoverflow.com/questions/24287292/returning-true-and-false-in-ontouch

 

 

5-3 제스처 이벤트 처리하기

제스처 이벤트는 터치 이벤트 중에서 스크롤 등을 구별한 후 알려주는 이벤트이다. 제스처 이벤트를 처리해 주는 클래스는 GeustureDetector이며, 이 객체를 만들고 터치 이벤트를 전달하면 GestureDetector 객체에서 각 상황에 맞는 메서드를 호출한다.

메서드 이벤트 유형
onDown() 화면이 눌렸을 경우
onShowPress() 화면이 눌렸다 떼어지는 경우
onSingleTapUp() 화면이 한 손가락으로 눌렸다 떼어지는 경우
onSingleTapConfirmed() 화면이 한 손가락으로 눌려지는 경우
onDoubleTap() 화면이 두 손가락으로 눌려지는 경우
onDoubleTapEvent() 화면이 두 손가락으로 눌려진 상태에서 떼거나 이동하는 등 세부적인 액션을 취하는 경우
onScroll() 화면이 눌린 채 일정한 속도와 방향으로 움직였다 떼는 경우
onFling() 화면이 눌린 채 가속도를 붙여 손가락을 움직였다 떼는 경우
onLongPress() 화면을 손가락으로 오래 누르는 경우

 

▼ 제스처 이벤트 처리하기

public class MainActivity extends AppCompatActivity {
    TextView textView;

    // 제스처 디텍터 객체 선언
    GestureDetector detector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    	// 중략
    
    	detector = new GestureDetector(this, new GestureDetector.OnGestureListener(){

            @Override
            public boolean onDown(MotionEvent motionEvent) {
                println("onDown() 호출됨");

                return true;
            }

            @Override
            public void onShowPress(MotionEvent motionEvent) {
                println("onShowPress() 호출됨");
            }

            @Override
            public boolean onSingleTapUp(MotionEvent motionEvent) {
                println("onSingleTapUp() 호출됨");

                return true;
            }

            @Override
            public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1,
                                    float v, float v1) {
                println("onScroll() 호출됨: " + v + ", " + v1);

                return true;
            }

            @Override
            public void onLongPress(MotionEvent motionEvent) {
                println("onLongPress() 호출됨");
            }

            @Override
            public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1,
                                   float v, float v1) {
                println("onFling() 호출됨: " + v + ", " + v1);
                return true;
            }
        });

        // 뷰를 터치했을 때 발생하는 터치 이벤트를 제스처 디텍터로 전달
        View view2 = findViewById(R.id.view2);
        view2.setOnTouchListener(new View.OnTouchListener(){
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                detector.onTouchEvent(motionEvent);
                return true;
            }
        });
    }
    
    // 중략
    
}

제스처 이벤트 처리하기
제스처 이벤트 처리하기

제스처 이벤트를 처리하는 순서를 정리해 보겠다.
① XML 화면 중간에 위치한 뷰(view2)를 findViewById 메서드로 찾아 참조한다.
② setOnTouchListener 메서드를 호출하여 리스너를 등록한다.
③ setOnTouchListener 메서드의 파라미터로 OnTouchListener 객체를 생성하면서 전달한다.
④ onTouch 메서드 안에서 GestureDetector 객체의 onTouchEvent 메서드를 호출하면서 MotionEvent 객체를 전달한다.
⑤ GestureDetector 객체가 터치 이벤트를 처리한 후 GestureDetector 객체에 재정의된 메서드를 호출한다.

 

*detector: 탐지기

*여기서도 많은 메서드들이 true를 반환하는데 그 이유는 위의 터치 이벤트에서 서술한 것과 같다.

 

https://stackoverflow.com/questions/3756383/what-is-meaning-of-boolean-value-returned-from-an-event-handling-method-in-andro

 

 

5-4 키 이벤트 처리하기

키 입력은 onKeyDown 메서드를 재정의하여 처리할 수 있다. 키 입력 이벤트는 하드웨어 키보드나 소프트 키패드에 상관없이 동일한 이벤트로 전달된다. 시스템 버튼 중 하나인 [BACK] 버튼도 이 이벤트로 처리할 수 있다. 나머지 시스템 버튼인 [HOME] 버튼, [Recent App] 버튼은 앱에서 직접 제어는 안 되며 키가 입력되었는지 정보만 전달받을 수 있다.

 

boolean onKeyDown (int keyCode, KeyEvent event)

boolean onKey (View v, int keyCode, KeyEvent event)

 

onKeyDown 메서드로 전달되는 파라미터는 두 개이며, keyCode는 어떤 키가 사용되었는지 구별할 때 사용되고 KeyEvent는 키 입력 이벤트에 대한 정보를 알고 싶을 때 사용된다. onKey 메서드는 뷰의 OnKeyListener 인터페이스를 구현할 때 사용된다. 다음은 keyCode 정숫값으로 구분할 수 있는 키값을 설명한다. 다음은 keyCode 정숫값으로 구분할 수 있는 키값을 설명한다.

키 코드 설명
KEYCODE_DPAD_LEFT 왼쪽 화살표
KEYCODE_DPAD_RIGHT 오른쪽 화살표
KEYCODE_DPAD_UP 위쪽 화살표
KEYCODE_DPAD_DOWN 아래쪽 화살표
KEYCODE_DPAD_CENTER [중앙] 버튼
KEYCODE_CALL [통화] 버튼
KEYCODE_ENDCALL [통화 종료] 버튼
KEYCODE_BACK [뒤로 가기] 버튼
KEYCODE_VOLUME_UP [소리 크기 증가] 버튼
KEYCODE_VOLUME_DOWN [소리 크기 감소] 버튼
KEYCODE_0 ~ KEYCODE_9 숫자 0부터 9까지의 키값
KEYCODE_A ~ KEYCODE_Z 알파벳 A부터 Z까지의 키값

 

▼ 키 이벤트 처리하기

    // 중략
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {

        if(keyCode == KeyEvent.KEYCODE_BACK){
            Toast.makeText(this, "시스템 [BACK] 버튼이 눌렸습니다.", Toast.LENGTH_LONG).show();

            return true;
        }

        return false;
    }
        
    // 중략

키 이벤트 처리하기
키 이벤트 처리하기

 

 

5-5 단말 방향을 전환했을 때 이벤트 처리하기

단말의 방향이 바뀌었을 때는 가로와 세로 화면의 비율에 따라 화면이 다시 보이게 된다. 이 때문에 액티비티는 메모리에서 없어졌다가 다시 만들어진다.


app > res > 우클릭 > New > Android Resource Directory > Directory name에 layout-land 입력 > Resource type은 layout 선택 > OK

layout-land 폴더의 이름은 미리 지정된 것으로, 앱을 실행했을 때 단말에 의해 자동으로 확인된 후 단말을 가로 방향으로 돌리면 이 폴더 안에 들어 있는 레이아웃 파일을 우선적으로 적용한다.


*layout-land 폴더는 프로젝트 창을 Android에서 project로 변경하면 찾을 수 있다.

 

단말이 바뀔 때 액티비티가 메모리에서 없어졌다가 새로 만들어지기 때문에 액티비티 안에 선언해 두었던 변숫값이 사라지므로 변수의 값을 저장했다가 다시 복원하는 방법이 있어야 한다. 이 문제를 해결할 수 있도록 onSaveInstanceState 콜백 메서드가 제공된다. 이 메서드는 액티비티가 종료되기 전의 상태를 저장하고 이때 저장한 상태는 onCreate 메서드가 호출될 때 전달되는 번들 객체로 복원할 수 있다.

 

*콜백 메서드(Callback Method): 시스템에서 자동으로 호출하는 메서드

 

▼ 단말 방향이 바뀌었을 때 데이터를 저장하고 복원하기

public class MainActivity extends AppCompatActivity {
    String name;

    EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        showToast("onCreate 호출됨");

        editText = findViewById(R.id.editText2);

        Button button = findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View view) {
                name = editText.getText().toString();
                showToast("입력된 값을 변수에 저장했습니다: " + name);
            }
        });

        if(savedInstanceState != null){
            name = savedInstanceState.getString("name");
            showToast("값을 복원했습니다: " + name);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putString("name", name);
    }
    
    // 중략
    
    public void showToast(String data){
        Toast.makeText(this, data, Toast.LENGTH_LONG).show();
    }
}

단말 방향 바꾸고 값 복원하기
단말 방향 바꾸고 값 복원하기

단말 방향이 바뀌었을 때 데이터를 저장하고 복원하는 순서를 정리해 보겠다.
① 버튼을 클릭했을 때 onClick 메서드가 호출되면서 사용자가 입력한 값을 name 변수에 할당한다.
② 단말 방향을 바꾸어 액티비티가 소멸되었다가 다시 만들어질 때는 onSaveInstanceState 메서드 안에서 name 변수의 값을 파라미터로 전달받은 Bundle 객체에 넣어준다.
③ Bundle 객체의 데이터는 단말에 저장되고 onCreate 메서드가 호출될 때 파라미터로 전달된다.
④ onCreate 메서드의 파라미터는 savedInstanceState라는 이름으로 되어있으며, 이 객체에서 데이터를 가져와 name 변수에 다시 할당하면 데이터를 복구하게 된다.

 

*단말의 방향을 바꾸려면 에뮬레이터의 [Rotate left]나 [Rotate right] 아이콘을 클릭한 다음 시스템 버튼들 옆의 작은 회전 모양 아이콘까지 눌러주어야 한다.

*에디트텍스트에 표시되는 값은 직접 복원하여 설정하지 않아도 그대로 유지된다.

 

액티비티는 바뀌지 않고 단순히 화면에 보이는 레이아웃만 바꾸고 싶다면 액티비티를 굳이 없앴다가 다시 만들 필요가 없다. 이럴 땐 액티비티의 전환 없이 단말의 방향이 바뀌는 이벤트를 앱에 전달한 다음 추가적인 기능이 동작하도록 만들 수 있다.

 

▼ AndroidManifest.xml 파일의 <activity> 태그에 configChanges 속성 추가하기

    // 중략
    
        <activity
            android:name=".MainActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:exported="true">
            
    // 중략

 

단말의 방향이 바뀌는 것을 앱에서 알 수 있도록 매니페스트에 액티비티를 등록할 때 configChanges 속성을 설정해야 한다. 방향을 세로 또는 가로로 고정시키고 싶다면 screenOrientation 속성 값을 지정하면 된다.

 

*Manifest: 나타내다

*configuration: 배열, 배치

 

configChanges 속성 값을 설정하면 단말의 방향이 바뀔 때마다 액티비티에서 인식할 수 있으며, 단말이 바뀌는 시점에 configurationChanged 메서드가 자동으로 호출된다. MainActivity.java 파일에서 onConfigurationChanged 메서드를 재정의해야 한다.

 

▼ 단말의 방향이 바뀌었을 때 호출되는 onConfigurationChanged 메서드 재정의하기

public class MainActivity extends AppCompatActivity {
    
    // 중략

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // 가로 방향으로 전환할 때 처리
        if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
            showToast("방향: ORIENTATION_LANDSCAPE");
            
        // 세로 방향으로 전환할 때 처리
        }else if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
            showToast("방향: ORIEBTATION_PORTRAIT");
        }
    }

    public void showToast(String data){
        Toast.makeText(this, data, Toast.LENGTH_LONG).show();
    }
}