DataBinding
DataBinding이란?
- 코틀린에서 레이아웃을, 레이아웃에서 코틀린의 데이터를 직접 참조하는 라이브러리
- 코틀린에서 레이아웃 파일에 의존적인 부분이 많이 사라지고 MVVM등의 패턴과 함께 사용된다
One-way binding
- 데이터의 흐름이 단방향을 말한다
Two-way binding
- 데이터의 흐름이 양방향을 말한다
기본 사용법
-
Databinding에서 사용되는 레이아웃 파일들의 Root태그는
<layout>
이되어야 한다1 2 3 4
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" ...> .... </layout>
-
어떤 객체를 참조하여 데이터를 출력할 지
<data>
를 정의해 주어야 한다1 2 3
<data> <variable name="user" type="com.example.User"/> </data>
-
데이터를 선언한 후
@{user.firstName}
처럼 데이터를 View에 참조하도록 연결할 수 있다1 2 3 4
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
이벤트 처리
-
메소드 참조
- 데이터가 바인딩될 때 생성된다
- 핸들러에 이벤트를 할당하려면 호출할 메서드 이름이 될 값을 사용해야 한다
1 2 3
class MyHandlers { fun onClickFriend(view: View) { ... } }
1 2 3 4 5
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:onClick="@{handlers::onClickFriend}"/>
- 바인딩 하려는 핸들러 메소드는 해당 이벤트 클릭 리스너의 메소드 형태와 동일해야 한다
-
리스너를 바인딩
- 이벤트가 발생할 때 실행된다
- 리턴 값만 리스너의 예상 리턴 값과 일치하면 된다
1 2 3
class Presenter { fun onSaveClick(task: Task){} }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="task" type="com.android.example.Task" /> <variable name="presenter" type="com.android.example.Presenter" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{() -> presenter.onSaveClick(task)}" /> </LinearLayout> </layout>
- 위의 예에서는
onClick(VIew)
에 전달되는 view 매개변수가 정의되지 않았다 - 리스너 결합에서는 두 가지 방식으로 리스너 매개변수를 선택할 수 있다
- 모든 매개변수를 무시하거나, 모든 매개 변수의 이름을 지정할 수 있다
1 2 3
class Presenter { fun onSaveClick(view: View, task: Task){} }
1
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
1
2
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
레이아웃 세부 정보
-
Import
- import를 사용하면 레이아웃 파일 내에서 클래스를 쉽게 참조 할 수 있다
1 2 3 4 5 6 7 8
<data> <import type="android.view.View"/> </data> <TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
- View 클래스를 가져오면 View 클래스의 VISIBLE 및 GONE 상수를 참조할 수 있다
-
alias
- 클래스 이름 충돌이 발생하면 클래스 중 하나의 이름을 별칭으로 바꿀 수 있다
1 2 3
<import type="android.view.View"/> <import type="com.example.real.estate.View" alias="Vista"/>
-
Variable
1 2 3 4 5 6
<data> <import type="android.graphics.drawable.Drawable"/> <variable name="user" type="com.example.User"/> <variable name="image" type="Drawable"/> <variable name="note" type="String"/> </data>
<data>
안에 선언되는 variable 은 빌드 시 binding 클래스가 생성 되면서 각각의 setter/getter 메소드를 갖게 된다- 이때 setter 가 호출 되기 전 기본 값은 참조 형식의 variable 은 null, int 의 경우 0, boolean 의 경우 false 를 갖는다.
- 만약 참조하려는 variable 이 null일 경우 NullpointerException 을 방지 하기 위해 위와 같은 기본 값이 return 된다
"@{user.age}"
일 때 user 가 null 인 경우 age(Int) 의 기본 값인 0 이 할당되게 된다.
-
Include
- 레이아웃 파일에서 include 태그를 사용하면 include 하려는 레이아웃 파일로 변수를 전달할 수 있다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </LinearLayout> </layout>
-
include 한 name.xml 과 contact.xml 에 bind:variable 속성을 통해 user 변수를 전달
-
이렇게 사용하기 위해서는 전달 받고자 하는 레이아웃 파일에서 같은 이름의 변수가 선언 되어있어야 한다
데이터 객체
- 기본적으로는 databinding 에서 임의의 데이터 객체를 사용하여 레이아웃과 바인딩을 구현 할 수 있지만 바인딩 하고 있는 객체의 값이 변경 되어도 UI 가 업데이트 되진 않는다
- 데이터가 변경 되었을 때 이를 알려주는 기능을 데이터 객체에 부여하면 databinding 의 장점을 극대화 시킬 수 있다
Observable 객체
-
바인딩 하려는 객체에 Observable 인터페이스를 구현하면 해당 객체에 단일 리스너를 연결하여 그 객체에 모든 속성의 변경사항을 수신할 수 있게 된다
-
BaseObservable 클래스를 통해서 리스너 등록 매커니즘을 구현한다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class LoginViewModel : BaseObservable { @Bindable fun getRememberMe(): Boolean { return data.rememberMe } fun setRememberMe(value: Boolean) { if (data.rememberMe != value) { data.rememberMe = value saveData() notifyPropertyChanged(BR.remember_me) } } }
- Bindable 어노테이션을 통해서 변화를 감지하고자 하는 getter위에 사용하고
- notiftyPropertyChanged()는 setter에 사용한다
Observable Fields
- 데이터바인링 라이브러리에서는 각 필드단위로 Observable를 구현할 수 있는 fields를 제공한다
1 2 3 4 5 6 7 8 9 10
ObservableField<T> ObservableBoolean ObservableByte ObservableChar ObservableShort ObservableInt ObservableLong ObservableFloat ObservableDouble ObservableParcelable
1 2 3 4 5 6 7 8 9 10
// 구현하려는 필드에 선언해서 사용할 수 있다 class User { val firstName = ObservableField<String>() val lastName = ObservableField<String>() val age = ObservableInt() } // 값에 엑세스 하려면 set(), get() 메소드를 사용한다 user.firstName = "Google" val age = user.age
생성되는 바인딩 클래스
- 컴파일 단계에서 자동으로 바인딩 클래스가 생성 된다.
- 레이아웃 파일 이름을 기준으로 생성되며 생성되는 바인딩 클래스들은 모두 android.databinding.ViewDataBinding 클래스를 확장한다
Binding생성
-
레이아웃에 바인딩하는 방법은 여러가지가 존재하지만 가장 일반적인 방법은 binding class의 정적 메소드를 사용하는 것이다
-
inflate()를 사용하면 View 계층을 확장함과 동시에 data binding이 이루어진다
1 2
val binding : MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater) val binding : MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false)
-
바인딩클래스를 미리 알수 없을 때에는 DataBindingUtil 클래스를 사용해서 바인딩을 생성한다
1 2
val viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent) val binding: ViewDataBinding? = DataBindingUtil.bind(viewRoot)
Id가 있는 view에 대한 binding
-
databinding 은 id 가 있는 view 에 대해서는 자동으로 해당 view 에 대한 필드를 생성하여 findViewById 를 사용하지 않아도 view 에 바로 엑세스 할 수 있다.
-
kotlin 의 경우 kotlin-extension 을 통해 layout id 로 생성 되어지는 view 에 바로 엑세스가 가능한 맥락과 유사한 내용이다
Variable
-
레이아웃에 binding 을 위해 선언한 변수들에 대해서 binding class 에서는 setter/getter 메소드를 제공 한다
1 2 3 4 5 6
<data> <import type="android.graphics.drawable.Drawable"/> <variable name="user" type="com.example.User"/> <variable name="image" type="Drawable"/> <variable name="note" type="String"/> </data>
ViewStub
- ViewStub 은 최초에는 레이아웃 프로세스에 포함되지 않고 보이지 않는 상태로 존재하다가, 호출하는 시점에 view 계층에 포함되어지는 특수한 View 이다
- 복잡하게 구성된 레이아웃을 빠르게 전개시켜야하는 상황에서, 레이아웃의 전개 시기를 선택적으로 늦출 수 있다
1
2
3
4
5
6
<ViewStub
android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dp"
android:layout_height="40dp" />
1
2
3
4
5
// 생성되는 바인딩 클래스에서 ViewStub은 ViewStubProxy로 표현된다
val viewStubProxy = binding.stub;
val viewStub = viewStubProxy.getViewStub();
viewStub.inflate()
viewStub.setVisibility(View.VISIBLE)
객체 반환
자동 객체 형 반환
-
바인딩 식에서 객체가 리턴 될때, 데이터바인딩 라이브러리 내부에서 속성에 값을 설정할 적절한 메소드를 선택하게 된다
-
객체는 선택 된 메소드의 매개변수 타입으로 캐스팅 되는데, 이것은 ObservableMap 을 사용하여 데이터를 관리할 경우 편리하다
1 2 3 4 5 6 7 8 9 10
<layout> <data> <import type="android.databinding.ObservableMap" /> <variable name="map" type="ObservableMap<String,Object>" /> </data> .. <TextView .... android:text="@{map[`firstName`]}" /> </layout>
1 2 3 4 5 6
//MainActivity.kt val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.map = ObservableArrayMap<String, Any>().apply { put("firstName", "hong") put("age", 20) }
- 객체 타입이 불확실할 경우 별도의 캐스팅 동작을 바인딩 식에 적절히 추가해 주어야 한다
1
2
3
<TextView
....
android:text="@{String.valueOf(map[`firstName`])}" />
양방향 데이터 바인딩
-
단방향 데이터 바인딩을 사용하면 속성에 값을 설정하고 속성의 변경에 반응하는 리스너를 설정할 수 있다
1 2 3 4 5 6 7 8 9 10 11
<!-- 단방향 데이터 바인딩 --> <CheckBox android:id="@+id/rememberMeCheckBox" android:checked="@{viewmodel.rememberMe}" android:onCheckedChanged="@{viewmodel.rememberMeChanged}" /> <!-- 양방향 데이터 바인딩 --> <CheckBox android:id="@+id/rememberMeCheckBox" android:checked="@={viewmodel.rememberMe}" />
-
=
기호가 포함뵌@={}
표기법은 속성과 관련된 데이터 변경사항을 받는 동시에 사용자 업데이트를 수신한다
양방향 데이터 바인딩을 지원하는 속성
클래스 | 속성 | 결합 어댑터 |
---|---|---|
AdapterView | android:selectedItemPositio android:selection |
AdapterViewAdapter |
CalendarView | android:date | CalendarViewBindingAdapter |
CompoundButton | android:checked | CompoundButtonBindingAdapter |
DatePicker | android:year android:month android:day |
DatePickerBindingAdapter |
NumberPicker | android:value | NumberPickerBindingAdapter |
RadioButton | android:checkedButton | RadioGroupoBindingAdapter |
RatingBar | android:rating | RatingBarBindingAdapter |
SeekBar | android:progress | SeekBarBindingAdapter |
TabHost | android:currentTab | TabHostBindingAdapter |
TextView | android:text | TextViewBindingAdapter |
TimePicker | android:hour android:minute |
TimePickerBindingAdapter |