DataBinding)@BindingAdapter사용하기(이미지/리사이클러뷰)

소정·2023년 5월 31일
0

Kotlin

목록 보기
20/27

💡 @BindingAdapter란?

  • 자가 원하는 속성을 직접 만들 수 있는 문법
  • 뷰들에 없는 새로운 xml 속성을 연결하는 기능 메소드를 가지는 객체
  • static 메소드를 가진 object로 명시
  • 싱글턴 패턴 (객체가 단 한마리 밖에 생성되지 않는 것!)
  • 사용하려면 빌드그래이들에 어노테이션 해독기 등록 해야함

    To use data binding annotations in Kotlin, apply the 'kotlin-kapt' plugin in your module's build.gradle

[1] 서버에 있는 Url 데이터와 바인딩하는 이미지뷰

  • android:src는 res폴더 안에 있는 이미지만 보여줄 수 있다
  • 대부분의 이미지는 서버에 있는 경우가 많기에 src속성 사용 불가
  • data binding하려면 xml 속성으로 변수와 연결해야 함
  • 하지만 ImageView에는 URL을 설정하는 속성이 기본적으로 제공되지 않음
  • 개발자가 원하는 속성을 직접 만들 수 있는 문법 : @BindingAdapter
  • 바인딩아답터를 설정하는 클래스 정의하여 사용한다

0. 화면 데이터 바인딩 준비

1. 그래이들에 추가

2. main.xml

데이터 바인디이 될 수 있도록 layout으로 감싼 뒤 data영역과 뷰 영역을 만든다

<?xml version="1.0" encoding="utf-8"?>

<layout>
    <!-- 1. 레이아웃 xml 에서 사용할 데이터 변수   -->
    <data>
        
        
    </data>


    <!-- 1. 레이아웃 영역   -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".MainActivity">

        

    </LinearLayout>

</layout>

3. main.kt에서 화면 연동

3.1) MyViewModel

  • 앱의 데이터를 주관하는 클래스
  • 변수의 데이터 변경을 관촬할 수 있는 ObservableXXX 로 정의한다
package com.bsj0420.ex98databindingbindingadapter

import androidx.databinding.ObservableField
import java.util.Date

class MyViewModel {

    // 1. 이미지 url 변수 [관촬 가능한 타입 : ObservableXXX]
    var img : ObservableField<String> = ObservableField("https://cdn.pixabay.com/photo/2023/05/21/12/40/dog-8008483_1280.jpg") //초기화


    //1.1) 버튼 클릭 시 이벤트 콜백에서 호출하는 메소드
    fun changeImage() {
        img.set("https://cdn.pixabay.com/animation/2023/05/16/14/37/14-37-11-404_512.gif")
    }

}

3.2) main.kt

  • 데이터 바인딩유틸을 통해 레이아웃 연동
  • 바인딩과 뷰모델 연결
package com.bsj0420.ex98databindingbindingadapter

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.bsj0420.ex98databindingbindingadapter.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContentView(R.layout.activity_main)

        // 1. 데이터 바인딩유틸을 통해 레이아웃 연동
        val binding : ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        // 2. 바인딩과 뷰모델 연결
        binding.vm = MyViewModel()

    }
}

1. src에 데이터 바인딩 사용

  • xml 이미지는 나오지 않음
<?xml version="1.0" encoding="utf-8"?>

<layout>
    <!-- 1. 레이아웃 xml 에서 사용할 데이터 변수   -->
    <data>
        <!-- 뷰모델을 통한 변수 생성 -->
        <variable
            name="vm"
            type="com.bsj0420.ex98databindingbindingadapter.MyViewModel"/>
    </data>


    <!-- 1. 레이아웃 영역   -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".MainActivity">

        <!-- 1) 서버에 있는 Url 데이터와 바인딩하는 이미지뷰 -->
        <ImageView
            android:src="@{vm.img}"
            android:layout_width="180dp"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"/>
        <!-- src속성은 res폴더 안에 있는 이미지만 보여줄 수 있음 -->
        <!-- 대부분의 이미지는 서버에 있는 경우가 많기에 src속성 사용 불가함 -->
        <!-- data binding하려면 xml 속성으로 변수와 연결해야 함 -->
        <!-- 하지만 ImageView에는 URL을 설정하는 속성이 기본적으로 제공되지 않음 -->
        <!-- 그래서 개발자가 원하는 속성을 직접 만들 수 있는 문법이 존재함 : @BindingAdapter 라고 부름 -->
        <!-- 바인딩아답터를 설정하는 클래스 정의하기-->


    </LinearLayout>

</layout>

2. 속성 만들어서 사용하기! ( @BindingAdapter('이름정의') ) ***

1. 새로운 속성 만들기

  • static 메소드를 가진 object 클래스 만들기!!!
  • 코틀린엔 static키워드 없음 대신에 @JvmStatic 사용!!

💡 이미지뷰에 새로운 xml 속성 만들기

메소드 명은 내 맘대로
다만! 파라미터 (어떤뷰에 주는지(뷰타입), 어떤 값인지(속성값)) 가 중요!!

package com.bsj0420.ex98databindingbindingadapter

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

//기존 뷰들에 없는 새로운 xml 속성을 연결하는 기능 메소드를 가지는 객체
//보통 static 메소드를 가진 class로 사용
object MybindingAdapter  { //static 메소드를 가져야하기 때문에 class면 안됨 object로 명시
    //객체가 단 한마리 밖에 없는 애 : 싱글턴 패턴!! 

    // 1) 이미지뷰에 새로운 xml 속성 만들기
    // [속성명 : imageUrl ]
    @BindingAdapter("imageUrl") //어노테이션 해독기 필요 - 빌드그래이들에 기능 추가 필요!
    // To use data binding annotations in Kotlin, apply the 'kotlin-kapt' plugin in your module's build.gradle
    @JvmStatic //static 만드는 어노테이션
    fun loadImg(view : ImageView, url:String) {
        //메소드 명은 내 맘대로 다만! 파라미터 (어떤뷰에 주는지(뷰타입), 어떤 값인지(속성값)) 가 중요!!
        Glide.with(view.context).load(url).into(view)
    }


}
  1. @BindingAdapter() 사용하기 위해 buid.gradle에 속성 추가

  2. 내가 만든 바인딩 아답터 xml에서 등록

<?xml version="1.0" encoding="utf-8"?>

<layout>
    <!-- 1. 레이아웃 xml 에서 사용할 데이터 변수   -->
    <data>
        <!-- 뷰모델을 통한 변수 생성 -->
        <variable
            name="vm"
            type="com.bsj0420.ex98databindingbindingadapter.MyViewModel"/>
    </data>


    <!-- 1. 레이아웃 영역   -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".MainActivity">

        <!-- 1) 서버에 있는 Url 데이터와 바인딩하는 이미지뷰 -->
<!--        <ImageView-->
<!--            android:src="@{vm.img}"-->
<!--            android:layout_width="180dp"-->
<!--            android:layout_height="wrap_content"-->
<!--            android:adjustViewBounds="true"/>-->
        <!-- src속성은 res폴더 안에 있는 이미지만 보여줄 수 있음 -->
        <!-- 대부분의 이미지는 서버에 있는 경우가 많기에 src속성 사용 불가함 -->
        <!-- data binding하려면 xml 속성으로 변수와 연결해야 함 -->
        <!-- 하지만 ImageView에는 URL을 설정하는 속성이 기본적으로 제공되지 않음 -->
        <!-- 그래서 개발자가 원하는 속성을 직접 만들 수 있는 문법이 존재함 : @BindingAdapter 라고 부름 -->
        <!-- 바인딩아답터를 설정하는 클래스 정의하기-->


        <!--  내가만든 바인딩아답터를 쓰기  -->
        <ImageView
            app:imageUrl="@{vm.img}"
            android:layout_width="180dp"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"/>

        <!--  익명 함수 만들기   -->
        <Button
            android:onClick="@{v->vm.changeImage()}"
            android:text="chang image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

</layout>



[2] 리사이클러뷰에 대량의 데이터(List, Array) 바인딩 하는 custom 속성 지정

2-1) 리사이클러뷰 등록

1. 데이터바인딩을 사용한 리사이클러뷰 준비

1-1) item에 보여질 데이터 Item 준비

package com.bsj0420.ex98databindingbindingadapter

data class Item(
    var title:String,
    var msg : String
)

1-2) xml 제작

  • 리사이클러뷰xml도 최상위 root가 layout이어야함
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <!--리사이클러뷰도 바인딩으로!!!!!!!!!!!!-->

    <!-- 1. 데이터 변수 선언 -->
    <data>
        <variable
            name="item"
            type="com.bsj0420.ex98databindingbindingadapter.Item" />
    </data>

    <!-- 2. 모양 -->
    <com.google.android.material.card.MaterialCardView
        android:layout_margin="4dp"
        android:padding="16dp"
        app:strokeColor="@color/black"
        app:strokeWidth="0.5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:text="@{item.title}"
                android:textSize="24sp"
                android:textStyle="bold"
                android:padding="4dp"
                android:textColor="@color/black"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

            <TextView
                android:text="@{item.msg}"
                android:padding="4dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        </LinearLayout>

    </com.google.android.material.card.MaterialCardView>

</layout>

1-3) RecyclerItemAdapter item뷰를 재사용해 보여줄 아답터 준비

LayoutInflater
데이터 바인딩은 리사이클려부도 여과없이 DataBindingUtil를 사용하여 layout을 만든다

📢 버튼 클릭 이벤트
아답터에서 뷰바인딩을 사용해 하는게 좋다!!
데이터 바인딩으로 한다고 더 좋은게 아님

package com.bsj0420.ex98databindingbindingadapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bsj0420.ex98databindingbindingadapter.databinding.RecyclerItemBinding

//아답터는 값이 변하면 안돼서 List
class RecyclerItemAdapter(val context: Context, val items: List<Item>) : Adapter<RecyclerItemAdapter.VH>() {
    
    //바인딩이 붙잡고 있는 레이아웃 root 를 준다
    inner class VH(val binding:RecyclerItemBinding) : ViewHolder(binding.root)

    // 레이아웃 만들기
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val binding : RecyclerItemBinding = DataBindingUtil.inflate(LayoutInflater.from(context),R.layout.recycler_item,parent,false)
        return VH(binding)
    }

    override fun getItemCount(): Int = items.size

    // 이미 데이터 바인딩 되어 있어서 xml에 선언한 변수 item에 객체 값만 설정해주면 알아서 모든 뷰들에 바인딩 된다
    override fun onBindViewHolder(holder: VH, position: Int) {
        //원래는 일일이 찾아와서 set 했는데 그럴 필요 없음

        holder.binding.item = items[position]  //이미 아이템에 연결이 되어 있으니까 아이템만 주면됨
        
        //**********************************
        //아이템 클릭 이벤트 처리 --> 뷰바인딩 이용해서 하기
        holder.binding.root.setOnClickListener {
            Toast.makeText(context, "${items[position].title}", Toast.LENGTH_SHORT).show()
        }

    }

}

2. 메인 xml recycler뷰에 바인딩 할 뷰모델에 리사이클러 뷰가 보여줄 대량의 데이터 컬렉션 생성

package com.bsj0420.ex98databindingbindingadapter

import androidx.databinding.ObservableField
import java.util.Date

class MyViewModel {


    //2) 리사이클러 뷰가 보여줄 대량의 데이터 컬렉션 생성 및 초기화
    val items : ObservableField<MutableList<Item>> = ObservableField(mutableListOf( Item("sam","hi"), Item("Lisa", "blackpink") ))

   
}

3. BindingAdapter에 속성 생성

  • 데이터 바인딩 속성에 list를 보여주는 것 따위는 없다 직접 정의하여 사용함
package com.bsj0420.ex98databindingbindingadapter

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

//기존 뷰들에 없는 새로운 xml 속성을 연결하는 기능 메소드를 가지는 객체
//보통 static 메소드를 가진 class로 사용
object MybindingAdapter  { //static 메소드를 가져야하기 때문에 class면 안됨 object로 명시
    //객체가 단 한마리 밖에 없는 애 : 싱글턴 패턴!! 

    // 2) 리사이클뷰에 리스트를 설정할 수 있는 새로운 xml 속성 만들기 : [속성이름 : itemList]
//    @BindingAdapter("itemList")
//    @JvmStatic
//    fun setItemList(view:RecyclerView, items : MutableList<Item>) {
//        //컬렉션 타입은 사용 불가능....
//    }

    @BindingAdapter("itemList")
    @JvmStatic
    fun setItemList(view:RecyclerView, items : Any) {
        //컬렉션 타입은 사용 불가능....그래서 Any로 타입 지정
        view.adapter = RecyclerItemAdapter(view.context, items as List<Item>) //형변환
    }



}

4. 메인에 recycler에 사용하기

<?xml version="1.0" encoding="utf-8"?>

<layout>
    <!-- 1. 레이아웃 xml 에서 사용할 데이터 변수   -->
    <data>
        <!-- 뷰모델을 통한 변수 생성 -->
        <variable
            name="vm"
            type="com.bsj0420.ex98databindingbindingadapter.MyViewModel"/>
    </data>


    <!-- 1. 레이아웃 영역   -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".MainActivity">

        <!-- 1) 서버에 있는 Url 데이터와 바인딩하는 이미지뷰 -->
<!--        <ImageView-->
<!--            android:src="@{vm.img}"-->
<!--            android:layout_width="180dp"-->
<!--            android:layout_height="wrap_content"-->
<!--            android:adjustViewBounds="true"/>-->
        <!-- src속성은 res폴더 안에 있는 이미지만 보여줄 수 있음 -->
        <!-- 대부분의 이미지는 서버에 있는 경우가 많기에 src속성 사용 불가함 -->
        <!-- data binding하려면 xml 속성으로 변수와 연결해야 함 -->
        <!-- 하지만 ImageView에는 URL을 설정하는 속성이 기본적으로 제공되지 않음 -->
        <!-- 그래서 개발자가 원하는 속성을 직접 만들 수 있는 문법이 존재함 : @BindingAdapter 라고 부름 -->
        <!-- 바인딩아답터를 설정하는 클래스 정의하기-->


        <!--  내가만든 바인딩아답터를 쓰기  -->
        <ImageView
            app:imageUrl="@{vm.img}"
            android:layout_width="180dp"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"/>

        <!--  익명 함수 만들기   -->
        <Button
            android:onClick="@{v->vm.changeImage()}"
            android:text="chang image"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <!-- 2)리사이클러뷰에 대량의 데이터(List, Array) 바인딩 하는 custom 속성 지정 -->
        <androidx.recyclerview.widget.RecyclerView
            app:itemList="@{vm.items}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="300dp"/>

        <Button
            android:onClick="@{v -> vm.addItem()}"
            android:text="add item"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </LinearLayout>

</layout>



2-2) 리사이클러뷰에 버튼 클릭 시 데이터 추가

1. 메인 xml의 버튼

2. 데이터를 담당하는 뷰모델에 기능 추가

  • ObservableField 특징...같은 객체(리스트)를 다시 설정하면 화면 갱신 안됨
  • 때문에 완전히 새로운 리스트를 새로 만들어서 데이터 추가해야됨
  • 아예 새로운 리스트를 생성한뒤 기존 리스트를 뒤에 붙여준다
package com.bsj0420.ex98databindingbindingadapter

import androidx.databinding.ObservableField
import java.util.Date

class MyViewModel {


    //2.1) 버튼 클릭 이벤트 콜백에서 호출할 메소드
    fun addItem() {
        //원래는 서버에서 새로운 데이터를 읽어오는 코드 작성
        //테스트 목적으로 그냥 아이템 추가해보기 - add가 없음,,,
//        val list = items.get() //get하면 무조건 nullable....
//        list?.add(0, Item("NEW", Date().toString()))
//        items.set(list) //ObservableField 특징...같은 객체(리스트)를 다시 설정하면 화면 갱신 안됨...

        //아예 새로운 리스트를 생성한뒤 기존 리스트를 뒤에 붙여준다
        val list : MutableList<Item> = mutableListOf()
        list.add(Item("NEW", Date().toString()))
        list.addAll(items.get()!!) //addAll 다른 리스트 붙이는 메소드
        items.set(list)

        //노티파이 안해도 알아서 붙인다,,,
    }
}

📢 ObservableXXX 의 단점

  1. 새로 set 하는 객체가 변경되지 않으면 화면갱신이 안됨
  2. Activity나 Fragment의 라이프사이클을 고려하지 않고 데이터 변경한다...화면이 종료되는 상황에서도 화면 갱신을 시도하려고 함
    => 리소스 낭비가 됨,,

이런 단점을 개선하기 위해 Jetpack 라이브러리로 배포된 LiveData라는 녀석이 등장함

profile
보조기억장치

0개의 댓글