Use articles
First find the app's build.gradle:
android { compileSdk 32 defaultConfig { applicationId "com.example.myapplication" minSdk 24 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } //Add the following configuration dataBinding { enabled = true } }
Then enter the xml file and convert our xml into a databinding 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" 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>
Instructions about the IDE's automatic conversion to databinding layout: Stop the mouse at the free position in the layout of the outermost constraintLayout, and then press option+enter (Mac) at the same time, and the convert to data binding layout prompt will appear.
Let's start by writing a bean:
package com.example.myapplication import androidx.annotation.Keep @Keep data class User( var userName: String, var passWord: String )
Then the layout is written like this:
<?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> <variable name="user" type="com.example.myapplication.User" /> </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="@{user.userName}" 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>
name is the name, you can write it casually, it is generally best to be consistent with the bean, and type is the specifically defined type. @{user.***} implements data reference.
MainActivity uses:
package com.example.myapplication import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import com.example.myapplication.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { var activityMainBinding: ActivityMainBinding? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //setContentView(R.layout.activity_main) activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) val user = User("lzy", "123456") activityMainBinding?.user = user } }
Note that the original way of setting the layout should be replaced by the way of DataBindingUtil.
Run a handful:
This completes the use of the most basic databinding. Then some students may say, this is very troublesome, what are the advantages of using databinding? Let's see next:
package com.example.myapplication import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import com.example.myapplication.databinding.ActivityMainBinding import java.lang.Thread.sleep class MainActivity : AppCompatActivity() { var activityMainBinding: ActivityMainBinding? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //setContentView(R.layout.activity_main) activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) val user = User("lzy", "123456") activityMainBinding?.user = user Thread { for (i in 0 until 100) { sleep(1000) user.userName = "lzy".plus(i) activityMainBinding?.user = user } }.start() } }
The effect we see is this:
Every second, the UI will automatically change. It is not obvious that we only have one TextView. If we have many Views, when the data class changes, it will automatically drive the UI changes, which will make the code more concise. You need to set data for each View.
Going a step further, we can also define a Bean class like this:
package com.example.myapplication; import androidx.annotation.Keep; import androidx.databinding.BaseObservable; import androidx.databinding.Bindable; @Keep public class User2 extends BaseObservable { private String userName; private String passWord; @Bindable public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; notifyPropertyChanged(BR.userName); } @Bindable public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; notifyPropertyChanged(BR.passWord); } }
use:
package com.example.myapplication import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import com.example.myapplication.databinding.ActivityMainBinding import java.lang.Thread.sleep class MainActivity : AppCompatActivity() { private var activityMainBinding: ActivityMainBinding? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) //val user = User("lzy", "123456") val user = User2() user.userName = "lzy2" user.passWord = "123456789" activityMainBinding?.user = user Thread { for (i in 0 until 100) { sleep(1000) user.userName = "lzy".plus(i) } }.start() } }
We found that after User2 inherits BaseObservable and uses notifyPropertyChanged, data changes automatically trigger UI changes.
Principles
Like other Jetpack components, the use of databinding is as simple as ever, but it's not enough to just use it, we must master the principles.
We first focus on the XML file. The XML file we write will be automatically converted into two other files through databinding:
File 1:
app/build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_main-layout.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout directory="layout" filePath="app/src/main/res/layout/activity_main.xml" isBindingData="true" isMerge="false" layout="activity_main" modulePackage="com.example.myapplication" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout"> <Variables name="user" declared="true" type="com.example.myapplication.User2"> <location endLine="9" endOffset="52" startLine="7" startOffset="8" /> </Variables> <Targets> <Target tag="layout/activity_main_0" view="androidx.constraintlayout.widget.ConstraintLayout"> <Expressions /> <location endLine="27" endOffset="55" startLine="13" startOffset="4" /> </Target> <Target tag="binding_1" view="TextView"> <Expressions> <Expression attribute="android:text" text="user.userName"> <Location endLine="21" endOffset="42" startLine="21" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="21" endOffset="40" startLine="21" startOffset="28" /> </Expression> </Expressions> <location endLine="25" endOffset="55" startLine="18" startOffset="8" /> </Target> </Targets> </Layout>
Note: There are two Target elements in Targets, which will be used later.
File 2:
app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
<?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" android:tag="layout/activity_main_0" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:tag="binding_1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Let's see what exactly does setContentView do?
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity, int layoutId, @Nullable DataBindingComponent bindingComponent) { activity.setContentView(layoutId); View decorView = activity.getWindow().getDecorView(); ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); return bindToAddedViews(bindingComponent, contentView, 0, layoutId);//1 }
We focus on note 1:
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component, ViewGroup parent, int startChildren, int layoutId) { final int endChildren = parent.getChildCount(); final int childrenAdded = endChildren - startChildren; if (childrenAdded == 1) { final View childView = parent.getChildAt(endChildren - 1); return bind(component, childView, layoutId); } else { final View[] children = new View[childrenAdded]; for (int i = 0; i < childrenAdded; i++) { children[i] = parent.getChildAt(i + startChildren); } return bind(component, children, layoutId);//1 } }
Let's look at the bind method:
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots, int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId); }
What is sMapper?
To be continued!