KOTLIN

[Kotlin] 공부 11일차 (2022-01-25)

HJ39 2022. 1. 25. 23:42
더보기

▷ 터치와 키 이벤트

   ▶ 터치 이벤트

   ▶ 터치 이벤트 처리

   ▶ 터치 이벤트 발생 좌표 얻기

   ▶ 키 이벤트

8장 실습

 

 

 

  • 터치와 키 이벤트
  • 터치 이벤트

터치(touch) : 앱의 화면에서 발생하는 사용자 이벤트

스와이프(swipe) : 화면에 손가락을 댄 상태로 쓸어넘기는 동작

 

액티비트 클래스에 터치 이벤트의 콜백 함수인 onTouchEvent()를 선언하면 이벤트 처리가 가능하다.

onTouchEvent() 함수에 매개변수는 MotionEvent객체이고 터치 종류와 발생 지점이 담겨있다.

 

  • 터치 이벤트의 종류

→ ACTION_DOWN : 화면을 손가락으로 누른 순간의 이벤트

→ ACTION_UP : 화면에서 손가락을 떼는 순간의 이벤트

→ ACTION_MOVE : 화면을 손가락으로 누른 채로 이동하는 순간의 이벤트

 

화면에 손가락을 살짝 눌렀다가 떼면 onTouchEvent() 함수는 2번(ACTION_DOWN, ACTION_UP) 호출된다.

 

□ 터치 이벤트 처리

class MainActivity : AppCompatActivity() {
    
    override fun onTouchEvent(event: MotionEvent?):Boolean{
        when(event?.action){
            MotionEvent.ACTION_DOWN ->{
                Log.d("kkang","Touch down event")
            }
            MotionEvent.ACTION_UP ->{
                Log.d("kkang","Touch up event")
            }
        }
        return super.onTouchEvent(event)
    }
}

 

  • 터치 이벤트 발생 좌표 얻기

onTouchEvent() 함수의 매개변수인 MotionEvent 객체로 얻을 수 있다.

 

→ x : 이벤트가 발생한 뷰의 X좌표

→ y : 이벤트가 발생한 뷰의 Y좌표

→ rawX : 화면의 X 좌표

→ rawY : 화면의 Y 좌표

 

□ 터치 이벤트가 발생한 좌표 얻기

override fun onTouchEvent(event: MotionEvent?): Boolean {
    when (event?.action) {
        MotionEvent.ACTION_DOWN -> {
            Log.d("kkang", "Touch down event x: ${event.x}, rawX : ${event.rawX}")
        }
    }

→ x는 터치 이번트가 발생한 뷰에서의 좌표값이고 rawX는 스크린, 화면에서의 좌표값이다.

 

 

  • 키 이벤트

사용자가 폰의 키를 누르는 순간에 발생한다.

 

□ 키 이벤트 콜백 함수

onKeyDown 키를 누른 순간의 이벤트
onKeyUp 키를 떼는 순간의 이벤트
onKeyLongPress 키를 오래 누르는 순간의 이벤트

 

□ 키 이벤트 처리

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    Log.d("kkang", "onkeyDown")
    return super.onkeyDown(keyCode, event)
}

 

→ 소프트 키보드(soft keyboard)

앱에서 글을 입력할 때 화면 아래에서 올라오는 키보드

안드로이드 시스템에 등록된 앱으로서 키를 누르면 글은 입력되지만 키 이벤트는 발생하지 않는다.

 

→ 내비게이션 바(navigation bar)

실행되는 앱과 상관없이 안드로이드 자체에서 제공하는 시스템

볼륨 조절 버튼과 뒤로가기 버튼은 키 이벤트로 처리할 수 있다.

전원, 홈, 오버뷰 버튼은 애티비티에 onKeyDown() 함수를 선언해 놓아도 사용자가 버튼을 눌렀을 때 호출되지 않는다.

 

□ 뒤로가기 버튼과 볼륨 조절 버튼의 이벤트 처리

override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
    when (keyCode) {
        KeyEvent.KEYCODE_BACK -> Log.d("kkang","BACK Button")
        KeyEvent.KEYCODE_VOLUME_UP ->Log.d("kkang","Volumn Up")
        KeyEvent.KEYCODE_VOLUME_DOWN ->Log.d("kkang","Volumn Down")
    }
    return super.onKeyDown(keyCode,event)
}

 

□ 뒤로가기 이벤트 처리

override fun onBackPressed(){
	Log.d("kkang","Back Button")
}

→ 앱의 첫 화면에서 뒤로가기 버튼을 눌렀을 때 앱의 종료되지 않고 한 번 더 누르라는 메시지를 출력하거나 종료할 것인지 묻는 알림 창을 출력할 때 사용한다.

 

  • 뷰 이벤트
더보기

뷰의 이벤트로 터치 이벤트로 처리할 수 있지만 만약 오른쪽 그림처럼 화면에 버튼과 체크박스, 리스트가 출력되었다고 하면 체크박스가 여러 개 있을 때 어떤 체크박스인지 알기 위해서 뷰 이벤트를 사용한다.

 

  • 뷰 이벤트의 처리 구조

일정한 구조에 따라 처리된다.

이벤트 소스(event source)와 이벤트 핸들러(event handler)로 역할이 나뉘며 이 둘을 리스너(listener)로 연결해야 이벤트를 처리할 수 있다.

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

 

□ 체크박스 이벤트 처리

binding.checkbox.setOnCheckedChangeListener(object: CompoundButton.OnCheckedChangeListener{
    overrid fun onCheckedChanged(p0:CompoundButton?, p1:Boolean){
        Log.d("kkang","체크박스 클릭")
    }
})

대부분의 이벤트 핸들러 이름 형식은 OnXXXListener인 인터페이스를 구현해서 만든다.

 

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

더보기

SAM?

코틀린에서 자바 인터페이스를 간단하게 사용하기 위해 제공하는 기법

 

 

□ 액티비티에서 인터페이스를 구현한 예시 1

import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.widget.CompoundButton
import android.os.Bundle
import com.example.androidlab.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity(),CompoundButton.OnCheckedChangeListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.checkbox.setOnCheckedChangeListener(this)
    }
    override fun onCheckedChanged(p0: CompoundButton?, p1:Boolean){
        Log.d("kkang","체크박스 클릭")
    }
    
}

□ 이벤트 핸들러를 별도의 클래스로 만든 예시 

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

    binding.checkbox.setOnCheckedChangeListener(MyEventHandler())
}

□ SAM 기법으로 구현된 예시

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    val binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
    
    binding.checkbox.setOnCheckedChangeListener{
        compoundButton, b->
        Log.d("kkang","체크박스 클릭")
    }
    
}

→ 위 3개 코드는 동일 기능을 하는 코드들이다.

 

  • 클릭과 롱클릭 이벤트 처리

ClickEvent, LongClickEvent는 뷰의 최상위 클래스인 View에 정의된 이벤트이다.

 

→ open fun setOnClickListener(l: View.onClickListener?): Unit

→ open fun setOnLongClickListener( l: View.onLongClickListener?): Unit

 

ClickEvent는 OnClickListener를 구현한 객체를 이벤트 핸들러로 등록해야하고, LongClickListener는 onLongClickListener를 구현한 객체를 이벤트 핸들러로 등록해야한다.

 

□ 버튼의 클릭, 롱 클릭 이벤트 처리

binding.button.setOnClickListener{
	Log.d("kkang","클릭 이벤트")
}

binding.button.setOnLongClickListener{
	Log.d("kkang","롱클릭 이벤트")
	true
}

→ true는 함수의 반환값을 의미한다.(람다함수라서 return을 사용하지 않는다)

 

□ 자바 함수

public class SAMText{
	JavaInterface1 callback;
	public void setInterface(JavaInterface1 callback){
		this.callback = callback;
	}
}

□ 코틀린에서 자바 함수 이용

obj.setInterface(object.JavaInterface1){
	override fun callback(){
	println("hello kotlin")
	}
})

□ SAM기법을 이용한 자바 함수 호출

obj.setInterface {println("hello SAM")}

→ 위 3개 코드는 모두 같은 함수이다.

→ 추상함수 하나를 포함하는 인터페이스만 SAM기법으로 이용할 수 있다.

 

  • 실습

스톱워치 만들기

 

□ round_bottom 리소스 파일

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" android:padding="10dp">
    <solid android:color="#6666FF"></solid>
    <corners
        android:bottomLeftRadius="30dp"
        android:bottomRightRadius="30dp"
        android:topLeftRadius="30dp"
        android:topRightRadius="30dp">

    </corners>
</shape>

 

□ activity_main.xml

<?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">

    <Chronometer
        android:id="@+id/chronometer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="100dp"
        android:gravity="center_horizontal"
        android:textSize="60dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="70dp"
        android:gravity="center_horizontal"
        android:orientation="horizontal">

    <Button
        android:id="@+id/startButton"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:background="@drawable/round_button"
        android:text="Start"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />

    <Button
        android:id="@+id/stopButton"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="stop"
        android:background="@drawable/round_button"
        android:textColor="#FFFFFF"
        android:textStyle="bold"
        android:layout_marginLeft="25dp"
        android:enabled="false"/>

    <Button
        android:id="@+id/resetButton"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="Reset"
        android:background="@drawable/round_button"
        android:textColor="#FFFFFF"
        android:textStyle="bold"
        android:layout_marginLeft="25dp"
        android:enabled="false"/>
    </LinearLayout>

</RelativeLayout>

 

□ MainActivity.kt

package com.example.ch8_event

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.SystemClock
import android.view.KeyEvent
import android.widget.Toast
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)

        binding.startButton.setOnClickListener {
            binding.chronometer.base = SystemClock.elapsedRealtime() + pauseTime
            binding.chronometer.start()

            binding.stopButton.isEnabled = true
            binding.resetButton.isEnabled = true
            binding.startButton.isEnabled = false
        }

        binding.stopButton.setOnClickListener {
            pauseTime = binding.chronometer.base - SystemClock.elapsedRealtime()
            binding.chronometer.stop()

            binding.stopButton.isEnabled = false
            binding.resetButton.isEnabled = true
            binding.startButton.isEnabled = true
        }

        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){
            if(System.currentTimeMillis()-initTime > 3000){
                Toast.makeText(this,"종료하려면 한 번더 누르세요",Toast.LENGTH_SHORT).show()
                initTime = System.currentTimeMillis()
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }
}

 

□ 실행화면

실습 실행 화면

 

11일차 공부 끝!

'KOTLIN' 카테고리의 다른 글

[Kotlin] 공부 13일차 (2022-01-28)  (0) 2022.01.29
[Kotlin] 공부 12일차 (2022-01-26)  (0) 2022.01.28
[Kotlin] 공부 10일차 (2022-01-24)  (0) 2022.01.24
[Kotlin] 공부 9일차 (2022-01-23)  (0) 2022.01.24
[Kotlin] 공부 8일차 (2022-01-20)  (0) 2022.01.21