본문 바로가기
▶개발/Android

Binding Adapter(결합 어댑터) 예제를 통해 이해하기

by 브라더 준 2022. 4. 16.

(본 포스팅의 예제는 뷰모델, 데이터바인딩, 라이브데이터의 기본 개념을 숙지하여야 이해가 용이합니다.)


BindingAdapter는 Jetpack 라이브러리 기본 구성 요소인 "데이터 결합 라이브러리"의 내용 중 하나이다.
안드로이드 개발문서에 따른 공식 번역으로는 “결합 어댑터”라고 부르고 있는데, 결합 어댑터의 역할은 아래와 같다.

 

안드로이드 기본 프레임워크에서 적절한 프레임워크를 호출하여 값을 설정하는 작업을 담당한다.


문장이 이해하기가 어렵다. 쉽게 예를 들자면 우리는 텍스트뷰의 텍스트나, 색상 등의 속성값을 설정할 수 있는데, 여기서 bindingAdapter를 이용하면 속성값을 설정할 때에 우리가 명시적으로 원하는 로직을 덧붙여서 값을 설정 할 수도 있다는 말이다.

아래 예제들을 통해 실 사용과 효과, 목적에 대해서 들여다보자.


(예제 1) 텍스트 뷰에 노출되는 텍스트의 값에 따라 색상을 설정한다.

    /**
     * 텍스트 색상 분기 처리
     */
    @JvmStatic
    @BindingAdapter("binding:effect_text")
    fun setEffectText(view: TextView, txt: String?) {
        val txt = txt ?: return

        if (txt.startsWith("a")) {
            view.setTextColor(view.context.getColor(R.color.purple_700))

        } else {
            view.setTextColor(view.context.getColor(R.color.teal_700))

        } // end if

        view.text = txt

    }


bindingAdapter 어노테이션으로 xml단에서 사용할 이름을 설정한다. 그리고 bindingAdapter 메소드의 매개변수가 중요한데, 첫 번째 매개변수는 해당 속성과 연결된 뷰(xml에서 해당 커스텀 속성값이 적용될 뷰 유형)의 유형으로 설정해야 한다. 두 번째 매개변수는 xml에서 전달받는 값의 타입으로 설정한다.

[activity_first.xml]
<- 1. editText 값 입력 : 양방향 바인딩으로 입력값 뷰모델로 전달 ->
<- 2. 버튼 클릭        : editText로 입력받은 값을 텍스트뷰에 전달 ->
<- 3. 텍스트뷰 값 출력   : binding adapter로 만든 메소드를 통해 색상값이 적용된 텍스트 출력 ->

<layout 
	<data>
	  <variable
		name="model"
		type="com.swedio.bindingadapter.viewmodel.FirstViewModel" />

	</data>
		...(전략)...

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="40dp"
            binding:effect_text="@{model.confirmText}" />

        <EditText
            android:id="@+id/et_input"
            android:hint="입력창"
            android:text="@={model.typingText}"
            android:layout_width="300dp"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_confirm"
            android:text="확인"
            android:onClick="@{() -> model.onClickConfirmButton()}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
            
  		...(후략)...

</layout>



위의 예시를 보면 알겠지만, 일전까지는 액티비티나 프래그먼트와 같은 컨트롤러 단에서 텍스트에 입력되는 값을 직접 확인하고 뷰를 제어해야 했다. 하지만 binding adapter를 사용함으로써, 액티비티의 비대함을 줄일 수 있고 뷰와의 종속성을 줄여나갈 수 있다.


(예제 2) Glide 라이브러리를 통해 이미지 불러오기

/**
 * glide 라이브러리를 이용한 맞춤 메소드 설정
 * : imageUrl, error 2개 값이 모두 할당되어야 사용한다. (requireAll = true)
 */
@JvmStatic
@BindingAdapter("binding:imageUrl", "binding:error")
fun loadImage(imageView: ImageView, imageUrl: String, errorDrawable: Drawable) {
    Glide.with(imageView).load(imageUrl).error(errorDrawable).into(imageView)

}

/**
 * glide 라이브러리를 이용한 맞춤 메소드 설정
 * : imageUrl, error 2개 값 중에 1개 값이라도 들어온다면 사용된다. (requireAll = false)
 */
@JvmStatic
@BindingAdapter(value = ["binding:imageUrl", "binding:placeHolder"], requireAll = false)
fun loadImageRequireNonAll(imageView: ImageView, imageUrl: String?, placeHolder: Drawable?) {
    if (imageUrl == null) {
        // 이미지 url이 없다. placeHolder만 들어왔다.
        imageView.setImageDrawable(placeHolder)

    } else {
        // 이미지 url은 있다. 단, placeHolder가 없을 수도 있다. (둘 다 들어올 경우도 있다.)
        Glide.with(imageView.context).load(imageUrl).placeholder(placeHolder).error(placeHolder).into(imageView)

    } // end if

}


위 2번째 예제는 glide 라이브러리를 이용해서 이미지를 로드하는 binding adapter이다. 위의 1번 예제와 달리 매개변수가 2개 이상이다. 즉, 여러 개의 attributes를 전달받아 사용자가 원하는 메소드를 설정할 수 있음을 확인할 수 있다. 기본적으로 requireAll 값이 true로, 모든 attributes값이 할당되어야만 호출된다. 하나의 속성값만 들어오더라도 호출하고 싶다면 requireAll 값을 위의 예제와 같이 false로 설정하면 된다.


(예제 3) RecyclerView 아이템 바인딩 하기

: 본 예제에서는 Open api의 청약 상세정보 리스트를 호출받아 리스트로 구현했다. (관련 포스팅: https://markim94.tistory.com/162)

@JvmStatic
@BindingAdapter("binding:bind_list")
fun setBindList(recyclerView: RecyclerView, list: MutableList<AptItem>?) {
    val cyList = list ?: return
    val cyListAdapter = recyclerView.adapter as? CyListAdapter ?: return

    cyListAdapter.setAptListItem(cyList)

}

 

  뷰모델의 라이브데이터를 binding adapter를 통해서, 인자값으로 전달받았다. 전달받은 인자값은 해당 뷰(리사이클러뷰)의 어댑터 쪽으로 데이터를 셋팅하고 있다.

 

[activity_third.xml]

        <Button
            android:id="@+id/btn_search"
            android:onClick="@{() -> model.onClickSearchListButton()}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:text="리스트 불러오기" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_cy_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            binding:bind_list="@{model.cyListItems}" />
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView<ActivityThirdBinding?>(this, R.layout.activity_third).apply {
        model = viewModel
        lifecycleOwner = this@ThirdActivity
    }

    binding.rvCyList.apply {
        adapter = CyListAdapter()
        layoutManager = LinearLayoutManager(this@ThirdActivity)

    }

}

 

 예제1과 마찬가지로, binding adapter를 통해서 컨트롤러 단에서의 작업을 최소화했다. 이를 통해 서로 간의 종속성을 줄일 수 있음을 확인할 수 있다.

 



작업 소스 : https://github.com/markim94/Blog/tree/main/BindingAdapter

 

반응형