결합 어댑터는 적절한 프레임워크를 호출하여 값을 설정하는 작업을 담당합니다. 한 가지 예로 setText()
메서드를 호출하는 것과 같이 속성 값을 설정하는 작업을 들 수 있습니다. 또 다른 예로는 setOnClickListener()
메서드를 호출하는 것과 같이 이벤트 리스너를 설정하는 작업이 있습니다.
데이터 결합 라이브러리를 사용하면 값을 설정하기 위해 호출되는 메서드를 지정하고 고유한 결합 로직을 제공하며 어댑터를 사용함으로써 반환된 객체의 유형을 지정할 수 있습니다.
속성 값 설정
결합된 값이 변경될 때마다 생성된 결합 클래스는 결합 표현식을 사용하여 뷰에서 setter 메서드를 호출해야 합니다. 데이터 결합 라이브러리에서 메서드를 자동으로 결정하거나 메서드를 명시적으로 선언하거나 맞춤 로직을 제공해 메서드를 선택하도록 허용할 수 있습니다.
자동 메서드 선택
이름이 example
인 속성의 경우 라이브러리는 호환 가능한 유형을 인수로 허용하는 setExample(arg)
메서드를 자동으로 찾으려고 합니다. 속성의 네임스페이스는 고려되지 않으며 메서드 검색 시 속성 이름 및 유형만 사용됩니다.
예를 들어 android:text="@{user.name}"
표현식이 있는 경우 라이브러리는 user.getName()
에서 반환한 유형을 허용하는 setText(arg)
메서드를 찾습니다. user.getName()
의 반환 유형이 String
이면 라이브러리는 String
인수를 허용하는 setText()
메서드를 찾습니다. 표현식이 int
를 대신 반환하면 라이브러리는 int
인수를 허용하는 setText()
메서드를 검색합니다. 표현식은 올바른 유형을 반환해야 합니다. 필요하다면 반환 값을 변환할 수 있습니다.
지정된 이름의 속성이 없더라도 데이터 결합은 작동합니다. 그때는 데이터 결합을 사용하여 setter에 필요한 속성을 생성할 수 있습니다. 예를 들어 지원 클래스 DrawerLayout
에는 어떤 속성도 없지만 많은 setter가 있습니다. 다음 레이아웃은 자동으로 setScrimColor(int)
메서드와 setDrawerListener(DrawerListener)
메서드를 각각 app:scrimColor
속성과 app:drawerListener
속성의 setter로 사용합니다.
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
맞춤 메서드 이름 지정
일부 속성에는 이름이 일치하지 않는 setter가 있습니다. 이러한 상황에서 속성은 BindingMethods
주석을 사용하여 setter와 연결될 수도 있습니다. 주석은 클래스와 함께 사용되며 이름이 바뀐 각 메서드에 하나씩 여러 BindingMethod
주석을 포함할 수 있습니다. 결합 메서드는 앱의 어떤 클래스에도 추가할 수 있는 주석입니다. 다음 예에서 android:tint
속성은 setTint()
메서드가 아닌 setImageTintList(ColorStateList)
메서드와 연결됩니다.
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
자바
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
일반적으로 Android 프레임워크 클래스에서 setter의 이름을 바꿀 필요가 없습니다. 이름 규칙을 사용하여 일치하는 메서드를 자동으로 찾는 속성이 이미 구현되었습니다.
맞춤 로직 제공
일부 속성에는 맞춤 결합 로직이 필요합니다. 예를 들어, android:paddingLeft
속성에는 연결된 setter가 없습니다. 대신 setPadding(left,
top, right, bottom)
메서드가 제공됩니다. BindingAdapter
주석이 있는 정적 결합 어댑터 메서드를 사용하면 속성의 setter가 호출되는 방식을 맞춤설정할 수 있습니다.
Android 프레임워크 클래스의 속성에는 BindingAdapter
주석이 이미 생성되어 있습니다. 예를 들어 다음 예는 paddingLeft
속성의 결합 어댑터를 보여줍니다.
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
자바
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
매개변수 유형은 중요합니다. 첫 번째 매개변수는 속성과 연결된 뷰의 유형을 결정합니다. 두 번째 매개변수는 지정된 속성의 결합 표현식에서 허용되는 유형을 결정합니다.
결합 어댑터는 다른 유형의 맞춤설정에 유용합니다. 예를 들어 맞춤 로더는 작업자 스레드에서 호출되어 이미지를 로드할 수 있습니다.
개발자가 정의하는 결합 어댑터는 충돌이 발생하면 Android 프레임워크에서 제공하는 기본 어댑터보다 우선 적용됩니다.
또한 다음 예에서와 같이 여러 속성을 받는 어댑터도 있을 수 있습니다.
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
자바
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
다음 예에서와 같이 레이아웃에서 어댑터를 사용할 수 있습니다. 여기서 @drawable/venueError
는 앱의 리소스를 나타냅니다. 리소스를 @{}
로 묶으면 유효한 결합 표현식이 됩니다.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
imageUrl
과 error
가 모두 ImageView
객체에 사용되며 imageUrl
은 문자열이고 error
는 Drawable
이면 어댑터가 호출됩니다. 속성의 하나라도 설정될 때 어댑터가 호출되도록 하려면 다음 예에서와 같이 어댑터의 선택적 requireAll
플래그를 false
로 설정할 수 있습니다.
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
자바
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
결합 어댑터 메서드는 선택적으로 핸들러의 이전 값을 사용할 수 있습니다. 이전 값과 새 값을 사용하는 메서드는 아래 예에서와 같이 속성의 모든 이전 값을 먼저 선언한 후 새 값을 선언해야 합니다.
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
자바
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); } }
이벤트 핸들러는 다음 예에서와 같이 하나의 추상 메서드가 있는 인터페이스 또는 추상 클래스에서만 사용할 수 있습니다.
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
자바
@BindingAdapter("android:onLayoutChange") public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } } }
다음과 같이 레이아웃에서 이 이벤트 핸들러를 사용할 수 있습니다.
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
리스너에 여러 메서드가 있으면 여러 리스너로 분할해야 합니다.
예를 들어 View.OnAttachStateChangeListener
에는 onViewAttachedToWindow(View)
와 onViewDetachedFromWindow(View)
의 두 메서드가 있습니다. 라이브러리는 2개의 인터페이스를 제공하여 이러한 메서드의 속성 및 핸들러를 구별합니다.
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
자바
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
하나의 리스너를 변경하면 다른 리스너에도 영향을 줄 수 있으므로 어느 한 속성 또는 둘 다에서 작동하는 어댑터가 필요합니다. 다음 예와 같이 주석에서 requireAll
을 false
로 설정하여 모든 속성에 결합 표현식을 할당할 필요는 없다는 것을 지정할 수 있습니다.
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
자바
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } } }
위의 예는 View
클래스가 OnAttachStateChangeListener
에 setter 메서드 대신 addOnAttachStateChangeListener()
메서드와 removeOnAttachStateChangeListener()
메서드를 사용하기 때문에 일반적인 예보다 약간 더 복잡합니다. android.databinding.adapters.ListenerUtil
클래스는 결합 어댑터에서 이전의 리스너를 삭제할 수 있도록 이 리스너를 계속 추적하는 데 도움이 됩니다.
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
로 OnViewDetachedFromWindow
및 OnViewAttachedToWindow
인터페이스에 주석을 지정하면 데이터 결합 코드 생성기는 addOnAttachStateChangeListener()
메서드에서 지원되는 것과 동일한 버전인 Android 3.1(API 수준 12) 이상에서 실행될 때만 리스너가 생성되어야 함을 인식하게 됩니다.
객체 변환
자동 객체 변환
결합 표현식에서 Object
가 반환되면 라이브러리는 속성 값을 설정하는 데 사용되는 메서드를 선택합니다. Object
는 선택한 메서드의 매개변수 유형으로 변환됩니다. 이 동작은 다음 예와 같이 ObservableMap
클래스를 사용하여 데이터를 저장하는 앱에서 유용합니다.
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
표현식의 userMap
객체는 값을 반환하며 이 값은 android:text
속성의 값을 설정하는 데 사용되는 setText(CharSequence)
메서드에서 볼 수 있는 매개변수 유형으로 자동으로 변환됩니다. 매개변수 유형이 불명확하면 표현식에서 반환 유형을 변환해야 합니다.
맞춤 변환
어떤 상황에서는 특정 유형 간에 맞춤 변환이 필요합니다. 뷰의 android:background
속성에 Drawable
이 필요하지만 지정된 color
값이 정수인 상황을 예로 들 수 있습니다. 다음 예는 Drawable
이 필요한데 정수가 대신 지정된 속성을 보여줍니다.
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Drawable
이 실행되고 정수가 반환될 때마다 int
가 ColorDrawable
로 변환되어야 합니다. 다음과 같이 BindingConversion
주석이 있는 정적 메서드를 사용하여 변환을 실행할 수 있습니다.
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
자바
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
그러나 결합 표현식에 지정하는 값 유형은 일관되어야 합니다. 다음 예에서와 같이 동일한 표현식에 서로 다른 유형을 사용할 수 없습니다.
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
추가 리소스
데이터 결합에 관해 자세히 알아보려면 다음 추가 리소스를 참조하세요.