声明 : https://www.jianshu.com/p/714062a9af75
目录:
简介
原理
使用方法1,一节界面数据绑定(基础使用)
2,二级界面的绑定
3,响应事件
4,BindAdapter
5,RecycleView 绑定机制
6,双向绑定
🌼 简介:
DataBinding 是一种库,借助该库,可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。简单来说,就是帮我们实现 view 和 data 绑定的工具.DataBinding 的出现让布局文件承担了部分原本属于界面的工作,使页面与布局之间的耦合度进一步降低.
🌷:项目简洁,可读性高,部分与 ui 控件的代码都在布局文件里完成.
🌷:不再需要 findViewById()
🌷:布局文件可以包含简单的业务逻辑.ui 控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互.
🌼 原理:
先略过去吧....太复杂了....🤓🤓🤓,就看看下面的使用方法吧.....
🌼 使用方法:
🍀 一级界面数据绑定
1,build.gradle
android {
//....
dataBinding {
enabled = true
}
}
启动绑定数据
2,创建 Person 对象
class Person(var name: String?, var age: Int, var sex: String?)
3,修改布局文件 activity_data_binding.xml
在布局文件外层加入<layout>标签,可以手动添加,也可以将鼠标移到文件的根目录,单击小灯泡的下来三角框,选中 Convert to data binding layout,AS会自动生成代码.

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DataBindingActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
我们所做的修改是在 ui 布局的最外层加上一个 layout 的标签,并将命名空间 xmlns 从ConstraintLayout移到了<layout>标签中.这样做的目的是,告诉 DataBinding 库,我们要对该布局进行绑定,此时,rebuild 该项目,DataBinding 库会为我们生成绑定该布局文件所需要的类.
4,实例化布局
有了 DataBinding 之后,就可以告别 findViewById()了,我们可以通过DataBindingUtil.setContentView()方法实例化布局文件,该方法返回实例化后布局文件对象,名字和布局文件的名字一样,并在后面加上 Binding.
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
}
}
🐳 比如,我的布局文件名字叫 activity_data_binding.xml ,布局文件对象叫 ActivityDataBindingBinding.
5,将数据传递到布局文件
为了减轻 Activity 的工作量,让布局文件也承担一部分工作,所以要将 Person 对象传递到布局文件.具体做法如下
🐳 首先在布局文件<data>标签中定义个布局变量<variable>,指定类型和名字,名字可以随意定义.
<data>
<variable
name="personD"
type="com.mcy.test.model.Person" />
</data>
<data>标签 用于放置 ui 控件所需要的数据,数据类型可以自定义,比如代码中是的 Person 类,也可以是基本类型.
<data>
<variable name="title" type="String" />
</data>
🐳🐳 然后在 Activty 中通过 setPersonD()方法,将 Person 对象传递给布局文件中对应的布局变量.
activityDataBindingBinding.personD = Person("张三", 30, "男")
🐳🐳🐳 绑定布局文件和成员变量
<EditText
android:text="@{personD.name}"/>
<TextView
android:text="@{personD.sex}"/>
布局文件用@{}
表达式为控件赋值
🐳🐳🐳🐳在布局文件引用静态类
有时候我们需要在布局文件中引用一些 java/kotlin 工具类,帮助我们处理简单的逻辑.
class Utils {
companion object {
@JvmStatic
fun getStr(a: Int): String {
return a.toString()
}
}
}
我们可以再布局文件中通过<import> 标签导入静态工具类
<data>
<import type="com.mcy.test.Utils" />
<data>
接着在控件中使用
<TextView
android:text="@{Utils.getStr(personD.age)}"/>
6,完整的布局和 Activty 文件
<?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>
<import type="com.mcy.test.Utils" />
<variable
name="personD"
type="com.mcy.test.model.Person" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DataBindingActivity">
<EditText
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:text="@{personD.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:text="@{Utils.getStr(personD.age)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:text="@{personD.sex}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
//单向
activityDataBindingBinding.personD = Person("张三", 30, "男")
}
}
运行截图:

🍀 二级界面的绑定
在一级界面布局中,设置好布局变量personD
之后,便可以接受来自 Activty 的数据,进而将数据和控件进行绑定,不仅如此,布局变量personD
同时也是命名空间 xmlns:app
的一个属性,一级界面正是通过命名空间xmlns:app
引用布局变量 personD
,将数据对象传递给二级页面.具体代码如下:
二级页面 layout_second.xml
将 sex 的 view 挪到二级页面去了
<?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="personD"
type="com.mcy.test.model.Person" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView3"
android:text="@{personD.sex}"
......./><!--省略部分代码 -->
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
一级页面 activity_data_binding.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"
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=".DataBindingActivity">
<!--省略部分代码 -->
<include
android:id="@+id/in_3"
layout="@layout/layout_second"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:personD="@{personD}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
运行出来的截图和图一是一样的啦,就粘贴过来啦😇😇😇
🍀 响应事件
1,编写一个类,用于接受和响应 Button 的 onClick 事件.
命名为 HandleClickListener可以单独写一个文件,也可以写在 Activity 中,作为内部类.
inner class HandleClickListener {
fun showToast(view: View) {
Toast.makeText(view.context, "哈哈哈", Toast.LENGTH_LONG).show()
}
}
⚠️⚠️⚠️**在定义事件方法名称时需要注意:方法的名称可以和原始函数名称不一样,方法参数和返回值必须和原始的回调函数保持一致。不然会报错,比如:clickFirst(View view)必须要有view参数,如果没有会报错.**
⚠️⚠️
2,在布局文件中定义并使用
<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="handleC"
type="com.mcy.test.DataBindingActivity.HandleClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DataBindingActivity">
<!--省略部分代码 -->
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:onClick="@{handleC::showToast}"
android:text="点我"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/in_3" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
也可以用双冒号android:onClick="@{handleC::showToast}"
3,在 Avtivity 中实例化HandleClickListener类
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
//监听
activityDataBindingBinding.handleC = HandleClickListener()
}
}
运行截图

🍀 自定义 BindingAdapter
在 gradle 启动 DataBinding 库的时候,就会为我们生成所需要的各种类,其中包括大量针对 ui 控件的,名为 XXXBindingAdapter 的类,这些类中包含各种静态方法,并且在这些静态方法前都有@BindingAdapter
标签,标签中的别名对应于 ui 控件在布局文件中的属性.
看个例子,TextView的 TextViewBindingAdapter 的部分源码:
public class TextViewBindingAdapter {
private static final String TAG = "TextViewBindingAdapters";
@SuppressWarnings("unused")
public static final int INTEGER = 0x01;
public static final int SIGNED = 0x03;
public static final int DECIMAL = 0x05;
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
//........
}
Databinding 库以静态方法的形式为 ui 控件的各个属性绑定了响应的代码.若开发人员在 UI控件的属性中使用了表达式,那么当布局文件被渲染时,属性所绑定的方法会被自动调用.
比如,当 TextView 被渲染时,android:text
属性会自动调用 TextViewBindingAdapter.setText()
方法.UI控件通过简单的属性设置,便可以在布局文件中调用所绑定的方法.
那么我们就来自定义一个处理图片的 BindAdapter 类:
1, 添加 Glide 库
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
2, 添加网络权限
<uses-permission android:name="android.permission.INTERNET" />
3, 编写处理图片的 BindAdapter 类
class ImageViewBindingAdapter {
companion object{
@JvmStatic
@BindingAdapter("imageV")
fun setImage(image: ImageView, imageUrl: String?) {
if (!TextUtils.isEmpty(imageUrl)) {
Glide.with(image.context).load(imageUrl).into(image)
} else {
image.setImageResource(R.mipmap.ic_launcher)
}
}
}
}
BindAdapter中的方法均为静态方法,第 1 个参数是调用者本身,即 ImageView,第 2 个参数是布局文件在调用该方法传递过来的参数.在静态方法前面需要加入@BindingAdapter()
标签,并为该方法起一个别名,此处为 imageV.布局文件正式通过别名来调用方法的.
4, 修改布局文件
在布局文件定义 String ,传递图片地址.
<variable
name="internetImageUrl"
type="String" />
ImageView调用
<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="internetImageUrl"
type="String" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginTop="9dp"
app:imageV="@{internetImageUrl}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button4"
tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
5, Activity 中设置布局文件变量
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
//自定义 adapter
activityDataBindingBinding.internetImageUrl =
"https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3436121203,3749922833&fm=26&gp=0.jpg"
}
}
运行截图:

在上面的示例中,我们做了接受网络图片地址的展示,如果还希望,在接受网络图片的时候,也能接受本地图片资源作为参数,这样,当网络图片地址为空的时候,则显示本地图片资源所制定的图片.根据这个需求,我们优化一下 BindAdapter
class ImageViewBindingAdapter {
companion object {
@JvmStatic
@BindingAdapter(value = ["imageV", "defaultRes"], requireAll = false)
fun setImage(image: ImageView, imageUrl: String?, imageResource: Int) {
if (!TextUtils.isEmpty(imageUrl)) {
Glide.with(image.context).load(imageUrl).into(image)
} else {
image.setImageResource(imageResource)
}
}
}
}
在@BindingAdapter
标签中,方法参数以 value = ["", ""]
的形式存在,变量 requireAll 作用是告诉 DataBinding 库这些参数是否都要赋值,默认是 true.
资源文件:
<?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="internetImageUrl"
type="String" />
<variable
name="imageRes"
type="int" />
</data>
<!--省略部分代码 ConstraintLayout -->
<ImageView
app:defaultRes="@{imageRes}"
app:imageV="@{internetImageUrl}" />
<!--省略部分代码 ConstraintLayout -->
</layout>
Activity:
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
//自定义 adapter
activityDataBindingBinding.internetImageUrl =
"https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3436121203,3749922833&fm=26&gp=0.jpg"
activityDataBindingBinding.imageRes = R.mipmap.ic_launcher
}
}
🍀 RecycleView 绑定机制
1,布局文件
<?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=".DataBindingActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
RecycleView 的布局
2,item 布局 item_recycle.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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.mcy.test.Utils" />
<variable
name="personI"
type="com.mcy.test.model.Person" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{personI.name}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/textView5"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Utils.getStr(personI.age)}"
app:layout_constraintLeft_toRightOf="@+id/textView4"
app:layout_constraintRight_toLeftOf="@+id/textView6"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{personI.sex}"
app:layout_constraintLeft_toRightOf="@+id/textView5"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
RecycleView 的 item 布局文件,分别绑定不同的数据.
3,编写 Adapter
class RecycleViewAdapter(private val list: MutableList<Person>) :
RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(DataBindingUtil.inflate(
LayoutInflater.from(parent.context), R.layout.item_recycle,parent, false ))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val personViewModel = list[position]
holder.itemRecycleBinding.personI = personViewModel
}
override fun getItemCount(): Int {
return list.size
}
inner class MyViewHolder(var itemRecycleBinding: ItemRecycleBinding)
: RecyclerView.ViewHolder(itemRecycleBinding.root) //.root 返回的是布局的最外层 ui 视图
}
编写 Adapter 需要注意三个地方:
⚠️ 在 onCreateViewHolder()方法中,通过 DataBindingUtil.inflate()实例化布局.
⚠️在 onBindViewHolder 方法中,设置布局变量
⚠️ ItemRecycleBinding 是 DataBinding 为布局文件 item_recycle.xml 生成的对象
4,Activity 添加数据
class DataBindingActivity : AppCompatActivity() {
private val list = mutableListOf<Person>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
//recycleview
for (i in 1..100) {
var personViewModel1 = Person("张三$i", i, "女")
list.add(personViewModel1)
}
activityDataBindingBinding.recycle.layoutManager = LinearLayoutManager(this)
activityDataBindingBinding.recycle.adapter = RecycleViewAdapter(list)
}
}
运行截图:

🍀 双向绑定
1,编写PersonViewModel类
class PersonViewModel(name: String, age: Int, sex: String) {
var name = ObservableField<String>()
var age = ObservableField<Int>()
var sex = ObservableField<String>()
init {
this.name.set(name)
this.age.set(age)
this.sex.set(sex)
}
}
ObservableField<T> 关键字将普通对象包装成可观察对象,可以包装基本类型,集合数组类型,自定义类型的数据.当数据发生变化时,界面所用的与之相关的数据会随之刷新.
2,编写布局文件
<?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>
<!--省略部分代码 ConstraintLayout -->
<variable
name="handleC"
type="com.mcy.test.DataBindingActivity.HandleClickListener" />
<variable
name="personVM"
type="com.mcy.test.model.PersonViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DataBindingActivity">
<EditText
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
android:text="@={personVM.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:onClick="@{handleC::showToast}"
android:text="@{personVM.name}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/in_3" />
<!--省略部分代码 ConstraintLayout -->
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
android:text="@={personVM.name}"
采用@={}
表达式完成双向绑定
3,编写 Activty
class DataBindingActivity : AppCompatActivity() {
var personViewModel = PersonViewModel("张三",20,"女")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView<ActivityDataBindingBinding>(
this,
R.layout.activity_data_binding
)
//双向
activityDataBindingBinding.personVM = personViewModel
}
inner class HandleClickListener {
fun showToast(view: View) {
Toast.makeText(view.context, personViewModel.name.get(), Toast.LENGTH_LONG).show()
}
}
}
运行图片

END

网友评论