💡 @BindingAdapter란?
- 자가 원하는 속성을 직접 만들 수 있는 문법
- 뷰들에 없는 새로운 xml 속성을 연결하는 기능 메소드를 가지는 객체
- static 메소드를 가진 object로 명시
- 싱글턴 패턴 (객체가 단 한마리 밖에 생성되지 않는 것!)
- 사용하려면 빌드그래이들에 어노테이션 해독기 등록 해야함
To use data binding annotations in Kotlin, apply the 'kotlin-kapt' plugin in your module's build.gradle
데이터 바인디이 될 수 있도록 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.1) MyViewModel
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()
}
}
<?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>
💡 이미지뷰에 새로운 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)
}
}
@BindingAdapter() 사용하기 위해 buid.gradle에 속성 추가
내가 만든 바인딩 아답터 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>
1-1) item에 보여질 데이터 Item 준비
package com.bsj0420.ex98databindingbindingadapter
data class Item(
var title:String,
var msg : String
)
1-2) xml 제작
<?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()
}
}
}
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") ))
}
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>) //형변환
}
}
<?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>
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 의 단점
- 새로 set 하는 객체가 변경되지 않으면 화면갱신이 안됨
- Activity나 Fragment의 라이프사이클을 고려하지 않고 데이터 변경한다...화면이 종료되는 상황에서도 화면 갱신을 시도하려고 함
=> 리소스 낭비가 됨,,이런 단점을 개선하기 위해 Jetpack 라이브러리로 배포된 LiveData라는 녀석이 등장함