본문 바로가기

안드로이드/부스트코스 안드로이드 기본편

[안드로이드 기본] 터치 이벤트 순서 알아보기

안드로이드 사용자 인터페이스를 보면, 액티비티 위에 다양한 뷰들이 올라가는 방식이다.

그러다보면 이벤트가 중첩되어서 처리되는 경우가 있다.

예를 들어서, 어떤 액티비티에 터치 이벤트가 설정되어 있는데

그 위에 올라가 있는 뷰에도 터치 이벤트가 설정되어 있다면 어떻게 될까???

그리고 그 뷰에 추가적으로 터치 리스너도 추가 되어 있다면 어떻게 동작할까?

무엇이 먼저 처리되지??

터치 이벤트를 처리하고나서 해당 이벤트를 전달하고 싶지 않을 때 어떻게 해야할까??

 

엑티비티위에 버튼이 올라간다고 하면,

그 버튼을 클릭했을 때 실질적으로는 버튼도 터치가 되지만, 그 해당 엑티비티도 선택이된다.

그렇다는 것은 각각 가지고 있는 터치 이벤트들이 전달 전달되어 실행된다는 것이다.

 

먼저 아래의 예제는

1. 엑티비티에 터치이벤트를 하나 넣을거다. (눌렀을 때 동작하게)

2. 뷰라는 것을 상속하는 CustomView라는 것을 하나 만들어 놓고, 이것을 1에서 만든 엑티비티 위에 덮어둘거다.
(이 뷰를 클릭했을 때 터치가 동작하게)

3. 마지막으로 CustomViewsetOnTouchListener()를 등록하여 실행시켜 보려고한다.

 

먼저 아무것도 존재하지 않는 레이아웃 하나를 만든다.

[activity_main.xml]

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

그리고, MainActivity.java 코드에 추가할 View를 상속받은 CustomView.java를 구현한다.

[CustomView.kt]

package com.practice.demotouchevent1

import android.content.Context
import android.util.Log
import android.view.MotionEvent
import android.view.View

class CustomView(context: Context): View(context) {

    private val tagName = MainActivity::class.java.simpleName

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if(event!!.action == MotionEvent.ACTION_DOWN){
            Log.d(tagName, "onTouchEvent : CustomView 구현")
            return false
        }
        return true
    }
}

CustomView클래스에서는 View클래스에서 제공하는 onTouchEvent를 오버라이드해서 사용한다.

event.action == MotionEvent.ACTION_DOWN은 손가락이 터치스크린 아래로 눌렀을 때 발생된다. (즉, 터치했을 때 발생하는)

이벤트가 발생할 때 확인을 위해 로그를 찍어보기로 하자! 

 

이렇게 만들어진 CustomViewMainActivity에 붙여보기로 하자!

[MainActivity.kt]

package com.practice.demotouchevent1

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View

class MainActivity : AppCompatActivity() {

    private val tagName = MainActivity::class.java.simpleName

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //뷰를 상속받은 커스텀뷰 만들기
        val customView = CustomView(this)

        //커스텀뷰에 터치 이벤트 리스너 등록하기
        customView.setOnTouchListener(object: View.OnTouchListener {
            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                if (event!!.action == MotionEvent.ACTION_DOWN) {
                    Log.d(tagName, "onTouch : CustomView 익명객체")
                    return false
                }
                return true
            }
        })
        setContentView(customView)
    }

    //메인 엑티비티가 선택될때 호출된다.
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if(event!!.action == MotionEvent.ACTION_DOWN){
            Log.d(tagName, "onTouchEvent : 엑티비티에 선언")
            return false
        }
        return true
    }
}

 

MainActivity에 뷰를 붙이기 위해 CustomView를 setContentView의 인자로 넘기고 있다. 

그리고 CustomView를 setOnTouchListener를 등록한다. 이때도 확인을 위해 로그를 찍어보자!

 

마지막으로 엑티비티도 CustomView를 클릭하면서 터치 이벤트가 발생하는지 알아보기 위해서

엑티비티에도 onTouchEvent를 오버라이드 한다.

마찬가지로 로그를 찍어본다.

 

[결과값]

2020-02-06 10:28:49.916 1369-1369/com.practice.demotouchevent1 D/MainActivity: onTouch : CustomView 익명객체
2020-02-06 10:28:49.916 1369-1369/com.practice.demotouchevent1 D/MainActivity: onTouchEvent : CustomView 구현
2020-02-06 10:28:49.917 1369-1369/com.practice.demotouchevent1 D/MainActivity: onTouchEvent : 엑티비티에 선언

 

로그를 분석해보니, 먼저 CustomView에 리스너로 등록했던 onTouch()가 먼저 발생하고, 그다음 엑티비티 위에 올라가있던 CustomView의  onTouchEvent()가 발생하고, 마지막으로 엑티비티의 onTouchEvent()가 발생하면서 터치이벤트가 전달전달하면서 발생한다는 것을 알 수 있었다.

 

그렇다면? CustomView에 터치 리스너로 등록했었던, onTouch이벤트만 발생시킬수는 없을까?? 끝내고 싶을때 어떻게 해야할까?

[MainActivity.kt에 customView에 터치 리스너 등록하는 부분]

//커스텀뷰에 터치 이벤트 리스너 등록하기
        customView.setOnTouchListener(object: View.OnTouchListener {
            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                if (event!!.action == MotionEvent.ACTION_DOWN) {
                    Log.d(tagName, "onTouch : CustomView 익명객체")
                    return true // false -> true로 변경
                }
                return true
            }
        })

onTouch()라던지, onTouchEvent() 모두 반환값을 Boolean값으로 되어 있다.

왜?? 이게 뭐하는 녀석인데?

CustomViewonTouch() -> CustomViewonTouchEvent() -> ActivityonTouchEvent() 순으로 터치이벤트가 실행되고 있다. 모든 터치 이벤트를 발생시키는게 효율적이지 않을 수가 있다는 것이다. 그래서 우리는 멈출 수 있는 기능이 필요하다.

 

그 기능을 return true 또는 false로 제어가 가능하다.

true로 반환값을 날려주는 것은, "내가 터치 이벤트 실행했으니.. 다음 전달 받을 애들아 너네는 실행하지마~" 라는 뜻이다.

그냥 쉽게 "나만 실행할거야" / "네(true)"라고 알아두면 이해가 될것이다.

 

[결과값]

2020-02-06 10:32:35.923 1515-1515/com.practice.demotouchevent1 D/MainActivity: onTouch : CustomView 익명객체

이로써, 터치이벤트는 CustomView의 onTouch메소드만 실행시키고 끝이난다.

 

터치이벤트 전체코드 보러가기