DataBinding :: Organize about annotation types

WindSekirun (wind.seo)·2022년 4월 26일
0

이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.

작성 시점: 2018-07-06

도입

데이터바인딩을 도입하다보면 자주 접하는 어노테이션이 있다. BindingAdapter , BindingConversion, InverseBindingAdapter, BindingMethods 이다. InverseBindingAdapter 는 양방향 데이터바인딩 글 (https://blog.uzuki.live/databinding-two-way-databinding-with-custom-view/) 에서 설명한 기억이 있고, 나머지도 한번씩은 언급했던 것 같지만 이 참에 정리를 해두려고 한다.

BindingAdapter

Set bold to TextView in DataBinding (https://blog.uzuki.live/set-bold-to-textview-in-databinding/) 글에서도 언급이 되었던 어노테이션인데, 특정 뷰에 xml 속성을 추가하는 기능을 한다.

@BindingAdapter("android:textStyle")
public static void setTypeface(TextView v, String style) {
    switch (style) {
        case "bold":
            v.setTypeface(null, Typeface.BOLD);
            break;
        default:
            v.setTypeface(null, Typeface.NORMAL);
            break;
    }
}

주로 첫 번째에 적용할 뷰, 두 번째 파라미터부터는 값을 넣는다. 첫 번째 파라미터에 들어가는 뷰는 해당 타입의 하위 클래스도 같이 해당된다. 예를 들면, TextView 를 상속하는 EditText 도 이 속성을 사용할 수 있다.

@BindingAdapter(value = {"minValue", "maxValue"}, requireAll = false)
public void bindMultiSliderMinValue(MultiSlider slider, int min, int max) {
    slider.setMin(min);
    slider.setMax(max);
}

값을 두 개 이상 넣을 때에는 value 를 string-array 로 만들고 각각 속성을 적어주면 된다. 추가로 나와있는 requireAll 의 경우에는 선택 파라미터(Optional Parameter) 로 기본값은 true 로 되어있다. 즉, requireAll 가 true 이면 minValue, maxValue 가 각각 있어야 된다는 의미이고, false 는 반대로 둘 중 하나만 있어도 된다는 뜻이다.

@BindingAdapter(value = "onEditorAction")
public void bindEditorAction(EditText editText, OnEditActionListener onEditActionListener) {
    editText.setOnEditorActionListener(onEditActionListener::onEditorAction);
}

public interface OnEditActionListener {
    boolean onEditorAction(TextView view, int actionId, KeyEvent event);
}

파라미터로 리스너를 추가할 수 있는데, interface 를 정의해놓고 파라미터에 넣으면 된다.

기본적으로 속성의 경우 app 네임스페이스가 붙어 app:minValue, app:maxValue 로 활용이 가능한데, 속성 자체를 android:maxValue 로 적어주면 android:maxValue 로 사용이 가능하다. 주로 안드로이드 기본 컴포넌트를 이용할 때 사용한다.

그리고, BindingAdapter 의 메서드는 static 로 선언하는 것이 기본적이다. static 로 선언하지 않을 수도 있지만 이 경우에는 BindingComponent 라는 클래스를 만들고 이 BindingComponentDataBindingUtils.setDefaultComponent 로 설정해주어야 한다.

public class BindingComponent implements DataBindingComponent {
    private final BindAdapter mAdapter;

    public BindingComponent(Context context) {
        this.mAdapter = new BindAdapter(context);
    }

    public static BindingComponent create(Context context) {
        return new BindingComponent(context);
    }

    @Override
    public BindAdapter getBindAdapter() {
        return mAdapter;
    }
}

이 조건이 문제가 되는 곳이 바로 Kotlin 으로 구성할 때 인데, 반드시 companion object 안이 아닌 해당 파일 자체를 object 로 선언하고 각 메서드에 @JvmStatic 어노테이션을 달아주어야 한다. 실제로 BindingAdpater 는 아래와 같이 적용된다.

com.github.windsekirun.demoapp.binding.BindAdapter.setTypeface(this.mboundView7, viewModelMDataSourceInt1JavaLangStringBoldJavaLangStringNormal);

BindingConversion

BindingAdapter 가 View 에 속성을 추가하는 것이라면, BindingConversion 는 이름에서 알 수 있다 싶이 변환하는 역할을 한다. 주의할 점은 적용될 뷰를 선택할 수 있는 BindingAdapter 와 달리 BindingConversion 는 전역적으로 적용된다. 예를 들어, boolean 을 View.VISIBLE 와 View.GONE 로 바꾸는 기능을 만들었다면 이는 View.VISIBLE 와 View.GONE 에만 사용할 수 있다. View.INVISIBLE 는 사용하지 못한다.

@BindingConversion
public static int convertBooleanToVisibility(boolean visible) {
    return visible ? View.VISIBLE : View.GONE;
}

이를 사용하면 기존에는 android:visibility="@{viewModel.mVisibleList ? View.VISIBLE : View.GONE}" 라고 적어야 될 것을, android:visibility="@{viewModel.mVisibleList}" 로 바꿀 수 있다. 다만, 위에도 말한듯이 INVISIBLE 는 기존처럼 적어야 한다. 실제로 BindingConversion `는 다음과 같이 적용된다.`

this.mboundView0.setVisibility(com.github.windsekirun.demoapp.binding.BindConversion.convertBooleanToVisibility(adapterIsVisibleDatePositionData));

InverseBindingAdapter

Inverse(=역) BindingAdapter, 즉 BindingAdapter> 가 ViewModel -> XML 를 위해 xml 속성을 추가하는 것이라면, InverseBindingAdapter 는 XML 에서의 변경값을 ViewModel 에 역으로 적용하는 역할을 한다. InverseBindingAdapter 어노테이션 자체에는 두 가지 파라미터가 들어가는데, 첫 번째 attribute는 속성을 적고, 두 번째 event 는 attribute 이름에 AttrChanged 를 붙인 것을 적는다.

@InverseBindingAdapter(attribute = "android:currentPage", event = "android:currentPageAttrChanged")
public static int getCurrentPage(ViewPager pager) {
    return pager.getCurrentItem();
}

자세한 설명은 DataBinding – Two-way Databinding with Custom View(https://blog.uzuki.live/databinding-two-way-databinding-with-custom-view/) 를 참고하는 것이 좋을 것 같다.

BindingMethods

기능은 BindingAdapter 와 같은데, 아래와 같은 BindingAdapter 를 대체할 수 있다.

@BindingAdapter("hintText")
public static void setInputHintText(InputView inputView, String hintText) {
    inputView.setHintText(hintText);
}

@BindingMethods({
        @BindingMethod(type = InputView.class, attribute = "hintText", method = "setHintText")
})

즉, 단순한 형태의 BindingAdapterBindingMethod 로 간단히 대체할 수 있는 것이다. 이 BindingMethods 어노테이션은 클래스에 붙이면 된다. 실제로는 다음과 같이 사용된다.

this.mboundView2.setHintText(viewModelHintText);

기존 다른 코드와는 다르게 해당 메서드를 단순히 실행시키는 것 처럼 생성된다. BinidingMethods 와 같은 행동을 하는 InverseBindingMethods 가 있는데, 이는 단순한 형태의 InverseBindingAdapter 를 대체하는 것이다. InverseBindingMethods 는 다음 링크를 참조하면 된다. https://android.googlesource.com/platform/frameworks/data-binding/+/master/extensions/baseAdapters/src/main/java/android/databinding/adapters/CompoundButtonBindingAdapter.java

profile
Android Developer @kakaobank

0개의 댓글