[AAC] Data Binding - 2

dwjeongยท2023๋…„ 11์›” 9์ผ
0

์•ˆ๋“œ๋กœ์ด๋“œ

๋ชฉ๋ก ๋ณด๊ธฐ
15/28

๐Ÿ”Ž observable ๋ฐ์ดํ„ฐ ๊ฐ์ฒด์™€ ์ž‘์—…

observability๋Š” ๊ฐ์ฒด๊ฐ€ ๋ฐ์ดํ„ฐ ๋ณ€ํ™”๋ฅผ ๋‹ค๋ฅธ ๊ฐ์ฒด์—๊ฒŒ ์•Œ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๋‚˜ํƒ€๋ƒ„. ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ์ฒด, ํ•„๋“œ ๋˜๋Š” ์ปฌ๋ ‰์…˜์„ ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ.

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•ด๋„ UI๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์Œ. ์ด ๋•Œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋‹ค๋ฅธ ๊ฐ์ฒด(๋ฆฌ์Šค๋„ˆ)์—๊ฒŒ ์•Œ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ์Œ.

๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ ๊ฐ์ฒด ์ค‘ ํ•˜๋‚˜๊ฐ€ UI์— ๋ฐ”์ธ๋”ฉ๋˜๊ณ  ๋ฐ์ดํ„ฐ ๊ฐ์ฒด์˜ ์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜๋ฉด UI๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋จ.



๐Ÿ“– Observable fields

ํด๋ž˜์Šค์— ์†์„ฑ์ด ๋ช‡ ๊ฐœ ์—†๋‹ค๋ฉด Observable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ๋ณด๋‹ค ์ œ๋„ค๋ฆญ Observable ํด๋ž˜์Šค์™€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์›์‹œ ํƒ€์ž…๋ณ„ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•„๋“œ๋ฅผ ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Œ.

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

Observable ํ•„๋“œ๋Š” ๋‹จ์ผ ํ•„๋“œ๋ฅผ ๊ฐ€์ง„ ๋…๋ฆฝ์  ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด.

class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}

ํ•„๋“œ์— ์ ‘๊ทผํ•˜๋ ค๋ฉด set() ๋ฐ get() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ฝ”ํ‹€๋ฆฐ ํ”„๋กœํผํ‹ฐ ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉ.

user.firstName = "Google"
val age = user.age

๐Ÿ“– Observable collections

๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์œ ํ•˜๋Š” ๋™์  ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, observable collection์€ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋Ÿฐ ๊ตฌ์กฐ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ.

ObservableArrayMap<String, Any>().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}
<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
โ€ฆ
 <!-- string ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” map-->
<TextView
    android:text="@{user.lastName}" 
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text="@{String.valueOf(1 + (Integer)user.age)}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>



integerํ˜• ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ObservableArrayList๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์œ ์šฉ.

ObservableArrayList<Any>().apply {
    add("Google")
    add("Inc.")
    add(17)
}
<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
โ€ฆ
<TextView
    android:text='@{user[Fields.LAST_NAME]}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
<TextView
    android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>



๐Ÿ“– Observable objects

๊ฐœ๋ฐœ์„ ๋” ์‰ฝ๊ฒŒํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๋Š” BaseObservable ํด๋ž˜์Šค ์ œ๊ณต.
BaseObservable์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ์ดํ„ฐ ํด๋ž˜์Šค๋Š” ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์•Œ๋ฆผ์„ ๋‹ด๋‹น.

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์€ ๋ชจ๋“ˆ ํŒจํ‚ค์ง€ ๋‚ด์— BR์ด๋ผ๋Š” ์ด๋ฆ„์˜ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ. ์ด ํด๋ž˜์Šค๋Š” ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์— ์‚ฌ์šฉ๋œ ๋ฆฌ์†Œ์Šค์˜ ID๋ฅผ ํฌํ•จํ•จ. Bindable ์–ด๋…ธํ…Œ์ด์…˜์€ ์ปดํŒŒ์ผ ์ค‘ BR ํด๋ž˜์Šค ํŒŒ์ผ์— ํ•ญ๋ชฉ์„ ์ƒ์„ฑํ•จ.

๋ฐ์ดํ„ฐํด๋ž˜์Šค์˜ ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ PropertyChangeRegistry ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ์Šค๋„ˆ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋“ฑ๋ก ๋ฐ ํ†ต์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก Observable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Œ.




๐Ÿ“– Lifecycle-aware ๊ฐ์ฒด

์•ฑ์˜ ๋ ˆ์ด์•„์›ƒ์€ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ์†Œ์Šค์— ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด UI์— ์ž๋™์œผ๋กœ ํ†ต์ง€๋จ.
๐Ÿ‘‰ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ๋ผ์ดํ”„์‚ฌ์ดํด์„ ์ธ์‹ํ•˜๋ฉฐ UI๊ฐ€ ํ™”๋ฉด์— ํ‘œ์‹œ๋  ๋•Œ๋งŒ ํŠธ๋ฆฌ๊ฑฐ๋จ.

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์€ StateFlow์™€ LiveData๋ฅผ ์ง€์›ํ•จ.

StateFlow ์‚ฌ์šฉ

์ฝ”ํ‹€๋ฆฐ ๋ฐ ์ฝ”๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜๋Š” ์•ฑ์˜ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ์†Œ์Šค๋กœ StateFlow ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ. StateFlow ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด lifecycle owner๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•จ.

class ViewModelActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Inflate view and obtain an instance of the binding class.
        val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

        // Specify the current activity as the lifecycle owner.
        binding.lifecycleOwner = this
    }
}

StateFlow์™€ ViewModel์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.

class ScheduleViewModel : ViewModel() {

    private val _username = MutableStateFlow<String>("")
    val username: StateFlow<String> = _username

    init {
        viewModelScope.launch {
            _username.value = Repository.loadUserName()
        }
    }
}

๋ ˆ์ด์•„์›ƒ์—์„œ ๋ฐ”์ธ๋”ฉ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ViewModel ๊ฐ์ฒด์˜ ์†์„ฑ ๋ฐ ๋ฉ”์„œ๋“œ๋ฅผ ํ•ด๋‹น ๋ทฐ์— ํ• ๋‹น.

<TextView
    android:id="@+id/name"
    android:text="@{viewmodel.username}" />




๐Ÿ”Ž ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค ์ƒ์„ฑ

Data Binding ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ ˆ์ด์•„์›ƒ ๋ณ€์ˆ˜์™€ ๋ทฐ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•จ.
์ƒ์„ฑ๋œ ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋Š” ๋ ˆ์ด์•„์›ƒ ๋ณ€์ˆ˜๋ฅผ ๋ ˆ์ด์•„์›ƒ ๋ทฐ์™€ ์—ฐ๊ฒฐํ•จ.

๋ชจ๋“  ์ƒ์„ฑ๋œ ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋Š” ViewDataBinding ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•จ.

๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ž˜์Šค์˜ ์ด๋ฆ„์€ ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ์˜ ์ด๋ฆ„์„ ํŒŒ์Šค์นผ ์ผ€์ด์Šค๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  "Binding" ์ ‘๋ฏธ์‚ฌ๋ฅผ ์ถ”๊ฐ€ํ•œ ๊ฒƒ.


๐Ÿ“– ๋ฐ”์ธ๋”ฉ ๊ฐ์ฒด ์ƒ์„ฑ

๋ฐ”์ธ๋”ฉ ๊ฐ์ฒด๋Š” ๋ ˆ์ด์•„์›ƒ์„ ์ธํ”Œ๋ ˆ์ดํŠธํ•œ ์งํ›„ ์ƒ์„ฑ๋จ. ๊ฐ์ฒด๋ฅผ ๋ ˆ์ด์•„์›ƒ์— ๋ฐ”์ธ๋”ฉํ•˜๋Š” ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์€ ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค์˜ ์ •์  ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ.
๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค์˜ inflate() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ์ธํ”Œ๋ ˆ์ดํŠธํ•˜๊ณ  ๊ฐ์ฒด๋ฅผ ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ์Œ.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater)

    setContentView(binding.root)
}

inflate() ๋ฉ”์„œ๋“œ์˜ ๋Œ€์•ˆ ๋ฒ„์ „์€ LayoutInflater ๊ฐ์ฒด ์™ธ์—๋„ ViewGroup ๊ฐ์ฒด๋ฅผ ์ธ์ˆ˜๋กœ ์‚ฌ์šฉํ•จ.

val binding: MyLayoutBinding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false)

๋ ˆ์ด์•„์›ƒ์ด ๋‹ค๋ฅธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ”Œ๋ ˆ์ดํŠธ๋œ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ„๋„๋กœ ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ์Œ.
val binding: MyLayoutBinding = MyLayoutBinding.bind(viewRoot)

์‚ฌ์ „์— ๋ฐ”์ธ๋”ฉ ํƒ€์ž…์„ ์•Œ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ DataBindingUtil ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ”์ธ๋”ฉ ์ƒ์„ฑ.
val viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
val binding: ViewDataBinding? = DataBindingUtil.bind(viewRoot)

ํ”„๋ž˜๊ทธ๋จผํŠธ, ๋ฆฌ์ŠคํŠธ๋ทฐ ๋˜๋Š” ๋ฆฌ์‚ฌ์ดํด๋Ÿฌ๋ทฐ ์–ด๋Œ‘ํ„ฐ ๋‚ด์—์„œ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ํ•ญ๋ชฉ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค์˜ inflate() ๋ฉ”์„œ๋“œ ๋˜๋Š” DataBindingUtil ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„ ์ˆ˜ ์žˆ์Œ.

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)



๐Ÿ“– ID๋ฅผ ๊ฐ€์ง„ ๋ทฐ

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ ˆ์ด์•„์›ƒ์—์„œ id๋ฅผ ๊ฐ€์ง„ ๋ทฐ์— ๋Œ€ํ•ด ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค์— immutable ํ•„๋“œ๋ฅผ ์ƒ์„ฑํ•จ. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ ˆ์ด์•„์›ƒ์˜ id๋ฅผ ํฌํ•จํ•œ ๋ชจ๋“  ๋ทฐ๋ฅผ ํ•œ๋ฒˆ์— ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์—์„œ ์ถ”์ถœํ•จ.
findViewById()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋น ๋ฅผ ์ˆ˜ ์žˆ์Œ.



๐Ÿ“– Variables, ViewStubs

  1. variables
    ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋ ˆ์ด์•„์›ƒ์—์„œ ์„ ์–ธ๋œ ๊ฐ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ ‘๊ทผ์ž ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•จ.
<data>
   <import type="android.graphics.drawable.Drawable"/>
   <variable name="user" type="com.example.User"/>
   <variable name="image" type="Drawable"/>
   <variable name="note" type="String"/>
</data>

์œ„ ์ฝ”๋“œ์—์„œ user, image ๋ฐ note ๋ณ€์ˆ˜์— ๋Œ€ํ•œ setter์™€ getter๋ฅผ ์ƒ์„ฑํ•จ.



  1. ViewStubs

ViewStub์€ ๋Ÿฐํƒ€์ž„ ์‹œ ๋ณด์ด์ง€ ์•Š์œผ๋ฉฐ ํฌ๊ธฐ๊ฐ€ 0์ด๊ณ  ๋ ˆ์ด์•„์›ƒ ๋ฆฌ์†Œ์Šค๋ฅผ ๋Šฆ๊ฒŒ ์ธํ”Œ๋ ˆ์ดํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๋ทฐ. ViewStub์ด ํ‘œ์‹œ๋˜๊ฑฐ๋‚˜ inflate()๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ๋ ˆ์ด์•„์›ƒ ๋ฆฌ์†Œ์Šค๊ฐ€ ์ธํ”Œ๋ ˆ์ดํŠธ๋จ.

๊ทธ ๋‹ค์Œ ViewStub์€ ์ƒ์œ„ ๋ทฐ๋ฅผ ์ธํ”Œ๋ ˆ์ดํŠธ๋œ ๋ทฐ๋กœ ๋Œ€์ฒดํ•จ.
๋”ฐ๋ผ์„œ ViewStub์€ setVisibility(int) ๋˜๋Š” inflate()๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ ๊นŒ์ง€ ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์— ์กด์žฌํ•จ.

ํ™•์žฅ๋œ ๋ทฐ๋Š” ViewStub์˜ ๋ ˆ์ด์•„์›ƒ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ViewStub์˜ ์ƒ์œ„ ๋ทฐ์— ์ถ”๊ฐ€๋จ.
๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ViewStub์˜ inflateId ์†์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธํ”Œ๋ ˆ์ดํŠธ ๋œ ๋ทฐ์˜ id๋ฅผ ์ •์˜, ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Œ.

     <ViewStub android:id="@+id/stub"
               android:inflatedId="@+id/subTree"
               android:layout="@layout/mySubTree"
               android:layout_width="120dip"
               android:layout_height="40dip" />

์ด๋ ‡๊ฒŒ ์ •์˜๋œ ViewStub์€ stub ์ด๋ผ๋Š” id๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ.
๋ ˆ์ด์•„์›ƒ ๋ฆฌ์†Œ์Šค mySubTree๋ฅผ inflateํ•œ ํ›„ ViewStub์€ ์ƒ์œ„ํ•ญ๋ชฉ์—์„œ ์ œ๊ฑฐ๋จ.

mySubTree๋ฅผ inflateํ•˜์—ฌ ์ƒ์„ฑ๋œ ๋ทฐ๋Š” inflatedId ์†์„ฑ์— ์ง€์ •๋œ subTree id๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฐพ์„ ์ˆ˜ ์žˆ์Œ. inflate๋œ ๋ทฐ์—๋Š” ์ตœ์ข…์ ์œผ๋กœ ๋„ˆ๋น„ 120dip, ๋†’์ด 40dip์ด ํ• ๋‹น๋จ.

     ViewStub stub = findViewById(R.id.stub);
     View inflated = stub.inflate();

inflate()๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ViewStub์ด inflate๋œ ๋ทฐ๋กœ ๋Œ€์ฒด๋˜๊ณ  inflate๋œ ๋ทฐ๊ฐ€ ๋ฐ˜ํ™˜๋จ. ์ด์— ๋”ฐ๋ผ์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ถ”๊ฐ€์ ์œผ๋กœ findViewById()๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ ๋„ inflate๋œ ๋ทฐ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Œ

  • ์‚ฌ์šฉ ์ด์œ 
    ๋ณต์žกํ•˜์ง€๋งŒ ์ž์ฃผ ์“ฐ์ด์ง€ ์•Š๋Š” ๋ ˆ์ด์•„์›ƒ (๋‹ค์šด๋กœ๋“œ ์ง„ํ–‰ ์ƒํ™ฉ์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ทฐ ๋“ฑ..)์ด ์žˆ์„ ๋•Œ ViewStub์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰๊ณผ ๋ Œ๋”๋ง ์‹œ๊ฐ„์„ ์ค„์ผ ์ˆ˜ ์žˆ์Œ.



  • ViewStubProxy๋ž€
    ์ธํ”Œ๋ ˆ์ด์…˜ ์ „ํ›„์˜ ViewStub์„ ๋‚˜ํƒ€๋‚ด๋Š” ํด๋ž˜์Šค์ด๋ฉฐ ViewStub์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Œ.
    ์ธํ”Œ๋ ˆ์ด์…˜ ํ›„์—๋Š” ์ธํ”Œ๋ ˆ์ด์…˜๋œ ๋ ˆ์ด์•„์›ƒ์˜ ๋ฃจํŠธ ๋ทฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.
    inflate๋œ ๋ ˆ์ด์•„์›ƒ์— ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋ทฐ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Œ.

ViewStub์ด ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์—์„œ ์‚ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ๋„๋ก ๋ฐ”์ธ๋”ฉ ๊ฐœ์ฒด์˜ ๋ทฐ๋„ ์‚ฌ๋ผ์ ธ์•ผ ํ•จ.
๋ทฐ๋Š” final์ด๊ธฐ ๋•Œ๋ฌธ์— ViewStubProxy ๊ฐœ์ฒด๋Š” ์ƒ์„ฑ๋œ ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค์—์„œ ViewStub์„ ๋Œ€์‹ ํ•˜์—ฌ ViewStub์ด ์žˆ์„ ๋•Œ ViewStub์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ œ๊ณตํ•˜๊ณ  ViewStub์ด inflate๋  ๋•Œ inflate๋œ ๋ทฐ ๊ณ„์ธต ๊ตฌ์กฐ์— ๋Œ€ํ•œ ์•ก์„ธ์Šค๋ฅผ ์ œ๊ณต.


๋‹ค๋ฅธ ๋ ˆ์ด์•„์›ƒ์„ inflateํ•  ๋•Œ ์ƒˆ ๋ ˆ์ด์•„์›ƒ์— ๋Œ€ํ•œ ๋ฐ”์ธ๋”ฉ์„ ์„ค์ •ํ•ด์•ผ ํ•˜๋ฏ€๋กœ ViewStubProxy๋Š” ViewStub OnInflateListener๋ฅผ ์ˆ˜์‹ ํ•˜๊ณ  ํ•„์š”ํ•  ๋•Œ ๋ฐ”์ธ๋”ฉ์„ ์„ค์ •.

ํ•œ๋ฒˆ์— ํ•˜๋‚˜์˜ ๋ฆฌ์Šค๋„ˆ๋งŒ ์กด์žฌํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ViewStubProxy๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ”์ธ๋”ฉ์„ ์„ค์ •ํ•œ ํ›„ ํ˜ธ์ถœํ•˜๋Š” OnInflateListener๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ.



๐Ÿ“– Dyname variables

RecyclerView.Adapter์™€ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ํŠน์ • ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋ฅผ ์•Œ์ง€ ๋ชปํ•จ. onBindViewHolder()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋™์•ˆ ๋ฐ”์ธ๋”ฉ ๊ฐ’์„ ํ• ๋‹นํ•ด์•ผ ํ•จ.

override fun onBindViewHolder(holder: BindingHolder, position: Int) {
    item: T = items.get(position)
    holder.binding.setVariable(BR.item, item);
    holder.binding.executePendingBindings(); // ๋ฐ”์ธ๋”ฉ์„ ์ฆ‰์‹œ ์‹คํ–‰ํ•ด์•ผ ํ•  ๊ฒฝ์šฐ ๊ฐ•์ œ๋กœ ์‹คํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ
}



๐Ÿ“– ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋ช… ์ปค์Šคํ…€

๋ชจ๋“ˆ ํŒจํ‚ค์ง€๊ฐ€ com.example.my.app์ธ ๊ฒฝ์šฐ ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋Š” com.example.my.app.databind ํŒจํ‚ค์ง€์— ๋ฐฐ์น˜๋จ.
data element์˜ ํด๋ž˜์Šค ์†์„ฑ์„ ์กฐ์ •ํ•˜์—ฌ ํด๋ž˜์Šค ์ด๋ฆ„์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํŒจํ‚ค์ง€์— ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์Œ.

<data class="ContactItem">
    ...
</data>
<data class=".ContactItem"> <!--ํด๋ž˜์Šค ์ด๋ฆ„ ์•ž์— ๋งˆ์นจํ‘œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๋‹ค๋ฅธ ํŒจํ‚ค์ง€์— ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค ์ƒ์„ฑ ๊ฐ€๋Šฅ-->
    ...
</data>
<data class="com.example.ContactItem"> <!-- ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜๋ ค๋Š” ์ „์ฒด ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Œ.-->
    ...
</data>

๐Ÿ”Ž ๋ฐ”์ธ๋”ฉ ์–ด๋Œ‘ํ„ฐ

๋ฐ”์ธ๋”ฉ ์–ด๋Œ‘ํ„ฐ๋Š” ๊ฐ’ ์„ค์ •์„ ์œ„ํ•ด ์ ์ ˆํ•œ ํ”„๋ ˆ์ž„์›Œํฌ ํ˜ธ์ถœ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์ผ์„ ๋‹ด๋‹นํ•จ.
(์˜ˆ: setText() ํ˜ธ์ถœ๊ณผ ๊ฐ™์€ ์†์„ฑ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ, setOnClickListener()๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ.)

๐Ÿ“– ์†์„ฑ ๊ฐ’ ์„ค์ •

๋ฐ”์ธ๋”ฉ๋œ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ƒ์„ฑ๋œ ๋ฐ”์ธ๋”ฉ ํด๋ž˜์Šค๋Š” ๋ฐ”์ธ๋”ฉ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ทฐ์—์„œ setter ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•จ. ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•  ์ˆ˜ ์žˆ๊ณ ,
๋ฉ”์„œ๋“œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•˜๊ฑฐ๋‚˜ ์ปค์Šคํ…€ ๋กœ์ง์„ ์ œ๊ณตํ•˜์—ฌ ๋ฉ”์„œ๋“œ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Œ.
๐Ÿ‘‰ xml์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“  ์†์„ฑ๊ณผ ๋กœ์ง์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.


๋ฉ”์„œ๋“œ ์ž๋™ ์„ ํƒ

๋งŒ์•ฝ android:text="@{user.name}" ๊ณผ ๊ฐ™์€ ํ‘œํ˜„์‹์ด ์ฃผ์–ด์ง€๋ฉด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” user.getName()์—์„œ ๋ฐ˜ํ™˜๋œ ํƒ€์ž…์„ ํ—ˆ์šฉํ•˜๋Š” setText(arg)๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์Œ.

user.getName()์˜ ๋ฐ˜ํ™˜ ํƒ€์ž…์ด String์ธ ๊ฒฝ์šฐ, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” String ์ธ์ˆ˜๋ฅผ ํ—ˆ์šฉํ•˜๋Š” setText() ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์Œ. ํ‘œํ˜„์‹์ด int์ธ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ int ์ธ์ˆ˜๋ฅผ ํ—ˆ์šฉํ•˜๋Š” setText() ๋ฉ”์„œ๋“œ๋ฅผ ํƒ์ƒ‰ํ•จ

๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์€ ํ•ด๋‹น ์ด๋ฆ„์˜ ์†์„ฑ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์—๋„ ๋™์ž‘. ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  setter์— ๋Œ€ํ•œ ์†์„ฑ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Œ.

<!-- DrawerLayout์—๋Š” ์†์„ฑ์ด ์—†์ง€๋งŒ ๋งŽ์€ setter๊ฐ€ ์žˆ์œผ๋ฉฐ setScrimColor(int) ๋ฐ addDrawerListener(DrawerListener) ๋ฉ”์„œ๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์‚ฌ์šฉํ•จ-->
<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 ์–ด๋…ธํ…Œ์ด์…˜์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Œ.

@BindingMethods(value = [
    BindingMethod(
        type = android.widget.ImageView::class,
        attribute = "android:tint",
        method = "setImageTintList")])

์ผ๋ฐ˜์ ์œผ๋กœ ์•ˆ๋“œ๋กœ์ด๋“œ ํ”„๋ ˆ์ž„์›Œํฌ ํด๋ž˜์Šค์—์„œ ์†์„ฑ์€ ์ด๋ฏธ ๋„ค์ด๋ฐ ๊ทœ์น™์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋˜๊ณ  ์ž๋™์œผ๋กœ ์ผ์น˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ฐพ์œผ๋ฏ€๋กœ setter์˜ ์ด๋ฆ„์„ ๋ฐ”๊ฟ€ ํ•„์š”๊ฐ€ ์—†์Œ.



์‚ฌ์šฉ์ž ์ •์˜ ๋กœ์ง ๋ถ€์—ฌ

์ผ๋ถ€ ์†์„ฑ์—๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐ”์ธ๋”ฉ ๋กœ์ง์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Œ.

android:paddingLeft์†์„ฑ์—๋Š” ์—ฐ๊ฒฐ๋œ setter๊ฐ€ ์—†์Œ. ๋Œ€์‹  setPadding(left, top, right, bottom)์ด ์ œ๊ณต๋จ.

BindingAdapter ์–ด๋…ธํ…Œ์ด์…˜์ด ํฌํ•จ๋œ ์ •์  ๋ฐ”์ธ๋”ฉ ์–ด๋Œ‘ํ„ฐ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์†์„ฑ์— ๋Œ€ํ•œ setter๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉ์ž ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Œ.

/** ์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์†์„ฑ๊ณผ ์—ฐ๊ด€๋œ ๋ทฐ์˜ ํƒ€์ž…, ๋‘ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ์ง€์ •๋œ ์†์„ฑ์— ๋Œ€ํ•œ ํƒ€์ž….**/
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}

์—ฌ๋Ÿฌ ์†์„ฑ์„ ๋ฐ›๋Š” ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์žˆ์„ ์ˆ˜๋„ ์žˆ์Œ.

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

์†์„ฑ์ด ์„ค์ •๋  ๋•Œ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ํ•˜๋ ค๋ฉด ๋‹ค์Œ ์˜ˆ์— ํ‘œ์‹œ๋œ ๋Œ€๋กœ ์–ด๋Œ‘ํ„ฐ์˜ requireAll ํ”Œ๋ž˜๊ทธ๋ฅผ false๋กœ ์„ค์ •.

@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("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
    if (oldPadding != newPadding) {
        view.setPadding(newPadding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
                    view.getPaddingBottom())
    }
}



์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ๋‹ค์Œ ์˜ˆ์ œ์™€ ๊ฐ™์ด ํ•˜๋‚˜์˜ ์ถ”์ƒ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š” ์ธํ„ฐํŽ˜์ด์Šค ๋˜๋Š” ์ถ”์ƒ ํด๋ž˜์Šค์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.

@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)
        }
    }
}
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>



๋ฆฌ์Šค๋„ˆ์— ๋ฉ”์„œ๋“œ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ธ ๊ฒฝ์šฐ ์—ฌ๋Ÿฌ ๋ฆฌ์Šค๋„ˆ๋กœ ๋ถ„ํ• ํ•ด์•ผ ํ•จ.

// 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)
}
@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)
        }
    }
}



๐Ÿ“– ๊ฐ์ฒด ๋ณ€ํ™˜

์ž๋™ ๊ฐ์ฒด ๋ณ€ํ™˜

๋ฐ”์ธ๋”ฉ ํ‘œํ˜„์‹์—์„œ ๊ฐœ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋˜๋ฉด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์†์„ฑ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์„ ํƒ.
๊ฐœ์ฒด๋Š” ์„ ํƒํ•œ ๋ฉ”์„œ๋“œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ํƒ€์ž…์œผ๋กœ ์บ์ŠคํŒ…๋จ.

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

object.key ํ‘œ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งต์˜ ๊ฐ’์„ ์ฐธ์กฐํ•  ์ˆ˜๋„ ์žˆ์Œ.
@{userMap["lastName"]}์„ @{userMap.lastName}์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Œ.


์œ„ ์˜ˆ์‹œ์˜ userMap ๊ฐ์ฒด๋Š” android:text ์†์„ฑ์˜ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” setText(CharSequence) ๋ฉ”์„œ๋“œ์— ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜ ์œ ํ˜•์œผ๋กœ ์ž๋™์œผ๋กœ ์บ์ŠคํŒ…๋˜๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•จ.

๋งค๊ฐœ๋ณ€์ˆ˜ ์œ ํ˜•์ด ๋ชจํ˜ธํ•  ๊ฒฝ์šฐ ํ‘œํ˜„์‹์—์„œ ๋ฐ˜ํ™˜ ์œ ํ˜•์„ ์บ์ŠคํŒ…ํ•  ๊ฒƒ.



์‚ฌ์šฉ์ž ์ •์˜ ๋ณ€ํ™˜

๋ทฐ์˜ android:Background ์†์„ฑ์€ Drawable์„ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ ์ง€์ •๋œ ์ƒ‰์ƒ ๊ฐ’์€ ์ •์ˆ˜.

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Drawable์ด ์˜ˆ์ƒ๋˜๊ณ  ์ •์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜๋  ๋•Œ๋งˆ๋‹ค int๋ฅผ ColorDrawable๋กœ ๋ณ€ํ™˜.
BindingConversion ์–ด๋…ธํ…Œ์ด์…˜๊ณผ ํ•จ๊ป˜ ์ •์  ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ.

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

๋ฐ”์ธ๋”ฉ ํ‘œํ˜„์‹์— ์ œ๊ณต๋˜๋Š” ๊ฐ’ ํƒ€์ž…์€ ๋™์ผํ•œ ํƒ€์ž…์ด์–ด์•ผ ํ•จ.

// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<!-- ์—๋Ÿฌ๊ฐ€ ์ผ์–ด๋‚˜๋Š” ์ฝ”๋“œ-->
<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

0๊ฐœ์˜ ๋Œ“๊ธ€