数据绑定库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
使用DataBinding之前:
findViewById<TextView>(R.id.sample_text).apply {
text = viewModel.userName
}
使用DataBinding之后:
<TextView
android:text="@{viewmodel.userName}" />
如上在布局中使用@{}
进行单向绑定,就完成了之前使用代码查找Textview再设置值的操作。
视图绑定、数据驱动、事件处理,减少模板代码,增加代码及逻辑清晰度,提高开发效率和维护效率;
自动空检测,避免空指针异常;
防止内存泄露,提高应用性能
DataBinding支持在Android OS 4.0 及以上使用;支持Android Gradle Plugin 1.5及以上,但最好使用最新的plugin版本。
哪个moudle使用,就在哪个moudle的build.gradle中添加如下代码即可使用DataBinding:
android {
...
dataBinding {
enabled = true
}
}
数据类
// 使用Kotlin中的data关键字创建User数据类
data class User(val firstName: String,val lastName: String)
布局文件
打开布局文件,选中根布局,按住 Alt + 回车键
,点击Convert to data binding layout
,就可以转换为DataBinding 的布局格式
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.databindingsample.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
DataBinding布局文件略有不同,以layout根标记开始,然后是数据元素和我们之前的视图根元素:
@{}
设置text为user.firstName@{}
设置text为user.lastNamerebuild project会生成相关的bind类, 绑定类名称根据布局文件生成,activity_main.xml则生成ActivityMainBinding.java
该类包含所有布局属性到布局视图的绑定,自动生成了对应的getter/setter方法
public abstract class ActivityMainBinding extends ViewDataBinding {
@Bindable
protected User mUser;
protected ActivityMainBinding(DataBindingComponent _bindingComponent, View _root,
int _localFieldCount) {
super(_bindingComponent, _root, _localFieldCount);
}
public abstract void setUser(@Nullable User user);
@Nullable
public User getUser() {
return mUser;
}
……
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
// 通过DataBindingUtil设置布局文件
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
// 通过生成的绑定类,给布局中的user变量赋值
// 相当于binding.setUser(new User("Test","User"))
binding.user = User("Test","User")
}
}
运行后显示效果如下:
上面说明了绑定类的生成规则,activity_main.xml则生成ActivityMainBinding.java,下面我们来自定义绑定类的名称:
……
……
……
……
DataBinding 支持在布局文件中使用以下运算符、表达式和关键字
目前不支持以下操作
其他:
android:text="@{user.lastName}"
生成的数据绑定代码自动检查空值并避免空指针异常。如果user为null ,那么 user.name默认值为null ; 如果有类似于age字段int类型,user为null, 则user.age默认值为0
答案就在自动生成的ActivityMainBindingImpl.java中:
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
// 1. 设置默认值
java.lang.String userFirstName = null;
int userAge = 0;
com.example.databindingsample.User user = mUser;
if ((dirtyFlags & 0x3L) != 0) {
// 2. 判空
if (user != null) {
// read user.firstName
userFirstName = user.getFirstName();
// read user.age
userAge = user.getAge();
}
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
// 3. 赋值
androidx.databinding.adapters.TextViewBindingAdapter
.setText(this.mboundView1, userFirstName);
this.mboundView2.setText(userAge);
}
}
??
空合并运算符, 左操作数不为空,就使用左操作数,为空则使用右操作数android:text="@{user.displayName ?? user.lastName}"
相当于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
[]
集合引用<data>
<!-- java.lang包外的需要导包,或者使用全类名 -->
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
在xml中< 需要转义,所以List
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`]}"
可以使用单引号包围属性值,在表达式中使用双引号;或者使用双引号包围属性值,在表达式里使用反单引号 **` **
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
有些资源引用需要用显示类型,如下:
Type | Normal reference | Expression reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
目前发现不支持:@mipmap/
当表达式求值为方法引用时,数据绑定将方法引用和所有者对象包装在侦听器中,并在目标视图上设置该侦听器。如果表达式计算结果为null,则数据绑定不会创建侦听器,而是设置一个空侦听器。
class MyHandler{
fun customOnClick(v: View){
Toast.makeText(v.context,"点击成功", Toast.LENGTH_LONG).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
binding.user = User("Test","User",100)
binding.myHandler = MyHandler()
}
<data>
<variable
name="myHandler"
type="com.example.databindingsample.MyHandler" />
data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{myHandler::customOnClick}"
android:text="@{String.valueOf(user.age)}" />
在表达式中,可以引用方法,但该方法除了方法名(权限修饰符、返回值类型、形参列表)都要和真实的侦听器方法保持一致。
例如上面的代码,方法名可以随便定义,但参数一定要和OnClickListener中的onClick(View v)方法保持一致,比如我把自定义方法中的View参数去除,编译报错如下
错误: 找不到符号
import com.example.databindingsample.databinding.ActivityMainBindingImpl;
^
符号: 类 ActivityMainBindingImpl
位置: 程序包 com.example.databindingsample.databinding
* What went wrong:
Execution failed for task ':databindingsample:compileDebugJavaWithJavac'.
> android.databinding.tool.util.LoggedErrorException: Found data binding errors.
****/ data binding error ****msg:Listener class android.view.View.OnClickListener with method onClick did not match signature of any method myHandler::customOnClick
与方法引用不同,在侦听器绑定中,只需要返回值和真实的侦听器的返回值保持一致即可。
fun onClick(v: View,user: User){
val textView = v as TextView
Toast.makeText(mContext,"点击了" + textView.text, Toast.LENGTH_LONG).show()
}
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{(tvName) -> tvName.isEnabled() ? myHandler.onClick(tvName,user) : void }"/>
Lambda表达式左侧的参数,是以真实的侦听器为准的
import使我们在xml中更容易引用,如下:
// 导入前
<variable
name="user"
type="com.example.databindingsample.User" />
<variable
name="user2"
type="com.example.databindingsample.User" />
// 导入后
<import type="com.example.databindingsample.User" />
<variable
name="user"
type="User" />
<variable
name="user2"
type="User" />
类名相同时,import也支持别名
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
variable声明变量上面已经用了很多次了,我们在标签里声明的variable都会在生成从bind类中生成对应的getter/setter方法,并且设置了默认值
includes
在activity_main.xml中增加include标签引入布局,并传递数据
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.example.databindingsample.User" />
<variable
name="user"
type="User" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
layout="@layout/test"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center"
bind:user="@{user}" />
LinearLayout>
layout>
test.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.example.databindingsample.User" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimaryDark"
android:gravity="center"
android:text="@{user.lastName}"
android:textColor="@android:color/white"
android:textSize="20sp" />
LinearLayout>
layout>
如上,即可在test.xml中使用user变量
但下面这种
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
merge>
layout>
参考资料: