사용자 이벤트 처리하기 - 뷰 이벤트

이윤설·2024년 8월 26일
0

액티비티 화면은 TextView, EditText, ImageView, Button 등의 뷰로 구성된다. 이 뷰들을 사용자가 터치했을 때의 이벤트는 터치 이벤트를 직접 처리하지 않고, 각 뷰에서 제공하는 별도의 이벤트 처리 방식을 사용한다.

뷰의 이벤트도 터치 이벤트로 처리할 수 있지만 그렇게 하면 프로그래밍이 복잡해진다.

위 사진을 예로 들어보자.
이 화면에서 사용자가 하트 버튼을 누르면 발생하는 이벤트는 터치 이벤트로 처리할 수 있다.
즉, 액티비티에 onTouchEvent()를 선언하면 된다. 하지만 화면에 뷰가 최소 4개이므로 어느 뷰를 터치했는지 알아야 한다.
즉, onTouchEvent() 매개변수를 이용해 사용자가 터치한 지점의 좌표를 얻어서 처리를 해야하는데, 뷰가 많을 때는 프로그래밍이 복잡해진다.
그러나 각 뷰가 제공하는 별도의 이벤트, 예를 들면 버튼은 ClickEvent, 체크박스는 CheckedChangeEvent, 리스트는 ItemClickEvent로 처리하면 더 간단명료해진다.
따라서 대부분의 뷰는 해당 뷰에 맞는 이벤트를 따로 제공하며 이를 사용한다.

그래도 이해가 안간다면...

뷰 이벤트의 처리 구조

뷰 이벤트는 이전 장에서 배운 onTouchEvent, onKeyDown과 같은 이벤트 콜백 함수만 선언해서는 처리할 수 없다.

뷰 이벤트 처리는 이벤트 소스와 이벤트 핸들러로 역할이 나뉘며, 이 둘을 리스너로 연결해야 이벤트를 처리할 수 있다.

  • 이벤트 소스: 이벤트가 발생한 객체
  • 이벤트 핸들러: 이벤트 발생 시 실행할 로직이 구현된 객체
  • 리스너: 이벤트 소스와 이벤트 핸들러를 연결해 주는 함수

예제

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.checkbox.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener {
            override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
                Log.d("ryan", "체크박스 클릭: $isChecked")
            }
        })
    }
}

  • 이벤트소스: checkbox
  • 리스너: setOnCheckedChangeListener
  • 이벤트핸들러: object

앱이 시작되면 체크박스가 화면 중앙에 표시된다.
사용자가 체크박스를 클릭하면 setOnCheckedChangeListener가 호출된다.
리스너 내에서 Log.d를 사용하여 체크박스의 상태를 로그로 출력한다.

뷰 이벤트의 처리 구조 - 여러가지 방법들

인터페이스로 구현한 object 클래스를 이벤트 핸들러로 만들었지만, 액티비티 자체에서 인터페이스를 구현할 수도 있다.
또한 이벤트 핸들러를 별도의 클래스를 만들어 처리할 수도 있으며 코틀린의 SAM기법을 사용할 수도 있다.

  1. 액티비티에서 직접 인터페이스 구현

이 방법은 액티비티 클래스 자체가 이벤트 리스너 인터페이스를 구현하는 방식이다.

class MainActivity : AppCompatActivity(), CompoundButton.OnCheckedChangeListener {
    
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.checkbox.setOnCheckedChangeListener(this)
    }

    override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
        Log.d("MyApp", "체크박스 상태: $isChecked")
    }
}

장점:

  • 모든 이벤트 핸들러를 한 곳에서 관리할 수 있다.
  • 액티비티의 다른 메서드나 속성에 쉽게 접근할 수 있다.

단점:

  • 많은 이벤트를 처리해야 할 경우 코드가 복잡해질 수 있다.
  1. 별도의 클래스로 이벤트 핸들러 만들기
class CheckboxEventHandler : CompoundButton.OnCheckedChangeListener {
    override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
        Log.d("MyApp", "체크박스 상태: $isChecked")
    }
}

class MainActivity : AppCompatActivity() {
    
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.checkbox.setOnCheckedChangeListener(CheckboxEventHandler())
    }
}

장점:

  • 이벤트 핸들링 로직을 분리하여 코드 구조가 깔끔해진다.
  • 여러 곳에서 재사용할 수 있다.

단점:

  • 액티비티의 상태나 메서드에 접근하기 어려울 수 있다.
  1. SAM(Single Abstract Method) 변환 사용

SAM 변환은 단일 추상 메서드를 가진 인터페이스를 간단한 람다식으로 표현할 수 있게 해주는 코틀린의 기능이다.

class MainActivity : AppCompatActivity() {
    
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.checkbox.setOnCheckedChangeListener(this::onCheckboxChanged)
    }

    private fun onCheckboxChanged(buttonView: CompoundButton, isChecked: Boolean) {
        Log.d("MyApp", "체크박스 상태: $isChecked")
    }
}

장점:

  • 코드가 간결하면서도 가독성이 좋다.
  • 별도의 클래스나 인터페이스 구현 없이 메서드 참조를 사용할 수 있다.

단점:

  • 복잡한 로직을 구현하기에는 제한적일 수 있다.

각 방법은 상황에 따라 장단점이 있다:

  1. 액티비티에서 직접 구현: 간단한 앱이나 이벤트가 많지 않을 때 유용하다.
  2. 별도 클래스: 복잡한 이벤트 처리 로직이 필요하거나, 여러 곳에서 재사용해야 할 때 좋다.
  3. SAM 변환: 간단한 이벤트 처리에 적합하며, 코드를 간결하게 유지하고 싶을 때 좋다.

클릭과 롱클릭 이벤트 처리

안드로이드는 앱의 화면을 구성하는 데 필요한 다양한 뷰를 제공하며 대부분 뷰에서 자체 이벤트를 제공한다. 그런데 뷰가 아무리 많아도 이벤트 처리 구조는 동일하다.
그러므로 이벤트 소스와 이벤트 핸들러를 리스너로 연결하는 구조만 이해한다면 어떤 뷰 이벤트라도 쉽게 처리할 수 있다.

ClickEvent는 OnClickListener를 구현한 객체를 이벤트 핸들러로 등록해야 한다.
LongClickEvent는 OnLongClickListener를 구현한 객체를 이벤트 핸들러로 등록해야 한다.

예제

class MainActivity : AppCompatActivity() {

    private lateinit var button: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button = findViewById(R.id.button)

        // Click 이벤트 처리
        button.setOnClickListener {
            // 클릭했을 때 수행할 작업
            Toast.makeText(this, "Button Clicked!", Toast.LENGTH_SHORT).show()
        }

        // Long Click 이벤트 처리
        button.setOnLongClickListener {
            // 길게 클릭했을 때 수행할 작업
            Toast.makeText(this, "Button Long Clicked!", Toast.LENGTH_SHORT).show()
            true // true를 반환하면 롱 클릭 이벤트가 처리되었음을 의미
        }
    }
}

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click or Long Click Me"
        android:layout_centerInParent="true"/>

</RelativeLayout>


만약 버튼을 클릭하면 토스트 메시지에 Button Clicked가 뜬다.
중요한 점은 이벤트 소스, 리스너, 이벤트 핸들러가 무엇인지 파악하는 것이다.

이벤트 소스: button (버튼 객체)
리스너: OnClickListener (클릭 이벤트를 처리), OnLongClickListener (롱 클릭 이벤트를 처리)
이벤트 핸들러: 클릭 및 롱 클릭 이벤트가 발생했을 때 실행되는 Toast 메시지를 표시하는 코드 블록

시계 앱의 스톱워치 기능 만들기

profile
화려한 외면이 아닌 단단한 내면

0개의 댓글