오늘은 이어서 이벤트 처리 방법에 대해 공부했다.
사용자의 동작은 이벤트를 발생시키고, 앱은 화면에서 발생하는 다양한 사용자 이벤트를 처리해 상호작용한다.
손가락으로 화면을 잠시 눌렀다가 떼는 행위.
override fun onTouchEvent(event: MotionEvent?): Boolean {
return super.onTouchEvent(event)
}
위처럼 콜백함수 선언해두면, 유저가 액티비티 화면을 터치하는 순산 onTouchEvent() 함수가 자동으로 호출된다.
매개변수인 MotionEvent 객체에는 터치의 종류와 좌표값이 담긴다.
터치 이벤트 종류
- Action_Down : 화면을 손가락으로 누른 순간의 이벤트
- Action_Move : 손가락으로 화면을 누른 채 이동하는 순간의 이벤트
- Action_UP : 화면에서 손가락을 떼는 순간의 이벤트
터치 발생 좌표 얻기
x: 이벤트가 발생한 뷰 안의 좌상단 기준 x좌표
y : 이벤트가 발생한 뷰 안의 좌상단 기준 y좌표
rawX : 디바이스 전체 화면에서의 x좌표
rawY : 디바이스 전체 화면에서의 y좌표
MotionEvent?의 action을 해당 이벤트로 판별해 동작 처리.
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
Log.d("event","터치 좌표는 x : ${event.x}, rawX : ${event.rawX}")
}
MotionEvent.ACTION_UP -> {
Log.d("event", "UP")
}
}
return super.onTouchEvent(event)
}
사용자가 폰의 키를 누르는 순간 발생하는 이벤트
여기서 의미하는 키에서 텍스트 입력시 화면 하단에서 나오는 소프트 키보드
를 이용해 입력한 키는 제외된다. 즉, 소프트 키보드는 키 이벤트 X.
폰 하드웨어 버튼인 전원
, 볼륨UP&DOWN
, 그리고 하단 네비게이션 바의 뒤로가기
, 홈
, 오버뷰
(최근 사용한 목록)의 키가 키이벤트에 포함된다.
하지만 전원, 홈, 오버뷰
버튼은 액티비티의 onKeyDown() 함수로 앱에서 이벤트를 처리할 수 없는 버튼이다.
즉, 뒤로가기, 볼륨업, 볼륨다운
은 처리 가능하다.
뒤로가기 버튼은 앱에서 이벤트 처리하지 않으면 이전 액티비티로 이동한다.
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when(keyCode) {
KeyEvent.KEYCODE_BACK -> Log.d("event", "뒤로가기 버튼")
KeyEvent.KEYCODE_VOLUME_UP -> Log.d("event", "볼륨 업")
KeyEvent.KEYCODE_VOLUME_DOWN -> Log.d("event", "볼륨 다운")
}
return super.onKeyDown(keyCode, event)
}
TextView, ImageView 같은 뷰를 사용자가 터치했을때의 이벤트
터치, 키 이벤트는 콜백함수인 onTouchEvent(), onKeyDown() 함수만 액티비티에 선언해도 이벤트를 처리할 수 있다.
하지만, 뷰 이벤트는 콜백함수의 선언만으로는 처리할 수 없다.
구성요소
이벤트 소스 : event가 발생한 객체
이벤트 핸들러 : event 발생 시 실행할 로직이 구현된 객체
리스너 : 이벤트 소스와 핸들러를 연결하는 함수
이벤트 소스에 리스너로 이벤트 핸들러를 등록하면 이벤트 발생 시 실행되는 구조.
대부분 이벤트 핸들러의 형식은OnXXXListener
이다.
기본 구조 예시
checkbox1이 이벤트소스, setOnCheckedChangeListener가 리스너, object객체가 이벤트 핸들러이다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.checkbox1.setOnCheckedChangeListener(object: CompoundButton.OnCheckedChangeListener{
override fun onCheckedChanged(p0: CompoundButton?, p1: Boolean) {
Log.d("event","체크박스 클릭")
}
})
}
어느 방법이든 지정된 인터페이스를 구현한 객체를 이벤트 핸들러로 등록한다.
// 1. 액티비티에서 인터페이스 구현
class MainActivity1 : AppCompatActivity(), CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(p0: CompoundButton?, p1: Boolean) {...}
//...
binding.checkbox1.setOnCheckedChangeListener(this)
}
// 2. 이벤트 핸들러를 별도의 클래스로 구현
class MyEventHandler : CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(p0: CompoundButton?, p1: Boolean) {...}
}
//...
binding.checkbox1.setOnCheckedChangeListener(MyEventHandler())
// 3. 람다(SAM 기법)로 구현
class MainActivity1 : AppCompatActivity() {
// ...
binding.checkbox1.setOnCheckedChangeListener { p0, p1 -> Log.d("event", "체크박스 클릭")
}
뷰의 최상위 클래스인 View에 정의된 이벤트로 가장 기초적이면서 많이 사용되는 이벤트
ClickEvent : 뷰를 짧게 클릭할 때 발생하는 이벤트
LongClickEvent : 뷰를 길게 클릭할 때 발생하는 이벤트
두 이벤트의 핸들러
open fun setOnClickListenr(l: View.onClickLister?): Unit
open fun setOnLongClickListenr(l: View.onLongClickLister?): Unit
binding.btn.setOnClickLister {
//...
}
binding.button1.setOnClickListener {
Log.d("event", "버튼 클릭")
}
// LongClick은 콜백함수의 반환값이 Boolean이라 마지막줄 true가 필요하다.
binding.button1.setOnLongClickListener {
Log.d("event", "롱버튼 클릭")
true
}
package com.example.ch8_event
import android.os.Bundle
import android.os.SystemClock
import android.view.KeyEvent
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.ch8_event.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
// 뒤로가기 버튼 누른 시각을 저장하는 속성
var initTime = 0L
// 멈춘 시각을 저장하는 속성
var pauseTime = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// start 버튼 클릭 이벤트 처리
binding.startButton.setOnClickListener {
binding.chronometer.base = SystemClock.elapsedRealtime() + pauseTime
binding.chronometer.start()
// 버튼 표시 조정
binding.startButton.isEnabled = false
binding.stopButton.isEnabled = true
binding.resetButton.isEnabled = true
}
// stop 버튼 클릭 이벤트 처리
binding.stopButton.setOnClickListener {
pauseTime = binding.chronometer.base - SystemClock.elapsedRealtime()
binding.chronometer.stop()
binding.stopButton.isEnabled = false
binding.startButton.isEnabled = true
binding.resetButton.isEnabled = true
}
// reset 버튼 클릭 이벤트 처리
binding.resetButton.setOnClickListener {
pauseTime = 0L
binding.chronometer.base = SystemClock.elapsedRealtime()
binding.chronometer.stop()
binding.stopButton.isEnabled = false
binding.resetButton.isEnabled = false
binding.startButton.isEnabled = true
}
}
// 뒤로가기 버튼 이벤트 핸들러
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// 누른 버튼이 뒤로가기 버튼일 때
if (keyCode === KeyEvent.KEYCODE_BACK) {
// 뒤로가기 버튼을 처음 눌렀거나, 누른지 3초가 넘었을 때
if (System.currentTimeMillis() - initTime > 3000){
Toast.makeText(this, "종료하려면 한번 더 누르세요!", Toast.LENGTH_SHORT).show()
initTime = System.currentTimeMillis()
return true
}
}
return super.onKeyDown(keyCode, event)
}
}