안드로이드 개발을 처음 배울 때에는 findViewById의 사용법을 먼저 배웠으나, 찾아보니 2021년 정도 이후로 구글쪽에서는 ViewBinding의 사용을 권장하고 있는 것 같다. (왜??)
이번 글에서는 findViewById와 ViewBinding의 공통점과 차이점을 알아보고, 코드상에서 어떻게 작성해야하는지에 대해 정리해본다.
findViewById | ViewBinding | |
---|---|---|
Type safety | 뷰 검색 시, 해당 뷰 유형을 명시적으로 캐스팅해야하므로 런타입 오류의 가능성이 있다. | 뷰바인딩 클래스를 사용하여 뷰를 검색하므로 잘못된 타입의 뷰에 접근하는 오류를 방지한다. |
Null safety | 검색한 뷰가 존재하지 않을 경우 null을 반환할 수 있어 null 체크가 필요하다. | 뷰바인딩 안에는 레이아웃에 정의된 뷰가 담겨져 있으므로 null을 반환할 일이 없다. |
성능 | 뷰를 검색할 때마다 뷰 계층 구조를 탐색해야한다. | 바인딩 클래스를 사용하여 뷰를 직접 참조하므로 더 효율적이다. |
레이아웃 XML 파일과 바인딩 클래스 생성 | XML 파일과 별도로 뷰를 연결할 코드를 작성해야한다. | 컴파일러가 자동으로 XML 파일을 기반으로한 바인딩 클래스를 생성하므로 작성할 코드가 줄어든다. |
차이점을 살펴보고나니, 왜 구글에서 ViewBinding의 사용을 권장했는지 알 것 같다. 실제로 코드에서 어떤식으로 작성해야하는지도 살펴보자.
findViewById<뷰 타입>(뷰 아이디)
<FrameLayout android:id="@+id/btn" android:layout_width="80dp" android:layout_height="30dp" android:layout_marginStart="16dp" android:background="@drawable/mainpage_fl_btn_rec" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/myPage" android:textSize="15sp" android:textStyle="bold" /> </FrameLayout>
이렇게 생긴 FrameLayout이 있다면, 이 요소를 버튼으로 사용하기 위해 kotlin 파일에는 아래와 같이 작성해줘야 한다.
val myPageBtn = findViewById<FrameLayout>(R.id.btn) myPageBtn.setOnClickListener { goMyPage() }
작성하기에는 간단하여 마구 사용했지만,,, 솔직히 쓰다가 null 오류가 날 때가 종종 있었다. 보통은 id의 이름을 잘 못 썼거나 했을 때에 null 오류가 일어났었다.
아래 내용을 작성해줘야 한다.
android { //이것저것다른것들 viewBinding { enable = true } }
- 이렇게 하면, 모듈에 포함된 각 XML 파일에 대한 바인딩 클래스가 생성된다.
- 각 클래스의 이름은 XML 파일 이름을 파스칼 표기법으로 바꾸고 끝에 Binding을 추가한 것이다.
- activity_main.xml -> ActivityMainBinding
즉, 내가 XML에 그려놓은 모든 요소에 대한 주소 값을 담고 있는 주소록이 내 손에 쥐어지는 것이다..! findViewById의 방식이 모든 집을 다 돌아다니면서 "여기가 neo씨네 집인가요?" 문을 두들기는 방식였다면, ViewBinding은 주소록을 보고 "음 neo씨네 집은 00아파트 00호군" 이러고 바로 해당 주소로 찾아가는 것이다. 세상 편하다~
그 이후 코드 상에서 사용하면 되는데, 이때 기억해야 하는 것들이 있다.
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = MainActivityBinding.inflate(layoutInflater) val view = binding.root setContentView(view) }
- inflate() 메서드를 호출하면 MainActivityBinding 클래스 인스턴스가 생성된다.
- binding.root로 해당 바인딩의 root 뷰, 즉 그려놓은 activity_main.xml 뷰의 참조 값을 가져온다.
- 해당 값을 setContentView()안에 넣어주어 화면의 활성 뷰로 만든다.
binding.btn.setOnClickListenet { goMyPage() }
android { //이것저것다른것들 viewBinding { enable = true } }
private var _binding: MainFragmentBinding? = null private val binding get() = _binding!!
- 변수를 두 개 선언했는데, _binding은 이 프래그먼트가 사용되지 않았을 때 자원을 바로 반환해줄 수 있도록 준비해두는 역할이라고 한다.
- 실제로 코드상에서 요소들에 대해 접근할 때 사용할 변수는 binding이 되겠다.
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = MainFragmentBinding.inflate(inflater, container, false) val view = binding.root return view } override fun onDestroyView() { super.onDestroyView() _binding = null }
- _binding에 .inflate()하여 뷰바인딩 클래스를 생성해주면, binding에서 get()으로 해당 인스턴스를 가져가게 된다. 그래서 코드 상에서는 binding으로 각 요소에 접근할 수 있게 된다.
- inflate를 보면 activity에서의 사용법과 조금 다른 부분들이 있는데, inflate 안에 들어가는 매개변수들이다. 기본적인 패턴인 것 같으니 자주 쓰게 되면 외워두는 것이 좋겠다.
- onDestroyView()에서 뷰바인딩을 null로 두는 부분이 있다.
- 프래그먼트가 뷰보다 오래 지속되므로, 프래그먼트를 날릴 때 뷰바인딩에 대한 참조를 해제하는 것까지 해야 깔끔하게 정리가 될 것 같다.
[TIL-240403]