在app的build.gradle文件中(模块build)中添加如下代码,即可
android {
...
dataBinding {
enabled = true
}
}
但是如果 使用了kotlin进行编码 顶部要依赖kotlin插件,否则编译失败
apply plugin: "kotlin-kapt"
数据绑定 是数据源驱动View 所以其布局文件是和普通的布局文件是不同的,其根元素 是固定的layout 标签 其内部包含两个标签 从上而下是 1,数据标签 2,根布局(就是之前的整个普通布局),示例如下:
//数据标签 包含驱动视图所需要的所有数据
//布局
假如不使用数据绑定 则布局文件是:
data标签是引入 驱动VIew所需要的所有 数据
比如:
上面是定义一个变量(variable标签定义一个变量), 这个变量的类型(type)是 com.example.User
variable是定义一个变量, type 是声明这个变量的类型 variable可以定义你使用的几乎所有的类型 而且variable可以定义多个如下所示:
//引用数据类型
//引用数据类型
//基础数据类型
import功能:
type是定义一个变量的类型,这个类型可以是类型全地址,比如com.example.User 但是也是可以写成 type =“User”,但是这时我们需要先将User类导入到data标签内,就像普通的类导入一样:如下
如果我们在导入的过程发现类的名称冲突时 比如:
这时我们根本无法清楚的知道 变量view到底是那个类型 ,这时我们可以使用重命名(alias)将导入的某个类改名为另外一个名称
这时我们就可以知道清楚的知道,变量view的类型是Vista也就是com.example.real.estate.View
总结:
Variables&type 功能:
(1) Variables标签是定义一个变量,而且可以定义多个 比如:
(2)上面仅仅是定义了 基础数据类型和引用类型但是没有定义集合 因为集合的定义方式有些特殊,因为集合的泛型会使用到<>,这个和标签<>会造成冲突,为了解决这种冲突 集合的< 要使用转义字符 <
代替 ,比如:
常见的转义字符
" "
& &
< <
> >
不断开空格
(3) databinding为variable声明的每个变量都声明了set和get方法 而且get都有默认值 引用类型为null, int为0,布尔值为false,等等,
而且 如果调用者为null,则直接返回null不在进行下一步调用,比如user.name 如果user为null则直接返回null,所以使用DataBinding 避免了空指针
(4)variable 会默认的生成一个context 变量 这个变量来源于rootView的getContext() 而且这个默认生成的变量context会覆盖data中声明的context变量(如果你声明了的话),换言之,1,我们不能再data中定义context变量名 因为其会被覆盖,2.我们可以在布局文件中使用context来获取一些系统的方法 比如:
android:text="@{context.getApplicationInfo().toString()}"
前面已经说明了如何声明所需要的数据变量,现在就需要再布局文件中使用这些变量达到绑定数据的目的
只要布局文件中有属性可以设置的都可以通过databinding来实现
//设置text属性
//这里设置text属性
布局文件设置属性都是:属性="属性值"
的形式 但是databinding是通过:属性="@{表达式}"
的方式实现的,这里的表达式的最终值就是属性值,比如:
//设置可见性
android:visibility="@{user.gender==1 ? View.VISIBLE : View.GONE}"
//设置背景
android:background="@{@color/colorImg }"
//设置文本
android:text="@{String.valueOf(user.age)}"
//设置文本
android:text="@{user.firstName}"/> //设置text属性
这里的表达式 包括:
1,资源内容
我们可以在表达式中直接调用res下的资源 比如color.xml string.xml dimen.xml 如下
android:text="@{@string/age}"
android:background="@{@color/colorImg}"
如果我们在引用资源的时候需要写入参数 这时需要:
android:text="@{@string/info(age,gender)}"
完整的xml
string.xml(string资源文件)
...
%1$d岁,性别%2$s
...
activity_test.xml(布局文件)
2,表达式特性
databinding表达式支持很多表达式特性比如:(以下的text均是指:TextView的text属性)
(1),算数运算:+ - / * % 与括号()
android:text="@{String.valueOf((8+5-7%3)*3/2)}"
(2),字符串拼接
android:text='@{"hello"+" world"}'
(3),逻辑运算 &&(需要转换成转义字符&&
) ||
&&运算 需要转换成转义字符
android:text="@{true &&false ? String.valueOf(3) :String.valueOf(5)}"
结果是5
|| 运算
android:text="@{true || false ? String.valueOf(3) :String.valueOf(5)}"
结果是3
(4),二进制运算 &(需要转换成转义字符&
) | ^
android:text="@{String.valueOf(2&3)}"
结果 2
android:text="@{String.valueOf(2|3)}"
结果 3
android:text="@{String.valueOf(2^3)}"
结果 1
(5) 一元运算 + - ! ~
正号 +
android:text="@{String.valueOf(+3)}"
结果 3
负号 -
android:text="@{String.valueOf(-3)}"
结果-3
android:text="@{String.valueOf(!3)}"
非!
android:text="@{String.valueOf(!true)}"
结果false
取反 ~
android:text="@{String.valueOf(~5)}"
结果-6
(6) 位移运算 >> >>> <<
(7) 比较运算 == > < >= <=
不过 < 需要使用转义字符<
代替
**(8) 三元运算符 ? : **
表达式? result1 : result2 如果表达式为true 则整个表达式的结果是 result1 否则是result2
android:text="@{true ? String.valueOf(5) : String.valueOf(3)}"
结果 5
(9) 判断是否为空的表达式 ??
result1 ?? result2 如果result1为null 则取result2值 否则取result1的值
android:text="@{null??String.valueOf(3)}"
结果为:3
(10) 可以使用 instanceof
判断是否是某个类型 可以使用 cast
进行类型转换
(11) 可以调用某个类的方法
(12) 可以访问类的成员变量
**(13) 可以使用[] **
(14)可以使用 字符 字符串 null等
(15),但是需要注意的是 表达式中不能使用 this super new 以及显示通用调用
3 特殊用法
(1) 前面我们说好 可以导入集合以及使用集合,但是我们需要注意 导入集合时<需要使用转义字符<
,而且在调用集合的值的时候 我们需要使用[] 而不是get,比如:
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
(2) TextView的一些注意事项
在布局文件中 TextView不能直接设置bean类的int属性 否则报错
`android.content.res.Resources$NotFoundException: String resource ID #0x14`
正确做法是:先将int转换为string类型
android:text="@{Integer.toString(user.age)}"
android:text="@{String.valueOf(user.age)}"
Binding 拼接字符串 不支持中文硬编码拼接 应该在string.xml中定义
官方提供了一种方式
android:transitionName='@{"image_" + id}' //运行没问题 (使用英文拼接字符串)
android:transitionName='@{"图片" + id}' //运行报错(使用中文拼接字符串) 错误信息如下:
错误信息:
Caused by: com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException: 3 字节的 UTF-8 序列的字节 3 无效
解决方式:在string.xml中定义:
岁
android:text='@{user.age+@string/age}'
或者:
%1$d岁
android:text='@{@string/ageDes(user.age)}'
dataBinding 不仅仅可以设置属性值 还可以处理事件 而处理事件分为两种方式
方法引用
监听器绑定
如下是对View的onClick事件进行绑定处理的写法
TestActivity.kt
...
fun viewClick(view: View) {
Log.d("===","===viewClick=")
}
...
...
//方法引用
//监听器绑定
方法引用实现方式是:当我们设置一个方法引用时,在编译期间 dataBinding 会为我们直接生成一个 继承自OnClickListener的OnClickListenerImpl类,如下:
// Listener Stub Implementations
public static class OnClickListenerImpl implements android.view.View.OnClickListener{
private com.wkkun.jetpack.TestActivity value;
public OnClickListenerImpl setValue(com.wkkun.jetpack.TestActivity value) {
this.value = value;
return value == null ? null : this;
}
@Override
public void onClick(android.view.View arg0) {
this.value.viewClick(arg0); //1
}
}
当我们在绑定数据的时候,执行如下两个操作
1,生成OnClickListenerImpl实例,注意这里dataBinding自动做了防空判断 如果变量为null 最后view设置事件时会设置为null 如:view.setOnClickListener(null)
OnClickListenerImpl activityViewClickAndroidViewViewOnClickListener =null
...
if (activity != null) {
// read activity::viewClick
activityViewClickAndroidViewViewOnClickListener = (((mActivityViewClickAndroidViewViewOnClickListener == null) ? (mActivityViewClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mActivityViewClickAndroidViewViewOnClickListener).setValue(activity));
}
2,给View设置onClick事件
this.mboundView1.setOnClickListener(activityViewClickAndroidViewViewOnClickListener);
总结:方法绑定 是在编译期间为我们创建了监听事件 并且如果我们传入的方法的签名和 监听器的签名不一致 会直接报错(上述的代码标记1处)
监听器绑定是在 事件触发的时候,生成绑定监听器 并触发 dataBinding 中我们写入的逻辑表达式
如下监听器绑定:
dataBinding 最终生成的编译代码是:
1首先生成 OnClickListener的实现类 该类会最终设置给View
public final class OnClickListener implements android.view.View.OnClickListener {
final Listener mListener;
final int mSourceId;
public OnClickListener(Listener listener, int sourceId) {
mListener = listener;
mSourceId = sourceId;
}
@Override
public void onClick(android.view.View callbackArg_0) {
mListener._internalCallbackOnClick(mSourceId , callbackArg_0);
}
public interface Listener {
void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0);
}
}
2,生成监听类 生成的DataBinding(该类后面会说到)类继承 Listener 并实现_internalCallbackOnClick方法
mCallback2 = new com.wkkun.jetpack.generated.callback.OnClickListener(this, 1);
实现的_internalCallbackOnClick方法如下:
// callback impls
public final void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0) {
// localize variables for thread safety
// activity != null
boolean activityJavaLangObjectNull = false;
// activity
com.wkkun.jetpack.TestActivity activity = mActivity;
activityJavaLangObjectNull = (activity) != (null);
if (activityJavaLangObjectNull) {
//如果变量不为空 则只需变量的方法
activity.viewClick(callbackArg_0);
}
}
3,给View设置监听类
this.mboundView2.setOnClickListener(mCallback2);
上面的逻辑 总的来说是 生成2个监听器 一个是监听View的分发事件A 另一个B是包装我们填入的方法 当A触发事件时 再触发B的事件.
也就是说 监听器绑定是在事件触发的时候绑定 而不是在编译器期间绑定,而且监听器绑定必定会创建2个监听类对象
说明: 在监听器绑定中 android:onClick="@{(view)->activity.viewClick(view)}" //监听器绑定
activity的viewClick的方法签名可以是任意的 比如:
android:onClick="@{(view)->activity.viewClick()}
//假设task是传入的其他变量
android:onClick="@{(view)->activity.viewClick(task)}
//假设A B C ...是传入的其他变量
android:onClick="@{(view)->activity.viewClick(A,B,C,..)}
而且(view)->{} 中的变量名任意 也可以不写 这个是监听的VIew事件传递的参数 可以接受也可以不接受
总结:
dataBinding为每一个布局文件生成一个绑定类,该类的命名规则是 首先将布局文件的名称取消下划线 再按照驼峰命名规则命名 最后后面拼接Binding,比如布局文件的名称是activity_main.xml,因此生成的相应类是ActivityMainBinding,该类包含从布局属性(例如,用户变量)到布局视图的所有绑定,并且知道如何为绑定表达式赋值,上面我们说到监听器绑定默认实现监听器的类就是这个DataBinding类
生成DataBinding类的方式有两种:
方式一,使用 DataBindingUtil
activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userInfoBinding =
DataBindingUtil.setContentView(this,R.layout.activity_user_info)
userInfoBinding.user = UserBean("我是用户名",20,"男","https://www.baidu.com")
}
方式二 Binding生成类 然后调用 inflate方法
activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userInfoBinding =ActivityUserInfoBinding.inflate(layoutInflater)
setContentView(userInfoBinding.root)
userInfoBinding.user = UserBean("我是用户名",20,"男","https://www.baidu.com")
}
userInfoBinding.root//是指Binding生成类 绑定的根布局
上面是生成Activity的DataBinding类, 在生成Fragment或者是List RecyclerView的DataBinding类如下:
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
如果我们想要为DataBinding类的变量赋值 则直接 DataBinding.变量名 = … 即可比如:
activityUserInfoBinding?.user = user
activityUserInfoBinding?.age = 10086
DataBinding类会绑定所有有ID的视图,所有如果我们想要引用某个视图可以直接引用
activityUserInfoBinding.tv
如果我们想要引用根布局 则直接 activityUserInfoBinding.root 即可,根布局不需要ID也可以引用
现在我们已经知道如何生成DataBinding,如何编写 布局文件 如何为控件赋值 则简单的写个实例:
TestActivity.kt
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val activityTestBinding =
DataBindingUtil.setContentView(this, R.layout.activity_test)
activityTestBinding.activity = this
activityTestBinding.age = 10086
activityTestBinding.data= Data("小明")
activityTestBinding.gender="男"
}
fun viewClick(view: View) {
Log.d("==","点击了我")
}
}
布局文件
Bean类
public class Data {
public Data(String name){
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
显示结果是:不截图了
10086
3
小明
这里是简单实现了 从数据到View的显示,但是我们并没有实现绑定,绑定是一个变化,另一个也跟着变化,但是上面的代码显然无法实现 数据变化 View也跟着变化 View变化数据也跟着变化的,
如果要实现上面的绑定 则需要使用Observable 实现单项绑定和双向绑定 请看下篇Databinding2
Databinding2-传送门