Android Jetpack之DataBinding(二)

前言

本文是在前文的基礎上繼續深入DataBinding的使用這一塊,如果有不懂的地方,請移步上一篇
Android Jetpack之DataBinding(一)

DataBinding使用

觀察者使用
使用可觀察的bean
  1. 繼承BaseObservable類,實現整體數據的可觀察。
  2. 爲屬性添加@Bindable註解,生成BR對應屬性。
  3. 重寫set方法實現,屬性改變時候動態喚醒刷新。
  4. 其餘實現就是上一篇中基本的綁定過程。
    數據類代碼如下:
class PersonObservable(): BaseObservable(){

    constructor(name:String,age:Int):this(){
        this.name = name
        this.age = age
    }

    @Bindable
    var name:String? = null
    set(value) {
        field = value
        //單個屬性喚醒刷新,參數爲編譯生成的BR條目
        notifyPropertyChanged(BR.name)
        //整個對象的屬性全部刷新
        notifyChange()
    }
    @Bindable
    var age:Int? = null
    set(value) {
        field = value
        notifyPropertyChanged(BR.age)
    }
}

xml代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="person" type="com.zgj.demojetpackapp.bean.PersonObservable"/>
        <variable name="listener" type="com.zgj.demojetpackapp.databinding.TestActivity3.EventListener"/>
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".databinding.TestActivity">

        <TextView
                android:text="@{person.name}"
                android:padding="@dimen/dimen_10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:padding="@dimen/dimen_10"
                android:text="@{person.age +`歲`}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="長大了一歲"
                  android:onClick="@{listener::onClick}"/>

    </LinearLayout>
</layout>

Activity相關調用代碼如下 ,可以看到只需要改變綁定對象的屬性值,不需要再將對象重新綁定即可實現刷新改變:

class TestActivity3 : AppCompatActivity() {

    val person = PersonObservable("張三", 23)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityTestDataBinding3Binding =
            DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding3)
        binding.person = person
        binding.listener = EventListener()
    }

    inner class EventListener {
        fun onClick(view: View) {
            person.age = person.age?.inc()
        }
    }

}

效果如下,點擊按鈕年齡隨之增長:
效果1

使用可觀察的屬性

定義可觀察的屬性,訪問使用get和set方法,代碼如下:

class PersonObservable2{
    var name:ObservableField<String> = ObservableField()
    var age:ObservableInt = ObservableInt()
}

xml代碼和上面一致,xml訪問不需要使用get,可以直接使用。
activity調用代碼如下:

class TestActivity3 : AppCompatActivity() {

    val person = PersonObservable2()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityTestDataBinding3Binding =
            DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding3)
        person.name.set("張三")
        person.age.set(23)
        binding.person = person
        binding.listener = EventListener()
    }

    inner class EventListener {
        fun onClick(view: View) {
            person.age.set(person.age.get().inc())
        }
    }

}

效果和上述一致。

問題:如果兩個類的屬性名相同會生成幾個BR屬性,會不會相互影響呢?
1、會生成1個BR屬性。	
2、不會相互影響。

測試如下:
新建StudentObservable如下,屬性和PersonObervable完全一樣:

class StudentObservable(): BaseObservable(){

    constructor(name:String,age:Int):this(){
        this.name = name
        this.age = age
    }
    
    @Bindable
    var name:String? = null
    set(value) {
        field = value
        notifyPropertyChanged(BR.name)
        notifyChange()
    }
    
    @Bindable
    var age:Int? = null
    set(value) {
        field = value
        notifyPropertyChanged(BR.age)
    }
    
}

xml代碼如下:


        <TextView
                android:text="@{`學生:`+student.name}"
                android:padding="@dimen/dimen_10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:padding="@dimen/dimen_10"
                android:text="@{`學生:`+student.age +`歲`}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

activity調用代碼如下:

class TestActivity3 : AppCompatActivity() {

    val person = PersonObservable("張三",23)
    val student = StudentObservable("張三",23)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityTestDataBinding3Binding =
            DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding3)

        binding.person = person
        binding.student = student
        binding.listener = EventListener()
    }


    inner class EventListener {
        fun onClick(view: View) {
            person.age = person.age?.inc()
            student.age = student.age?.plus(2)
        }
    }


}

效果2

原因分析:在綁定的時候傳入了特定的發佈者對象,即BaseObservable對象,根據對象和屬性共同定位是那一個屬性,如下:

原因1

DataBinding綁定列表

實現的列表效果如下:
列表效果
具體實現步驟:

  1. 自定義ViewHolder

    主要是將入參換成ViewDataBinding類型,將root傳給super的構造器即可

class BindingViewHolder(var dataBinding:ViewDataBinding) :RecyclerView.ViewHolder(dataBinding.root){
}
  1. 定義Adapter/BaseAdapter
  2. 在Adapter中進行數據綁定
BaseAdapter 實現綁定 同時抽象一個方法傳入layoutId,如下:
abstract class BaseAdapter<T>(var data :MutableList<T> = mutableListOf()) : RecyclerView.Adapter<BindingViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        return BindingViewHolder(
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                getLayoutId(),
                parent,
                false
            )
        )
    }

    override fun getItemCount(): Int {
        return  data.size
    }


    abstract fun getLayoutId(): Int

}
RecycleDemoAdapter實現具體的業務,如下:
class RecycleDemoAdapter() : BaseAdapter<RecycleListAnimalBean>() {

    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
//        holder.dataBinding.setVariable(BR.animal,data.get(position))
        (holder.dataBinding as ItemRecycleBinding).animal = data[position]
    }

    override fun getLayoutId(): Int {
        return R.layout.item_recycle
    }
}

對應的bean數據如下:
class RecycleListAnimalBean {

    /**
     * 名稱、類型、年齡、描述
     */
    val name:ObservableField<String> = ObservableField()
    val type:ObservableInt = ObservableInt()
    val age:ObservableInt = ObservableInt()
    val typeDesc:ObservableField<String> = ObservableField()

}

item_recycle對應的xml佈局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="animal" type="com.zgj.demojetpackapp.bean.RecycleListAnimalBean"/>
    </data>
    <LinearLayout
            android:padding="@dimen/dimen_10"
            android:orientation="vertical"
            android:layout_marginTop="20dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <TextView
                android:id="@+id/tv_name"
                android:text="@{animal.name,default=`名稱`}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>


        <TextView
                android:id="@+id/tv_age"
                android:text="@{`年齡:`+animal.age,default=`0`}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:id="@+id/tv_type"
                android:text="@{`類型:`+animal.type,default=`類型`}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:id="@+id/tv_desc"
                android:text="@{animal.typeDesc,default=`描述`}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

    </LinearLayout>
</layout>
  1. activity設置相關的數據和adapter,代碼如下
class TestActivity4() : AppCompatActivity() {

    var binding: ActivityTestDataBinding4Binding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding4)
        init()
    }

    private fun init() {
        binding?.rvContent?.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
        val animalBean = RecycleListAnimalBean()
        animalBean.name.set("老虎")
        animalBean.age.set(10)
        animalBean.type.set(1)
        animalBean.typeDesc.set("老虎是一種動物,和貓比較像")

        val animalBean1 = RecycleListAnimalBean()
        animalBean1.name.set("長頸鹿")
        animalBean1.age.set(20)
        animalBean1.type.set(2)
        animalBean1.typeDesc.set("長頸鹿是一種動物,脖子非常長")
        val data: MutableList<RecycleListAnimalBean> = mutableListOf(animalBean, animalBean1)

        var adapter = RecycleDemoAdapter()
        adapter.data = data
        binding?.rvContent?.adapter = adapter

    }

}

activity_test_data_binding4對應的xml代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>


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

        <TextView
                android:text="動物列表"
                android:gravity="center"
                android:layout_width="match_parent"
                android:padding="@dimen/dimen_10"
                android:layout_height="wrap_content"/>

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rv_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

    </LinearLayout>
</layout>
DataBinding結合ViewModel和LiveData使用/ DataBinding結合ViewModel和可觀察的數據類型

LiveData如果不熟悉,請移步LiveData
ViewModel如果不熟悉,請移步ViewModel
DataBinding基礎不熟悉,請移步DataBinding(一)

  1. 創建ViewModel如下

    ViewModel分兩種情況 一種是LiveData管理普通的類studentData,一種是LiveData管理可觀察數據類studentObservable,同時下
    面附上了兩個類的代碼。
    ViewModel主要進行了兩件事情,分別爲
    1)初始化了LiveData屬性
    2)延遲1s鍾之後進行相關屬性改變(普通類通過postValue方法,可觀察數據類型直接改變值即可)

class Test5ViewModel : ViewModel() {


   init {
       Thread(Runnable {
           Thread.sleep(1000)
           val student = Student("李四", 20)
           studentData.postValue(student)

           studentObservable.name = "趙六"
           studentObservable.age = 60
       }).start()
   }

   val student: Student = Student("張三", 23)

   var studentData: MutableLiveData<Student> = MutableLiveData(student)

   val studentObservable: StudentObservable = StudentObservable("王五", 16)



} 

Student類
data class Student (var name:String,var age:Int)
StudentObservable類
class StudentObservable(): BaseObservable(){

   constructor(name:String,age:Int):this(){
       this.name = name
       this.age = age
   }

   @Bindable
   var name:String? = null
   set(value) {
       field = value
       notifyPropertyChanged(BR.name)
       notifyChange()
   }
   @Bindable
   var age:Int? = null
   set(value) {
       field = value
       notifyPropertyChanged(BR.age)
   }


}
  1. activity綁定如下
    主要做了兩件事情:
    1)binding關聯生命週期,跟LiveData發消息相關
    2)進行ViewModel的實例化並且綁定到相關的xml
class TestActivity5 : AppCompatActivity() {

    var binding: ActivityTestDataBinding5Binding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding5)
        binding?.lifecycleOwner = this

        val model: Test5ViewModel = ViewModelProviders.of(this).get(Test5ViewModel::class.java)

        binding?.nameViewModel = model

    }

}
  1. 對應的xml使用如下
    聲明ViewModel對象直接使用即可,注意此處nameViewModel.studentData.name 最後一步點屬性是沒有提示的 一定要寫對
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="nameViewModel" type="com.zgj.demojetpackapp.databinding.Test5ViewModel"/>
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:padding="@dimen/dimen_10"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <TextView
                android:text="@{`姓名:`+nameViewModel.studentData.name}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:text="@{`年齡:`+nameViewModel.studentData.age}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>


        <TextView
                android:text="可觀察的數據類型"
                android:layout_margin="@dimen/dimen_10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:text="@{`姓名:`+nameViewModel.StudentObservable.name}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:text="@{`年齡:`+nameViewModel.StudentObservable.age}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>


    </LinearLayout>
</layout>

整體效果如下(初始值爲ViewModel的初始化值,1s只會會變到最終的效果):
結合效果

DataBinding結合可觀察的ViewModel
  1. 新建可觀察的Test6ViewModel /ObservableViewModel如下
    同樣繼承於ViewModel,同時實現Observable接口
baseViewModel
open class ObservableViewModel :ViewModel(),Observable{


    private val callbacks:PropertyChangeRegistry = PropertyChangeRegistry()

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
        callbacks.remove(callback)
    }

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
        callbacks.add(callback)
    }

    fun  notifyChange(){
        callbacks.notifyCallbacks(this,0,null)
    }

    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }

}
具體ViewModel
class Test6ViewModel : ObservableViewModel() {

    init {
        Thread(Runnable {
            Thread.sleep(2000)
            student.name = "李四"
            student.age = 33
            notifyChange()

        }).start()
    }
    val student: Student = Student("張三", 23)

}
  1. activity綁定如下,和前面一致
class TestActivity6 : AppCompatActivity() {

    var binding: ActivityTestDataBinding6Binding? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding6)
        binding?.lifecycleOwner = this

        val model: Test6ViewModel = ViewModelProviders.of(this).get(Test6ViewModel::class.java)

        binding?.nameViewModel = model

    }

}
  1. 對應的xml如下,直接視同student即可
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="nameViewModel" type="com.zgj.demojetpackapp.databinding.Test6ViewModel"/>
    </data>
    <LinearLayout
            android:orientation="vertical"
            android:padding="@dimen/dimen_10"
            android:gravity="center_horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <TextView
                android:text="@{`姓名:`+nameViewModel.student.name}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

        <TextView
                android:text="@{`年齡:`+nameViewModel.student.age}"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>

    </LinearLayout>
</layout>

效果如下(從最初的張三,23 在2s之後變成李四,33):
可觀察ViewModel

源碼

源碼鏈接:源碼

總結

1、限於篇幅,本篇暫時到此,後續在繼續雙向綁定以及其他相關的內容。
2、本篇主要降到,觀察者使用、可變屬性、列表綁定、結合ViewModel、LiveData以及可觀察的ViewModel等內容。
3、本篇代碼均是實際運行之後貼出的代碼,但難免有什麼粗心或者遺漏問題,歡迎指出討論。
4、另外記錄了自己寫的過程中遇到的其他問題。
5、另外此篇內容主要是使用方面的,後續如果有時間研究了相關的原理再進行相關更新。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章