이 글은 깡썜의 안드로이드 프로그래밍을 보며 작성하였습니다.
스마트폰 화면에서 발생하는 사용자 이벤트는 크게 두 가지 모델로 나누어 봅니다. 델리게이션 이벤트 모델(Delegation Event Model)과 하이어라키 이벤트 모델(Hierarchy Event Model)입니다. 델리게이션이벤트 모델은 뷰에서 발생하는 이벤트를 처리하기 위한 모델이고, 하이어라키 이벤트 모델은 액티비티에서 발생하는 사용자의 터치나 키 이벤트를 직접 처리하기 위한 모델입니다.
델리게이션이벤트 모델은 이벤트 소스와 이벤트 핸들러를 리스너(Listener)로 연결하여 처리하는 구조입니다.
모든 뷰는 버튼과 같이 터치 이벤트로 처리할 수 있습니다. 그렇다면 모두 터치 이벤트로 처리하면 되는데 위와 같은 구조로 왜 처리하냐면 이벤트를 조금 더 명료하게 처리하기 위해서 입니다.
위의 예시를 보면 취소, 저장과 같이 버튼형식도 있지만, 텍스트, Switch등도 존재합니다. 이 때, 이를 모두 터치이벤트로 처리하면 개발자가 직접 알고리즘으로 해결해야 합니다. 그러기 보다는 이벤트 소스(이벤트가 발생한 뷰 객체)에 따라서 ClickEvvent, CheckEvent등 다르게 줘서 이벤트를 만드는 것입니다. 즉, 델리게이션 이벤트 모델은 이벤트가 발생한 객체를 명료하게 지징하고자 이벤트 소스를 사용하고, 이벤트 성격을 명료하게 지징하고자 리스너를 사용합니다.
위의 그림처럼 SwitchView에서 CheckedChangedEvent가 발생하면 어느 객체에서 이벤트가 처리된다는 것을 명료하게 연결하여 처리할 수 있습니다. 코드는 아래와 같습니다.
// CompoundButton.OnCheckedChangeListener를 구현하는 싱글턴 객체 생성 후 코드 구현
switchView.setOnCheckedChangeListener(object: CompoundButton.OnCheckedChangeListener {
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
// 구현 내용
}
})
// SAM을 이용한 람다식 사용
switchView.setOnCheckedChangeListener { buttonView, isChecked ->
// 구현 내용
}
코드의 내용은 SwitchView 객체에서 CheckedChangeEvent가 발생하면 object 객체를 실행하여 이벤트를 처리하라는 의미입니다. 이 때, 이벤트 핸들러(위에서는 object) 클래스는 꼭 지정된 인터페이스를 구현해야 합니다.
SAM 활용(Single Abstract Method)
SMA은 하나의 추상 함수를 가지는 인터페이스 활용을 목적으로 합니다.
인터페이스를 구현한 객체를 등록할 때 람다 함수를 이용하여 쉽게 등록하는 방법인데, 위의 예에서는 CompoundButton.OnCheckedChangeListener 인터페이스를 활용했습니다.
원래는 setOnCheckedChangeListener의 매개변수로 CompoundButton.OnCheckedChangeListener 인터페이스를 구현한 객체를 넘겨줘야 하는데 람다식만 지정하면 컴파일러가 자동으로 인터페이스에 추상함수가 하나만 있으므로 그 함수를 오버라이드받고 개발자가 지정한 람다 함수의 내부 내용을 오버라이드 받은 함수 내부에 추가해줍니다. 또한 이러한 람다만 전달해도 컴파일러가 알아서 매개변수에 선언된 인터페이스를 구현한 익명 클래스를 자동으로 만들어줍니다. 이 모든 작업이 컴파일러에 의해 자동으로 이뤄지므로 개발자는 람다 함수만 정의하면 되는 것입니다.
안드로이드에서는 클릭 이벤트 이외에 다양한 이벤트를 제공하며 이벤트 소스인 뷰에 따라 다른 이벤트를 제공하기도 합니다. 이 때, 뷰가 많고 이벤트 종류가 많더라도 델리게이션 이벤트 모델만 이해하면, 이벤트를 처리하는 구조는 모두 같습니다. 이벤트 소스와 이벤트 핸들러는 setOnXXXListener() 함수로 연결하고, 이벤트 핸들러는 OnXXXListener를 구현해서 작성합니다.
button.setOnClickListener {
// 구현 내용
}
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
// 구현 내용
}
})
button.setOnLongClickListener(object: View.OnLongClickListener {
override fun onLongClick(v: View?): Boolean {
// 구현 내용
return false
}
})
button.setOnLongClickListener {
// 구현 내용
false
}
하이어라키 이벤트 모델(Hierarchy Event Model)은 액티비티가 화면에 출력되었을 때 발생하는 사용자의 키 이벤트와 화면 터치 이벤트를 처리하기 위한 모델입니다. 하이어라키 이벤트 모델은 델리게이션 이벤트 모델 처럼 이벤트 소스와 이벤트 핸들러를 리스너로 연결하여 처리하는 구조가 아닙니다. 만약 액티비티에서 터치 이벤트와 키 이벤트를 직접 처리하고 싶다면 이벤트 발생 시 자동 호출되는 함수만 액티비티 내에 재정의하면 됩니다
터치 이벤트가 발생할 때 콜백 함수를 액티비티 내에 정의하는 것만으로 이벤트 처리가 가능합니다. 코드는 아래와 같습니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
// 화면을 터치 시 발생하는 콜백 함수
override fun onTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
// 화면에 터치된 순간의 이벤트
MotionEvent.ACTION_DOWN -> {
// 이벤트가 발생한 뷰 내에서의 좌표값
val pointX = event.x
val pointy = event.y
// 화면에서의 좌표값
val screenX = event.rawX
val screenY = event.rawY
}
// 터치를 떼는 순간의 이벤트
MotionEvent.ACTION_UP -> {
}
// 터치한 후 이동하는 순간의 이벤트
MotionEvent.ACTION_MOVE -> {
}
}
return true
}
}
사용자가 스마트폰의 키를 눌렀을 때 이벤트 처리가 필요한 경우가 있습니다. 그런데 아래와 같은 소프트 키보드는 키 이벤트로 처리할 수 없습니다. 만일 소프트 키보드가 아닌 하드웨어 키보드가 제공되는 스마트폰이라면 해당 하드웨어 키보드의 키가 눌렸을 때 키 이벤트로 처리할 수 있습니다.
그러나 안드로이드 스마트폰은 대부분 소프트웨어 키보드를 이용합니다. 그렇기에 키 이벤트는 키보드 이외의 키 이벤트를 처리하기 위해 자주 사용됩니다. 그 예시가 뒤로가기 버튼, 홈 버튼 등인데 홈 버튼같은 경우는 일반 애플리케이션에서 이벤트 처리로 제어할 수 없습니다.
따라서 앱에서 키 이벤트 처리는 뒤로가기 버튼 처리가 대부분입니다. 뒤로가기 버튼은 기본적으로 이전 액티비티로 화면을 전환하는 버튼인데, 이 이벤트 처리를 다르게 작동시키고 싶을 때 키 이벤트를 이용합니다. 예를 들어, 뒤로가기 버튼을 눌렀을 때, "종료하려면 한번 더 누르세요." 와 같은 메시지를 띄우는 작업은 키 이벤트를 처리해서 작성합니다.
// 키가 눌린 순간의 이벤트
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// 매개변수인 keyCode로 어느 버튼을 누른 건지 식별할 수 있다
if(keyCode == KeyEvent.ACTION_DOWN) {
}
return super.onKeyDown(keyCode, event)
}
// 키를 뗴는 순간의 이벤트
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
return super.onKeyUp(keyCode, event)
}
// 키를 오래 누르는 순간의 이벤트
override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
return super.onKeyLongPress(keyCode, event)
}
이 때, 뒤로가기 버튼 이벤트를 처리하는 메서드가 하나 더 있는데, onBackPressed() 메서드입니다. 이 메서드는 뒤로가기 버튼 제어만을 목적으로만 사용합니다.
// 뒤로가기 버튼이 눌렸을 시
override fun onBackPressed() {
super.onBackPressed()
}
뒤로가기 버튼을 3초 이내에 두번 눌러 나가지는 소스는 다음과 같습니다.
class MainActivity : AppCompatActivity() {
// 시간 확인용 변수
var initTime: Long = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
// 키가 눌린 순간의 이벤트
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// 매개변수인 keyCode로 어느 버튼을 누른 건지 식별할 수 있다
if(keyCode == KeyEvent.ACTION_DOWN) {
// 햔재 시간과 비교해서 버튼을 누른 시간이 3초 이상일 경우
if(System.currentTimeMillis() - initTime > 3000) {
val toast = Toast.makeText(this, "종료하려면 한번 더 누르세요", Toast.LENGTH_SHORT)
toast.show()
// 현재 시간 저장
initTime = System.currentTimeMillis()
// 3초 이내일 경우(즉, 버튼을 한번 누르고 3초 이내로 또 누른 경우)
} else {
finish()
}
}
return super.onKeyDown(keyCode, event)
}
}