XML 기능 확장
Set method
나 @BindingMethod
를 써서 숨겨져있는 여러 함수나 프로퍼티를 간단하게 XML에서 사용할 수 있다
Model 부분에서 View에 대한 의존성을 제거
View -> Model로 변화를 받아야 할 때 유용하다 (2 way binding)
In XML...
<!-- main_activity.xml -->
<TextView
android:id="@+id/sample_text"
android:enabled="@{true}" />
<CustomView
android:id="@+id/sample_custom_view"
app:goneIf="@{false}"/>
// CustomView.kt
var goneIf: Boolean
// MainActivityBinding.java
this.sampleText.setEnabled(true);
this.sampleCustomView.setGoneIf(false);
xml에서 명시한 이름과 signature에 해당하는 함수를 찾아서 연결시켜줌
아주 간단한 수준의 바인딩
@BindingAdapter(
values = ["hello", "isUppercase"],
requireAll = false
)
public static void bindTextView(TextView textView, int val1, boolean val2) {
// 구현
}
같은 코드를 코틀린으로 하면...
(리시버를 활용하면 이 함수를 다른 곳에서도 유용하게 쓸 수 있음)
@BindingAdapter(
values = ["bind:hello", "isUppercase"],
requireAll = false
)
fun TextView.bindTextView(val1: Int, val2: Boolean) {
// 구현
}
여기서
@BindingAdapter
는 바인딩 어댑터를 선언하는 데 사용.
여기서 values가 이 바인딩 어댑터가 처리하는 attributes에 해당. (e.g. bind:hello, app:isUppercase
)
bind:hello
와 같이 namespace를 명시적으로 지정할 수도 있고, 지정하지 않으면 app
으로 설정됨.
함수의 첫번째 파라미터는 바인딩할 대상 View를 나타냄
언급한 것처럼 코틀린에서 Receiver로 활용할 수 있음.
함수의 나머지 파라미터는 어노테이션에서 지정한 각 values에 대응됨.
이 때 둘의 개수가 정확히 일치하지 않으면 에러가 발생함.
이름이나 함수 몸체 등은 중요하지 않음
모든 부분을 커버할 수 있는 유니버셜한 바인딩
조금 복잡함
@BindingMethods(
value = BindingMethod(
type = View::class,
attribute = "android:onClick",
method = "setOnClickListener"
)
)
Set method와 비슷하지만 attribute이름을 가지고 method를 찾는게 아니라
함수 이름을 따로 명시한다는게 다름
거의 쓸 일 없다
Model -> View가 Binding이라면
View -> Model이 InverseBinding임
@InverseBindingAdapter(attribute = "android:text", event = "textChanged")
fun TextView.getTextInverseBinding(): String {
return text.toString()
}
여기서 event
는 InverseBinding이 동작해야 하는 시점을 결정짓는다.
그런데 이 event를 발생시키기 위한 BindingAdapter가 하나 더 필요하다.
@BindingAdapter(value = ["textChanged"])
fun TextView.setTextChanged(inverseBindingListener: InverseBindingListener) {
addTextChangedListener(object : SimpleTextWatcher() {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
inverseBindingListener.onChange()
}
})
}
InverseBindingListener
는 안드로이드의 바인딩 시스템에게 값의 변화를 알리고, 그 결과 textChanged라는 이벤트가 발생하게 되어서 InverseBinding이 동작하는 것.
그런데 이렇게 바인딩을 만들면,
textChanged
이벤트를 발생시킴이렇게 무한 반복이 일어나는데.. 이걸 막기 위해서는 다음과 같이 값을 비교하는 로직을 BindingAdapter
쪽에 작성해주어야 함
@BindingAdapter(value = ["android:text"])
fun TextView.setTextPreventOverwrite(text: String) {
if (isSame(this.text.toString(), text)) return;
setText(text)
}
아주 조심해야 한다..