[Kotlin] 9 - Widget And Listener

2024. 6. 25. 11:24· 공부/Kotlin
목차
  1. 코드에서 View를 참조하는 법
  2. View Binding
  3. Event and Listener
  4. Listener를 만드는 방법
  5. 실습
  6. Activity가 interface를 구현
  7. 객체를 변수로 전달
  8. 익명 객체(클래스) 전달
  9. 람다식
  10. Refactoring
반응형

9 - Widget And Listener

코드에서 View를 참조하는 법

  • setContentView 함수 호출 이후 xml에 선언된 View의 객체를 코드에서 참조 가능
  • 전통적인 방법
    • findViewById(아이디) 함수로 View의 객체를 얻는 법
  • 새로운 방법
    • viewBinding

HTML의 DOM구조와 비슷하다고 볼 수 있다.

MainActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.calc)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.calc)) { v, windowInsets ->
            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(insets.left, insets.top, insets.right, insets.bottom)
            windowInsets
        }
        val textViewTitle = findViewById<TextView>(R.id.textView) // 1
        textViewTitle.text = "Hello, World!" // 2
    }
}
  • 1 - Code에서 제어하고자 하는 모든 View에 대하여 findViewById 함수를 호출해야 한다.
  • 2 - 객체에 대한 참조를 얻게되면 코드 상에서 적용한 내용들이 앱이 화면에 반영된다.
  • > 문제점
    단점 : MainActivity에 findViewById를 사용할만큼 그 갯수만큼 작성해줘야한다. 
    단점 : 새로운 화면이 로딩되면 메모리가 로딩되면서 특징 : 다른 화면(xml) 이라면 Id명이 겹쳐도 문제가 없다 → 새로운 화면이 로딩되면 R - 우리 프로젝트의 res 관리하는 클래스. 우리 프로젝트에 1개 생성된다.

    activtiy_main.xml → @+id/textViewMain findViewById(R.id.textViewSecond)
    activity_second.xml → @+id/textViewSecond findViewById(R.id.textViewSecond)

    main에는 textViewSecond가 없는 상황이다. 위젯은 파일마다 따로 있는데 ID는 R 클래스안에 모든 파일의 Id를 통째로 관리 되기 때문에 IDE 상에서는 에러가 발생하지 않고, 빌드를 할 때 그때서야 null exception이 뜬다. → null safety 보장 못함

View Binding

  • Module:app 의 build.gradle.kts에 다음과 같은 내용을 추가하면 layout 폴더 안에 있는 파일에 대한 클래스가 자동으로 생성되며 이를 이용해 view 객체를 참조할 수 있다.

  • 클래스 이름은 파일 이름을 이용해 자동으로 생성된다.
  • activity_main.xml ==> ActivityMainBinding
class MainActivity : AppCompatActivity() {
    private val binding by lazy { ActivityCalcBinding.inflate(layoutInflater) }
    // 또는 private lateinit var binding:ActivityCalcBinding 사용 가능
    // lateinit을 사용하면 onCreate 함수 안에서 binding에 할당을 해주어야 함.
    // binding = ActivityMainBinding.inflate(layoutInflater)
    // 레이아웃 파일에 해당하는 객체를 생성 

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(binding.root) // 최상위 레이아웃 설정
        // binding이 초기화가 안되어져 있다가, binding을 사용하면서 그때서야 초기화가 되어진다.
        // 그래서 lazy를 사용해서 초기화를 늦게 해준다.
        // !!!!! setContentView의 파라미터를 생성된 객체로 변경된다.
        binding.textView.text = "Hello, World!" // binding.아이디 형식으로 xml 파일의 View를 바로 참조 가능
    }
}




> 개념

  • setContentView는 레이아웃 리소스 ID를 받아서 해당 레이아웃을 화면에 표시합니다. 이때 레이아웃 파일을 해석하여 뷰의 트리 구조를 생성합니다.

  • setContentView는 파일을 주면 파일을 해석하고, View를 주면 만들어진 구조의 View를 바로 사용한다.

  • binding은 파일을 해석하여 트리 구조를 미리 만들어 놓는다. 그래서 setContentView에 binding.root를 사용하면 binding에서 미리 만들어 놓은 그 구조를 바로 사용하지만 만약 setContentView에 Activity_Calc 를 작성하면 setContentView가 새로 해석하여 트리 구조를 만들고 그 구조를 사용하여 결국에는 밑에 쓰여진 textView.text = “Hello, World!”가 반영이 되지 않는다.

  • binding은 레이아웃 파일을 해석하여 뷰의 트리 구조를 미리 만들어 놓습니다. 따라서 setContentView에 binding.root를 사용하면 binding에서 미리 만들어 놓은 그 구조를 바로 사용합니다. 이렇게 하면 뷰 바인딩을 통해 뷰를 직접 참조할 수 있으므로 findViewById를 사용하지 않아도 되고, 뷰의 ID를 잘못 참조하여 발생하는 오류를 줄일 수 있습니다.

  • 하지만 setContentView에 레이아웃 리소스 ID를 직접 작성하면 setContentView가 새로 레이아웃 파일을 해석하여 뷰의 트리 구조를 만들게 됩니다. 이 경우 binding에서 미리 만들어 놓은 구조와는 별개의 새로운 구조가 생성되므로, binding을 통해 설정한 뷰의 속성이 반영되지 않게 됩니다. 따라서 binding을 사용할 때는 항상 setContentView에 binding.root를 사용해야 합니다. 이렇게 하면 binding에서 설정한 뷰의 속성이 정상적으로 반영됩니다.

Event and Listener

  • 사용자의 터치, 키 입력 등을 Event라고 한다.
  • Event를 처리하는 방법
    • Polling: 일정 주기마다 Event 발생을 확인하는 방법. Application에서는 적합하지 않으며 Arduino 등의 펌웨어에서 주로 사용하는 방법
    • Callback 또는 Listener: 특정 이벤트에 대한 처리 함수를 지정해두면 해당 이벤트 발생 시 시스템이 지정된 함수를 호출해 주는 방법.
    • 시스템이 호출해 주므로 콜백 함수의 형식이 중요하다. -> 주로 Interface로 정의 되어 있다.

Interface로 정의되어 있는 이유 : 이벤트 처리 함수 이름과 콜백 함수의 형식을 최소한의 규칙으로 제한 하기 위하여

Listener를 만드는 방법

  • Activity가 직접 implement 하는 방법. - 계산기의 숫자 키패드 같은 데서 사용
    • Event가 특정 View에 속한 것이 아닐 때(키 입력 등)
    • View는 달라도 비슷한 코드를 실행할 때
  • 이벤트를 수신할 View에 interface의 구현체 또는 람다식을 전달
    • 추상 함수 하나만 정의된 interface의 경우 SAM 표기법으로도 전달 가능
  • Interface를 구현한 별도의 클래스를 구현하고 그 객체를 전달

실습

  • Hello world 내의 버튼 4가지의 리스너를 다양한 방법으로 작성해본다.
    • Add: Activity가 직접 구현
    • Sub: 객체를 생성하여 변수로 전달
    • Mul: 익명 객체 전달
    • Div: 람다식

Activity가 interface를 구현

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val binding by lazy { ActivityCalcBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {

    ....

    binding.plusBtn.setOnClickListener(this) // Add 버튼에 리스너를 달아줌
    }

    override fun onClick(v: View?) { // View.OnClickListener의 추상 메서드 구현 
        try{
            val n1 = binding.editTextNumber1.text.toString().toDouble()
            val n2 = binding.editTextNumber2.text.toString().toDouble()
            binding.textViewResult.text="${n1+n2}"
        }catch(e:NumberFormatException){
            return
            // lambda, no return
        }
    }
}

객체를 변수로 전달

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val binding by lazy { ActivityCalcBinding.inflate(layoutInflater) }

    private val listener = View.OnClickListener {// 객체를 변수로 전달
        try{
            val n1 = binding.editTextNumber1.text.toString().toInt()
            val n2 = binding.editTextNumber2.text.toString().toInt()
            binding.textViewResult.text="${n1-n2}"
        }catch(e:NumberFormatException){
            // lambda, no return
        }
    }

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

        binding.plusBtn.setOnClickListener(this) // interface 구현
        binding.minusBtn.setOnClickListener(listener) // 객체를 변수로 전달
    }

    override fun onClick(v: View?) { // View.OnClickListener 인터페이스(추상메소드) 구현 }
}
  • View.OnClickListener 는 SAM 이므로 이와같이 람다식으로 객체를 만든다. 일반적인 경우 object:View.OnClickListener { } 형식으로 정의해야 한다.

익명 객체(클래스) 전달

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val binding by lazy { ActivityCalcBinding.inflate(layoutInflater) }

    private val listener = View.OnClickListener {// 객체를 변수로 전달 }

    override fun onCreate(savedInstanceState: Bundle?) {
        ....
        binding.plusBtn.setOnClickListener(this) // interface 구현
        binding.minusBtn.setOnClickListener(listener) // 객체를 변수로 전달
        binding.mulBtn.setOnClickListener(object:View.OnClickListener { // 익명 클래스 전달
            override fun onClick(v: View?) {
                try{
                    val n1 = binding.editTextNumber1.text.toString().toInt()
                    val n2 = binding.editTextNumber2.text.toString().toInt()
                    binding.textViewResult.text="${n1*n2}"
                }catch(e:NumberFormatException){
                    return
                }
            }
        })
    }
    override fun onClick(v: View?) { // View.OnClickListener 인터페이스(추상메소드) 구현 }
}

람다식

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val binding by lazy { ActivityCalcBinding.inflate(layoutInflater) }

    private val listener = View.OnClickListener {// 객체를 변수로 전달 }

    override fun onCreate(savedInstanceState: Bundle?) {
        binding.plusBtn.setOnClickListener(this) // interface 구현
        binding.minusBtn.setOnClickListener(listener) // 객체를 변수로 전달
        binding.mulBtn.setOnClickListener(object:View.OnClickListener { // 익명 클래스 전달
            override fun onClick(v: View?) {
                try{
                    val n1 = binding.editTextNumber1.text.toString().toInt()
                    val n2 = binding.editTextNumber2.text.toString().toInt()
                    binding.textViewResult.text="${n1*n2}"
                }catch(e:NumberFormatException){
                    return
                }
            }
        })
        binding.divBtn.setOnClickListener { // 람다식 
            try {
                val n1 = binding.editTextNumber1.text.toString().toInt()
                val n2 = binding.editTextNumber2.text.toString().toInt()
                binding.textViewResult.text = if(n2 != 0) "${n1 / n2}" else "0으로 나눌 수 없습니다."
            } catch (e:NumberFormatException) {}
        }
    }
    override fun onClick(v: View?) { // View.OnClickListener 인터페이스(추상메소드) 구현}
}
  • interface에 정의 된 추상 함수가 하나뿐인 경우 람다식 사용 가능

Refactoring

  • 현재 코드는 중복되는 코드가 매우 많은 상태
  • 현재 기능을 유지하면서 하나의 함수에서 처리하도록 리팩토링 해본다.
    • 각 리스너에 전달되는 View 객체(it, view 등의 변수) 의 id를 이용하면 어떤 버튼이 클릭되었는지 확인 할 수 있다.

Activity의 onClick으로 통합한 경우

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val binding by lazy { ActivityCalcBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
    ....
        binding.plusBtn.setOnClickListener(this)
        binding.minusBtn.setOnClickListener(this)
        binding.mulBtn.setOnClickListener(this)
        binding.divBtn.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        try {
            val n1 = binding.editTextNumber1.text.toString().toInt()
            val n2 = binding.editTextNumber2.text.toString().toInt()
            val result = when(v?.id) {
                R.id.plusBtn -> n1 + n2
                R.id.minusBtn -> n1 - n2
                R.id.mulBtn -> n1 * n2
                else -> if(n2!= 0) n1 / n2 else 0
            }
            binding.textViewResult.text="$result"
        } catch (e: NumberFormatException) {
            // lambda, no return
        }
    }
반응형
저작자표시 비영리 (새창열림)
  1. 코드에서 View를 참조하는 법
  2. View Binding
  3. Event and Listener
  4. Listener를 만드는 방법
  5. 실습
  6. Activity가 interface를 구현
  7. 객체를 변수로 전달
  8. 익명 객체(클래스) 전달
  9. 람다식
  10. Refactoring
'공부/Kotlin' 카테고리의 다른 글
  • [Kotlin] 13 - Shared preference & ViewModel
  • [Kotlin] 12 - Android RecyclerView
  • [Kotlin] 11 - Activity And Intent
  • [Kotlin] 10 - Widget And Listener
Future0_
Future0_
rm -rf /
Future0_
Luna Developer Blog
Future0_
전체
오늘
어제
  • 분류 전체보기 (112)
    • 프로그래밍 (4)
      • 알고리즘 (4)
    • 보안 (14)
      • Dreamhack (4)
      • Hackthebox (1)
      • Webhacking (9)
    • 프로젝트 (4)
    • 공부 (80)
      • Database (2)
      • Python (11)
      • System (4)
      • Java (13)
      • JSP (13)
      • Spring (11)
      • Kotlin (16)
      • 자료구조 (10)
      • 기계학습 (0)
    • Docker (4)
    • Github (2)
    • Tip (1)
    • 잡담 (2)

블로그 메뉴

  • 홈
  • 태그

공지사항

인기 글

태그

  • 상속
  • shared preference
  • Database
  • SpringBoot
  • 키 해시
  • 자료구조
  • cs
  • 컴퓨터
  • ViewModel
  • 보안
  • jsp
  • android studio 삭제
  • Java
  • native app
  • webhacking
  • 코틀린기본문법
  • Computer science
  • 프로그래밍
  • 알고리즘
  • spring
  • 디버깅키해시
  • Kotlin
  • Android Studio
  • docker
  • 1.9.22
  • dreamhack
  • React
  • api 통신
  • 자바빈즈
  • Python

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.0
Future0_
[Kotlin] 9 - Widget And Listener
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.