After using DataBinding and ViewBinding for a long time, I decided to write two articles to see how ViewBinding generates XxxBinding classes. I decided that two articles were not enough, so I wrote three.

Examples used in the code have been uploaded to Github: example address

DataBinding contains three blogs:

DataBinding (including ViewBinding

DataBinding (iii) Build process analysis

Use ViewBinding

1.1 Generating a ViewBinding File

ViewBinding was added in AS3.6, just need to be introduced in app.build below

android {
    buildFeatures{
        viewBinding = true
    }
}
Copy the code

Data_binding_base_class_source_out/is generated in app/build/generated/data_binding_base_class_source_out/ BuildTypes /out/{buildTypes}/out/buildTypes/out/{package name}/databinding generates the ViewBinding file.

1.2 Use and packaging
  1. The Activity is used in

     // MainActivity.kt
     class MainActivity : AppCompatActivity() {
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             val binding = ActivityMainBinding.inflate(layoutInflater)
             setContentView(binding.root)
    
             initView(binding)
         }
    
         private fun initView(binding: ActivityMainBinding) {
             binding.tvTitle.text = "viewBindingTitle"
    
             val fragmentManager = supportFragmentManager
             val fragmentTransaction = fragmentManager.beginTransaction()
             fragmentTransaction.replace(R.id.container, MainFragment())
             fragmentTransaction.commit()
         }
     }
    Copy the code

Through ActivityMainBinding. Inflate () to obtain ActivityMainBinding later, can be directly to call its attributes for each View configuration.

<? The XML version = "1.0" encoding = "utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout 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" tools:context=".MainActivity"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <FrameLayout android:id="@+id/container" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_title"/> </androidx.constraintlayout.widget.ConstraintLayout>Copy the code
  1. Use in fragments

    class MainFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { val binding = FragmentMainBinding.inflate(inflater) initView(binding) return binding.root } private fun InitView (binding: FragmentMainBinding) {binding.tvshowtext. text = "set in MainFragment"}}Copy the code

Corresponding to the XML for

<? The XML version = "1.0" encoding = "utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_show_text" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" Android: text = "I am fragments" / > < / androidx. Constraintlayout. Widget. Constraintlayout >Copy the code

If you don’t want to use a ViewBinding you can add tools to the root ViewGroup :viewBindingIgnore=”true” the XxxBinding file will not be generated

Encapsulate it in BaseActivity

abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {

    lateinit var binding: VB

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val parameterizedType = this.javaClass.genericSuperclass as ParameterizedType
        val types = parameterizedType.actualTypeArguments
        val vb = types[0] as Class<VB>
        binding = vb.getMethod("inflate", LayoutInflater::class.java)
            .invoke(null, layoutInflater) as VB

        setContentView(binding.root)

        initView()
        initData()
    }

    protected abstract fun initView()
    protected abstract fun initData()
}
Copy the code

By inheriting BaseActivity, you can use binding or individual Views directly. The same goes for BaseFragment:

abstract class BaseFragment<VB : ViewBinding> : Fragment() { protected lateinit var binding: VB override fun onCreateView( inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle? ) : View? { val parameterizedType = javaClass.genericSuperclass as ParameterizedType val types = parameterizedType.actualTypeArguments val vb = types[0] as Class<VB> binding = vb.getMethod("inflate", LayoutInflater::class.java) .invoke(null, layoutInflater) as VB return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initView() } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) initData() } abstract fun initView() abstract fun initData() }Copy the code

The use of the DataBinding

To enable DataBinding, add the following code to the build.gradle of the corresponding module. After synchronization, DataBinding support is introduced:

buildFeatures{
    dataBinding = true
}
Copy the code

Second, basic introduction

Open the layout file and select the root layoutViewGroup, according to the pressAlt + Enter, click on theConvert to data binding layout, or right mouse button selectionShow Context Action chooseConvert to data bindingYou can generate the layout rules required by DataBinding:

<? The 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"  xmlns:tools="http://schemas.android.com/tools"> <data> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code

The difference with the original layout is that a Layout tag wraps the original layout. The Data tag is used to declare the variables to be used and the types of variables. The Data tag acts as a bridge between the View and the Model. Bind data (Model) to UI (View).

Let’s start by creating a model

data class EmployeeBean(val id: String, val name: String, var email: String) 
Copy the code

Use in XML

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="com.zbt.databinding.EmployeeBean"/> <variable name="employeeBean" type="EmployeeBean" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_name" style="@style/TvStyle" android:text="@{employeeBean.name, Default = yao DAE}" /> <TextView style="@style/TvStyle" Android :text="@{employeebean.email}" /> </LinearLayout> </layout>Copy the code

Importing data can be done in a variety of ways, using import or type directly to import classes containing package names. Classes in the java.lang.* package are automatically imported and can be used directly

The above code lives a variable of type EmployeeBean. You can use this variable directly in the control. DataBinding maps Employeebean.name to the corresponding getter method by referencing TextView to the associated variable with @{employeebean.name}.

Set the layout file with DataBindingUtil in the Activity, get the XxxBinding file, and then set the variable employeeBean with XxxBinding, and you can see the data displayed

protected lateinit var dataBinding: DB

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    dataBinding = DataBindingUtil.setContentView(this, getLayoutId())

    initData()
}

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

override fun initData() {
    dataBinding.employeeBean = EmployeeBean("12345", "张三", "[email protected]")
    dataBinding.tvName.text = "yao da e"
}
Copy the code

EmployeeBean has not been assigned yet, so we can give a default value. Note that the default values are not displayed in the code.

Android: text = "@ {employeeBean. The name, the default = yao dae will work}"Copy the code

You can also assign values to individual controls in code. It is important to note that the following assignments cannot be used in conjunction with Android :text=”@{employeebean.email}”.

 dataBinding.tvEmail.text = "[email protected]"
Copy the code

When the data changes, the ViewDataBinding variable value needs to be reassigned before the UI can be refreshed

One-way data binding

As mentioned above, data changes need to be reassigned, so how to implement automatic data update? There are three ways to automatically refresh the UI for data changes: BaseObservable, ObservableField, and ObservableArray

3.1 BaseObservable

BaseObservable provides notifyChange(), which refreshes all values, and notifyPropertyChanged(int fieldId), which updates only the flag corresponding to BR. The generation of this BR is generated through the @bindable annotation, which can be associated with the view of the BR notify specific property, as shown in model

Class WorkBean: BaseObservable() {// If it is a private modifier, add the @bindable annotation to the member variable's get method. String = "" get() = field set(value) { field = value notifyPropertyChanged(com.zbt.databinding.BR.workName) } var workContent: String = "" // If it is a private modifier, add the @bindable annotation to the member variable's get method, Id @bindable get() = field set(value) {field = value /** * If it is notifyChange(), */ // update all fields notifyChange()} var workTime: String = ""}Copy the code

For the set() of the name field, only the name field is updated; for the workContent field, the set() of the workContent field updates all fields.

Take a look at a demo of three buttons that change the corresponding data

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="workBean" type="com.zbt.databinding.WorkBean" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView style="@style/TvStyle" android:text="@{workBean.workName}" /> <TextView style="@style/TvStyle" android:text="@{workBean.workContent}" /> <TextView style="@style/TvStyle" android:text="@{workBean.workTime}" /> <Button style="@style/BtnStyle" < buttonstyle ="@style/BtnStyle" style=" buttonstyle /BtnStyle" < buttonstyle ="@style/BtnStyle" style=" buttonstyle /BtnStyle" Android :onClick="changeTime" Android :text=" Change attributes time = 2 workdays "/> </LinearLayout> </layout>Copy the code

class Test2Activity :

BaseActivity<ActivityTest2Binding>() { private val workBean = WorkBean() override fun getLayoutId(): Int { return R.layout.activity_test2 } override fun initData() { workBean.workName = "dataBinding" workBean.workContent = "data refresh" workBean workTime = "1 hour" / * * * register androidx. Databinding. Observables of OnPropertyChangedCallback listener, When data changes observed object, * listener will be notified, the propertyId is @ Bindable compiled generated fields. * / workBean addOnPropertyChangedCallback (object: Observable.OnPropertyChangedCallback() { override fun onPropertyChanged(sender: Observable? , propertyId: Int) { when (propertyId) { BR.workName -> println("BR.workName changed") BR._all -> println("all changed") else -> println("other changed") } } }) dataBinding.workBean = workBean } fun changeNameAndTime(view: View) {workbean.workName = "workbean.workTime = "4 hours"} Fun changeContentAndTime(View: View) {workbean.workContent = "speed up" workbean.workTime = "8 hours "} Fun changeTime(View: View) {workbean.workTime = "2 workdays "}}Copy the code

You can see the changesworkNameOnly updatedworkNameCorresponding view; To change theworkContentAll views are refreshed; For change onlyworkTimeField is not updatedTextView.

3.2 ObservableField

An inherited Observable needs to set notifyXxx() to update the UI, but ObservableField is simpler and doesn’t need notify to update the UI. ObservableField also indirectly inherits from Observable, so it can be understood as an encapsulation of annotations and refresh operations in BaseObsevable fields. Official offer for the eight basic types of data encapsulation, such as ObservableByte, ObservableInt and ObservableParcelable, but can be by observables < T > generics to declare other types of data.

Use the corresponding Obserable for each field in Model, as follows:

class ObservableWorkBean(name: String, card: Boolean) {
    var name: ObservableField<String> = ObservableField(name)
    var card: ObservableBoolean = ObservableBoolean(card)
}
Copy the code

When used, the UI is refreshed with an ObservableFiled. Set () assignment

Override fun initData() {val workBean = ObservableWorkBean(" ObservableWorkBean ", false) dataBinding.workBean = workBean dataBinding.clickHandler = ObservableWorkBeanChangeHandler(workBean) } class ObservableWorkBeanChangeHandler(val workBean: ObservableWorkBean) {funChangename () {/** * ObservableField set() */ workbean.name ${Random.nextInt(1000)}") } fun changeCard() { workBean.card.set(true) } }Copy the code
3.3 ObservableArray

DataBinding provides fields to replace List and Map, ObservableArrayList and ObservableArrayMap, respectively, so I named them ObservableArray

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="strs" type="androidx.databinding.ObservableList&lt; String&gt;" /> <variable name="map" type="androidx.databinding.ObservableArrayMap&lt; String,String&gt;" /> <variable name="index" type="int" /> <variable name="key" type="String" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView style="@style/TvStyle" android:text="@{strs[index]}" /> <TextView style="@style/TvStyle" android:text="@{map[key]}" /> <Button style="@style/BtnStyle" android:onClick="changeIndexAndKey" Android :onClick="changeIndexAndKey" /> <Button style="@style/BtnStyle" android:onClick="changeIndexAndKey" / /> </LinearLayout> </layout>Copy the code

Test4Activity

override fun initData() { val list = ObservableArrayList<String>() list.add("12345") list.add("67890") val map = ObservableArrayMap<String, String>() map.put("map", "key shi map") map.put("list", "key shi list") dataBinding.strs = list dataBinding.map = map dataBinding.index = 0 dataBinding.key = "map" } fun changeIndexAndKey(view: View) { dataBinding.index = 1 dataBinding.key = "list" } fun changeValue(view: View) { dataBinding.strs? .set(1, "abc") dataBinding.map? .put("list", "key shi list value changed") }Copy the code

As you can see, changing index and value will refresh the UI, and you need to be aware that the subscript is out of bounds.

Only ObservableArrayList and ObservableArrayMap are provided in DataBinding. To use a HashMap, or an ObservableList of its structure, or an ObservableMap, we can do the following:

class ObservableHashMap<K, V> : HashMap<K, V>(), ObservableMap<K, V> {
    override fun addOnMapChangedCallback(callback: ObservableMap.OnMapChangedCallback<out ObservableMap<K, V>, K, V>?) {
        TODO("Not yet implemented")
    }

    override fun removeOnMapChangedCallback(callback: ObservableMap.OnMapChangedCallback<out ObservableMap<K, V>, K, V>?) {
        TODO("Not yet implemented")
    }
}

class ObservableLinkedList<T> : LinkedList<T>(), ObservableList<T> {
    override fun addOnListChangedCallback(callback: ObservableList.OnListChangedCallback<out ObservableList<T>>?) {
        TODO("Not yet implemented")
    }

    override fun removeOnListChangedCallback(callback: ObservableList.OnListChangedCallback<out ObservableList<T>>?) {
        TODO("Not yet implemented")
    }
}
Copy the code

Two-way data binding

Two-way data binding means that the view is refreshed when the data changes and can change the data when the view changes

4.1 Bidirectional data binding

Android :text=”@={workbean.name}” android:text=”@={workbean.name}”

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="workBean" type="com.zbt.databinding.ObservableWorkBean" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" android:orientation="vertical"> <TextView style="@style/TvStyle" android:text="model - View : Android :text="@{workbean.name}" /> <EditText style="@style/EtStyle" android:layout_marginTop="10dp" android:text="@={workBean.name}" /> </LinearLayout> </layout>Copy the code

4.2 Event Binding

Event binding is also a variable binding, except that the variable set is the callback interface. Event callbacks are used for the following events:

  • android:onclick
  • andorid:onLongClick
  • android:onTextChanged

Such as onClick and afterTextChanged

<TextView style="@style/TvStyle" Android :layout_marginTop="60dp" Android :text=" event binding "/> <TextView style="@style/TvStyle"  android:onClick="@{()->eventBinding.onNameClick(workBean)}" android:text="@{workBean.name}" /> <EditText style="@style/EtStyle" android:layout_marginTop="10dp" android:afterTextChanged="@{eventBinding.onAfterTextChanged}" android:hint="@{workBean.name}" />Copy the code

Test5Activity

class Test5Activity : BaseActivity<ActivityTest5Binding>() { override fun getLayoutId(): Int {return r.layout.activity_test5} Override fun initData() {val workBean = ObservableWorkBean(" ", true) dataBinding.workBean = workBean dataBinding.eventBinding = EventBinding(this, workBean) } class EventBinding(private val context: Context, private val workBean: ObservableWorkBean) { fun onNameClick(observableWorkBean: ObservableWorkBean) { Toast.makeText(context, "card: ${observableWorkBean.card.get()}", Toast.LENGTH_LONG).show() } fun onAfterTextChanged(s: Editable) { workBean.name.set(s.toString()) } } }Copy the code

The demo is as follows:

Method references in the event binding can be used, and can be consistent with the keep-event-callback method: Android: afterTextChanged = “@ {eventBinding. OnAfterTextChanged}”, It is also possible to use lambda to sign Android without following the default method :onClick=”@{()->eventBinding. OnNameClick (workBean)}”

Five, the use of operators

5.1 Basic operators

DataBinding supports basic operators, expressions, and keywords in layout files:

  • Arithmetic operation + – / * %
  • String merge +
  • Logic && | |
  • Binary & | ^
  • One yuan + -! ~
  • Shift >> >>> <<
  • Compare == > < >= <=
  • Instanceof
  • character, String, numeric, null
  • Cast
  • The method call
  • Field visit
  • Array access []
  • Ternary operators? :
5.2?? The operator

Null merge operator?? The first non-NULL value is taken as the return value, the preceding value is taken if the preceding value is not null, and the following value is taken otherwise

<TextView android:id="@+id/tv_email" style="@style/TvStyle" android:text="@{employeeBean.email?? employeeBean.name}" />Copy the code
5.3 Attribute Control

For example, control the visibility of a View

<import type="android.view. view "/> <TextView style="@style/TvStyle" Android :text="view visible test" android:visibility="@{workBean.card? View.VISIBLE : View.INVISIBLE}" />Copy the code
Avoid null pointer exceptions

DataBinding will also automatically help us avoid null pointer exceptions. For example, if the workBean in “@{workbean.name}” is null, the workbean.name will be assigned the default value null instead of throwing a null pointer exception

Sixth, the use of sets

DataBinding supports the use of data, lists, sets, and maps in layout files, fetching elements such as array[index]

To distinguish it from varible <> tags, <> using generics needs to be escaped

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="array" type="String[]" /> <variable name="list" type="java.util.List&lt; String&gt;" /> <variable name="map" type="java.util.Map&lt; String, String&gt;" /> <variable name="set" type="java.util.Set&lt; String&gt;" /> <variable name="sparse" type="android.util.SparseArray&lt; String&gt;" /> <variable name="index" type="int" /> <variable name="key" type="String" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" Android :orientation="vertical"> <TextView style="@style/TvStyle" Android :orientation=" orientation "/> <TextView style="@style/TvStyle" android:text="@{array[index]}" /> <TextView style="@style/TvStyle" android:text="@{sparse[index]}" /> <TextView style="@style/TvStyle" android:text="@{list[index]}" /> <TextView style="@style/TvStyle" android:text="@{map[key]}" /> <TextView style="@style/TvStyle" android:text='@{set.contains(key)? }' /> </LinearLayout> </layout>Copy the code

Test6Activity:

class Test6Activity : BaseActivity<ActivityTest6Binding>() {

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

    override fun initData() {

        dataBinding.array = arrayOf("array")

        val sparseArray = SparseArray<String>()
        sparseArray[0] = "sparseArray"
        dataBinding.sparse = sparseArray

        dataBinding.list = listOf("list")

        val map = mutableMapOf<String, String>()
        map.put("databinding", "map")
        dataBinding.map = map

        val set = mutableSetOf<String>()
        set.add("set")
        dataBinding.set = set

        dataBinding.key = "databinding"
        dataBinding.index = 0
    }
}
Copy the code

Include and viewStub

Include layout files also support DataBinding through DataBinding.

Reference the viewStub layout in the layout file

view_include.xml:

<? The 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"> <data> <variable name="workBean" type="com.zbt.databinding.WorkBean" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/teal_200" android:gravity="center" android:padding="20dp" android:text="@{workBean.workName}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code

view_stub.xml:

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="workBean" type="com.zbt.databinding.WorkBean" /> </data> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#42A5F5" android:gravity="center" android:padding="20dp" android:text="@{workBean.workContent}" /> </layout>Copy the code

Use include and viewStub

<? The XML version = "1.0" encoding = "utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="workBean" type="com.zbt.databinding.WorkBean" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" android:orientation="vertical"> <Button Android :id="@+id/btn_show" style="@style/BtnStyle" Android :text=" display viewStub" /> <include layout="@layout/view_include" bind:workBean="@{workBean}" /> <ViewStub android:id="@+id/view_stub" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout="@layout/view_stub" bind:workBean="@{workBean}" /> </LinearLayout> </layout>Copy the code

If you do not bind ViewStub data in XML using bind:workBean=”@{workBean}”, you can use the setOnInflateListener callback function to bind data in the callback function

Eight, annotations,

DataBinding provides annotations such as Bindable, BindingAdapter, BindingConversion, BindingMethod, InverseMethod, etc. Bindable is already used in DataBinding. Is used to bind data to the view and then notify.

BindingAdapter annotations are used to modify existing or custom attributes. The annotated object can be an existing attribute, such as Android :text, Android :layout_width, or you can customize the attribute and use it in the layout file.

The BindingConversion annotation is used for data conversion, or type conversion

BindingMethod DataBinding can use setter methods as custom properties in the layout by default, but BindingMethod annotations should be used if methods are not in setter format. Associate an existing method in a class by creating a custom property.

First we use these annotations in our code

class Test8Activity : BaseActivity<ActivityTest8Binding>() {companion Object {/** * Change native, in this case, */ @jvmStatic @bindingAdapter (" Android :text") Fun setText(TV: TextView, text: @jvmStatic @bindingAdapter ("text") fun printText(TV: TextView, text: String) {println(" get text: $text")} // // put in Companion object will report this error: @BindingConversion is only allowed on public static methods conversionString(java.lang.String) // @BindingConversion // fun conversionString(text: String): String? { // return "$text-conversionString" // } } override fun getLayoutId() = R.layout.activity_test8 override fun initData() {val employeeBean = employeeBean ("12345", "zhang Shan ", "[email protected]") dataBinding.employeeBean = employeeBean dataBinding.tvtToast.setOnClickListener { val workBean = WorkBean() databinding.workBean = WorkBean workbean.workName = "code word"}}} @bindingConversion Fun convertStringToColor(str: String): Int {return when (STR) {" red "-> color.parsecolor ("#FF1493")" orange "-> color.parsecolor ("#0000FF") else -> color.parsecolor ("#0000FF") Color.parseColor("#FF4500") } } @BindingMethods( BindingMethod( type = TextView::class, attribute = "showToast", method = "showToast" ) ) class TextViewToast(context: Context, attrs: AttributeSet) : androidx.appcompat.widget.AppCompatTextView(context, attrs) { fun showToast(s: String?) { if (TextUtils.isEmpty(s)) { return } Toast.makeText(context, s, Toast.LENGTH_LONG).show() } }Copy the code

These capabilities are then used in XML

<? The 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"> <data> <import type="com.zbt.databinding.Test8Activity.Companion" /> <variable name="employeeBean" type="com.zbt.databinding.EmployeeBean" /> <variable name="workBean" type="com.zbt.databinding.WorkBean" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" android:orientation="vertical"> <TextView style="@style/TvStyle" android:text="@{employeeBean.name}" app:text="@{employeeBean.email}" /> <TextView android:id="@+id/textView1" style="@style/BtnStyle" android:layout_marginTop="20dp" android:padding="20dp" Android :text=" pure blue "Android :text=" pure blue" Android :textColor='@{" blue "}' /> <TextView style="@style/BtnStyle" Android :layout_marginTop="20dp" Android :padding="20dp" Android :text=" orange "Android :textColor='@{" orange "}' /> <TextView style="@style/BtnStyle" Android :layout_marginTop="20dp" Android :padding="20dp" Android :text=" red "Android :textColor='@{" red "}' /> <com.zbt.databinding.TextViewToast android:id="@+id/tvt_toast" style="@style/BtnStyle" android:layout_marginTop="20dp" Android: background-color ="@color/teal_200"/> Android: background-color ="@color/teal_200" </LinearLayout> </layout>Copy the code
  • When BindAdapter changes the native properties, the properties are changed throughout the package
  • BindAdapter custom attributes that change where used
  • BindingConversion needs to be written as a top-level function or defined in an Object class
  • BindingMethod and BindingMethods are used for class annotation

The example here is to look at Test8Activity and Test9Activity in the code

RecyclerView used in

RecyclerView is often used in the code, combined with DataBinding can make the code structure more concise.

Because RecyclerView uses Adapter to set data, there is no way to set data using DataBinding.

item_view.xml

<? The 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"> <data> <variable name="employeeBean" type="com.zbt.databinding.EmployeeBean" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{employeeBean.name}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{employeeBean.id}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_email" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{employeeBean.email}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="@id/tv_name" app:layout_constraintRight_toLeftOf="@id/tv_id" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>Copy the code

adapter

class EmployeeAdapter(private val employeeBeans: List<EmployeeBean>) :
    RecyclerView.Adapter<EmployeeAdapter.EmployeeViewHolder>() {

    inner class EmployeeViewHolder(val binding: ItemViewBinding) :
        RecyclerView.ViewHolder(binding.root) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmployeeViewHolder {
        return EmployeeViewHolder(
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                R.layout.item_view,
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(holder: EmployeeViewHolder, position: Int) {
        holder.binding.employeeBean = employeeBeans[position]
    }

    override fun getItemCount(): Int {
        return employeeBeans.size
    }
}
Copy the code

Set data in the Activity:

class Test10Activity : AppCompatActivity() { private val employeeObservableList = ObservableArrayList<EmployeeBean>().apply { for (i in 0.. 32) {add (EmployeeBean (" ${Random () nextInt ()} ", "zhang SAN ${Random () nextInt (100)}", "${Random().nextInt(1000)}@163.com}" ) ) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test10) val recyclerView = findViewById<RecyclerView>(R.id.recycler_view) recyclerView.layoutManager = LinearLayoutManager(this) val employeeAdapter = EmployeeAdapter(employeeObservableList) employeeAdapter.notifyDataSetChanged() employeeObservableList.addOnListChangedCallback( DynamicDataChangeCallback<ObservableArrayList<EmployeeBean>>( employeeAdapter ) ) recyclerView.adapter = employeeAdapter } fun addItem(view: View) { if (employeeObservableList.size >= 1) { val employeeBean = EmployeeBean( "${Random().nextInt()}", "Zhang SAN ${Random (). NextInt} (100)", "${Random () nextInt (1000)} @ 163. com}") employeeObservableList. Add (0, employeeBean) } } fun addItemList(view: View) { if (employeeObservableList.size >= 2) { val employeeBeans: MutableList<EmployeeBean> = ArrayList<EmployeeBean>() for (i in 0.. 2) {val employeeBean = employeeBean ("${Random().nextint ()}", "${Random().nextInt(1000)}@163.com}" ) employeeBeans.add(employeeBean) } employeeObservableList.addAll(0, employeeBeans) } } fun removeItem(view: View) { if (employeeObservableList.size >= 2) { employeeObservableList.removeAt(1) } } fun updateItem(view: View) { if (employeeObservableList.size >= 2) { val employeeBean: EmployeeObservableList = employeeObservableList[1] = employeeObservableList[1] = employeeObservableList}}Copy the code

As you can see, the data is bound in item_view.xml. The Adapter is much cleaner.

The ObservableList contains OnListChangedCallback for adding and deleting List data. This allows ObservableArrayList to modify the interface directly as data changes

public interface ObservableList<T> extends List<T> { void addOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> callback); void removeOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> callback); abstract class OnListChangedCallback<T extends ObservableList> { public abstract void onChanged(T sender); public abstract void onItemRangeChanged(T sender, int positionStart, int itemCount); public abstract void onItemRangeInserted(T sender, int positionStart, int itemCount); public abstract void onItemRangeMoved(T sender, int fromPosition, int toPosition, int itemCount); public abstract void onItemRangeRemoved(T sender, int positionStart, int itemCount); }}Copy the code

conclusion

ViewBinding and DataBinding are generally summarized above. The modification methods provided by the system are basically enough. For other things, we can customize attributes and do operations by ourselves through @BindingMethods. For example, we can customize OnClick through @BindingMethods to prevent double clicking and so on

Refer to the article

Android DataBinding starts from beginner to advanced