본 시리즈는 안드로이드 아키텍처를 이해하기 위해 Android 공식문서를 읽고 정리한 글의 모음입니다.
본문에서의 예제는 공식문서내의 예제를 대부분 사용합니다.
공부하면서 작성하는 글이기 때문에 내용상 오류가 있을 가능성이 매우 높습니다.
https://developer.android.com/topic/libraries/view-binding
뷰 바인딩은 view와 상호작용하는 코드를 더욱 쉽게 작성할 수 있도록 도와주는 기능입니다.
잘 와닿지가 않은데, xml파일을 보면 각각의 id를 가진 여러 view들이 그 파일을 이루는 것을 볼 수 있습니다.
예전에 java로 안드로이드 공부할 때 findViewById()
를 사용하여 각 view component들을 객체로 만들어 사용했었던 기억이 납니다. 비로서 이런 과정을 거쳐야 코드단에서 view를 컨트롤 할 수 있었기 때문이죠. 코틀린에서도 마찬가지입니다. (참고로, Kotlin Android Extension에서는 findViewById()를 사용하지 않아도 됩니다. synthetic binding을 지원하기 때문입니다.)
// java
TextView textView = (TextView) findViewById(R.id.tv_first)
// kotlin
val textView = findViewById<TextView>(R.id.tv_first)
위와 같은 방법에는 몇 가지 문제점(단점)이 존재합니다.
위의 문제점들은 안드로이드 앱 개발자들에게 상당한 골칫거리였다고 합니다. (저는 경험 안해봤네요)
만약 레이아웃의 컴포넌트를 객체화 시키지 않고 바로 사용할 수 있다면, 불필요한 코드 작업을 덜 할 수 있지 않을까요? 그런 생각으로 도입 된 것이 ViewBinding
입니다.
Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout. In most cases, view binding replaces findViewById.
View Binding은 모듈별로 사용 설정 됩니다. 사용 설정이 됐다면 각 xml 파일에 대해 Binding Class
를 자동 생성합니다. Binding Class의 인스턴스는 ID를 가지고 있는 모든 뷰에 대한 직접적 참조를 포함
합니다. 그에 따라 findViewById를 사용하지 않고 대체 할 수 있다는 뜻입니다.
Binding Class에 존재하는 참조를 코드단에서 바로 사용하여 불필요한 findViewById를 사용하지 않는다는 이야기가 됩니다.
(코틀린은 BindingClass의 생성 없이 layout의 id값을 참조하여 view를 사용합니다. 이것은 ViewBinding과 다른 관점에서 봐야하며, 이에 대한 비교 포스팅은 추후에 하도록 하겠습니다.)
module level의 build.gradle
에서 viewBinding
을 허용해줍니다.
android {
...
buildFeatures {
viewBinding true
}
}
추가적으로, viewBinding은 앞서 언급했듯 모듈단에서 전체 레이아웃을 대상으로 이루어지므로, 원하지 않는 layout에 대해서는 제한을 두어야합니다.
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
ViewBinding Class는 다음의 규칙을 가지며 생성됩니다.
Binding
단어가 클래스의 이름 끝에 붙습니다.getRoot()
메서드가 자동 포함됩니다. 이 메서드는 레이아웃 파일의 루트 뷰에 관한 직접 참조를 제공합니다.공식문서의 예제를 예로 들겠습니다.
아래의 코드는 result_profile.xml
레이아웃 파일입니다.
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
위의 규칙에 따라 생성되는 ViewBinding Class의 이름은 ResultProfileBinding
이 됩니다.
또한, ImageView에 대한 id가 존재하지 않으므로, 그에 따른 참조는 없습니다.
자동 생성되는 getRoot()
메서드의 반환값은 부모 뷰인 LinearLayout이 됩니다.
Activity에서 사용할 BindingClass의 인스턴스를 설정하기 위해 onCreate()
메서드에서 다음 과정을 거칩니다.
inflate()
를 호출하여 인스턴스 생성getRoot()
를 사용하여 루트 뷰의 참조를 획득setContentView()
에 루트 뷰를 전달하여 화면상의 활성 뷰로 만들어 줌private lateinit var binding: ResultProfileBinding
// onCreate()에서 작업
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
// 1. inflate 호출하여 인스턴스 생성
binding = ResultProfileBinding.inflate(layoutInflater)
// 2. getRoot()
val view = binding.root
// 3. 화면상의 활성 뷰로 만든다.
setContentView(view)
}
이제, BindingClass의 인스턴스를 사용하여 View를 참조할 수 있습니다.
// name의 id를 가진 TextView에 setText() 함수 사용
binding.name.text = viewModel.name
// button의 id를 가진 Button에 ClickListener 설정
binding.button.setOnClickListener { viewModel.userClicked() }
Fragment에서 사용할 BindingClass의 인스턴스를 설정하기 위해 onCreateView()
메서드에서 다음 과정을 거칩니다.
inflate()
를 호출하여 인스턴스 생성getRoot()
를 사용하여 루트 뷰의 참조를 획득onCreateView()
에서 루트 뷰를 반환하여 화면상의 활성 뷰로 만들어 줌private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
// onCreateView()에서 작업
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 1. inflate()
_binding = ResultProfileBinding.inflate(inflater, container, false)
// 2. getRoot()
val view = binding.root
// 3. 부모 뷰 리턴
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
이제, BindingClass의 인스턴스를 사용하여 View를 참조할 수 있습니다.
// name의 id를 가진 TextView에 setText() 함수 사용
binding.name.text = viewModel.name
// button의 id를 가진 Button에 ClickListener 설정
binding.button.setOnClickListener { viewModel.userClicked() }
성능적인 특성으로 보았을 때 findViewById와 다른점이 아래와 같이 존재합니다.
이러한 차이점들이 나타내는 바는 layout과 코드단의 비호환성에 대한 검사가 compile 단계에서 이루어진다는 것입니다.
(DataBinding에 대한 포스팅은 뒤이어 할 것 같습니다.)
ViewBinding과 DataBinding은 모두 뷰를 직접 참조하는데 사용할 수 있는 BindingClass를 제공합니다. ViewBinding은 보다 단순한 경우를 처리하기 위함에 적합하며 DataBinding과 비교했을 때 다음과 같은 이점을 제시합니다.
하지만, DataBinding과 비교했을 때의 불리함 역시 존재합니다.
표현식 혹은 변수
를 제공하지 않으므로 동적인 UI 컨텐츠를 생성할 수 없습니다.