目录
简介
DataBinding 是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。库是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
借助布局文件中的绑定组件,您可以移除 Activity 中的许多界面框架调用,使其维护起来更简单、方便。还可以提高应用性能,并且有助于防止内存泄漏以及避免发生 Null 指针异常。
开始使用:
DataBinding 不但灵活而且兼容性广。因此大家可以在Android 4.0(API 级别 14 )的时候进行使用。
如果想要使用 DataBinding,那么大家就需要在Module 的build.gradle 中添加
android {
dataBinding {
enabled = true
}
}
或者
android {
dataBinding.enabled = true
}
如何使用(介绍)
如果需要使用DataBinding ,那么大家就需要在xml 布局中符合如下的规则:
- 使用 <layout> 包裹整体的内容
- 使用<data>包裹自己所需要的数据
如:
<?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">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
在代码中生成绑定类绑定布局的方式有两种:
- 使用 DataBindingUtil 的方式
- 使用 XMLBinding 方式
布局和绑定表达式
布局
与普通的xml不同,DataBinding布局文件以根标记 layout 开头,后跟 data 元素和 view 根元素。
<layout> 标签的内容
<layout> 标签中盛放xml文件中的命名空间。如:
- 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> 标签中盛放数据类型。如:
string 数据类型可以使用如下方式导入
- <import type=“String” alias=“str”/>
- <variable name=“strs” type=“String” />
<data> 标签中内容详解
<import> 中的type 属于 要导入的名称, alias 属于自己定义的别名,可以在布局中进行使用和类目相同的类进行区分。
<variable> 中的 name 表示在要在布局中使用的名称,和上面的别名相同。
当然 <import> 仅仅是导入使用的类型,如果想要色湖之数据还是需要在 <variable> 进行设置。
view 根元素
这里使用的View 根标签和我们使用的正常布局一样。
生成绑定类绑定布局
首先我们要知道,系统会为所有使用<layout>的布局生成一个绑定类,格式为布局文件的名称转换为 Pascal 大小写形式并在末尾添加 Binding 后缀。如activity_main.xml 生成的类就是ActivityMainBinding。
生成的绑定类将布局与布局中的视图关联起来。这些类都是从ViewDataBinding 继承而来的。
在上面我们介绍了代码中绑定布局的方式有两种,现在我们就介绍一下。
使用DataBindingUtil 的方式
如图我们可以看见DataBindingUtil 中可以使用 inflate 、bind 、setContentVIew 的方式绑定布局。
其中:
inflate :可以在任意地方进行使用,一般在Fragment / recyclerview 等地方进行使用
bind : 可以在任意地方进行使用。
setContentVIew : 只可以在Activity 中进行使用。
使用 xmlBinding 的方式
xmlBinding 和上面的DataBindingUtil 相似。
只不过如果使用 xmlBinding 之后必须要使用 setContentView (Activity 中)重新设置数据。
开发中使用哪种?
我个人推荐使用 DataBIndingUtil 的方式。大家都知道如果要写项目,那么就会抽取出Base 类,所以我们为了使用的方便一般就自己对于setCOntentView方法进行各种设置。比如我的BaseActviity。
abstract class BaseActviity<T : ViewDataBinding> : AppCompatActivity() {
var mBInding: T? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBInding = DataBindingUtil.setContentView(this,getLayoutIds())
}
abstract fun getLayoutIds(): Int
}
这样我们就可以在自己的类中使用统一的方式进行设置。
绑定表达式
在布局中设置动态属性
如果有一个 User 类,类中有name 和 age 两个属性,那么我们在布局中设置一个textview 的name 为user的name我们可以在布局中直接使用。
<?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.chaunzhi.test.User"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
app:layout_constraintTop_toTopOf="parent"
android:text="@{user.name}"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
当然,我们这里只是将数据类型添加到了布局中,但是布局仍然不知道内部的数据是上面,所以我们需要对于数据和布局进行绑定。
如,下面的例子:
val mBinding = setContentView<ActivityMainBinding>(this, R.layout.activity_main)
mBinding.user = User("蔡依林",22)
表达式语言
常见的表达式语法
表达式语言与托管代码中的表达式非常相似。您可以在表达式语言中使用以下运算符和关键字:
- 算术运算符 + - / * %
- 逻辑运算符 && ||
- 移位运算符 >> >>> <<
- 一元运算符 + - ! ~
- 二元运算符 & | ^
- 三元运算符 ?:
- instanceof
- 分组运算符 ()
- 字符串连接运算符 +
- 字面量运算符 - 字符、字符串、数字、null
- 类型转换
- 方法调用
- 字段访问
- 数组访问 []
缺少的运算
- this
- super
- new
- 显式泛型调用
Null 合并运算符
如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 ,则选择右边运算数
android:text="@{user.displayName ?? user.lastName}"
这在功能上 等效于 三元运算符
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用
这个功能就是在上面的时候进行使用的@{}的方式,这里不再次进行介绍。
避免出现NULL指针异常
生成的数据绑定类会自动检查是否有null 值,并且避免空指针异常。例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。
集合
为方便起见,可使用 [] 运算符访问常见集合,例如数组、列表、稀疏列表和映射。
注意:要使 XML 不含语法错误,您必须转义 < 字符。例如:不要写成 List<String> 形式,而是必须写成 List <;String>。
您还可以使用 object.key 表示法在映射中引用值。例如, @{map[key]} 可替换为 @{map.key}。
字符串字面量
您可以使用单引号括住特性值,这样就可以在表达式中使用双引号,如以下示例所示
android:text='@{map["firstName"]}'
也可以使用双引号括住特性值。如果这样做,则还应使用反单引号 ` 将字符串字面量括起来:
android:text="@{map[`firstName`]}"
资源
您可以使用以下语法访问表达式中的资源:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式字符串和复数形式可通过提供参数进行求值:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
当一个复数带有多个参数时,应传递所有参数:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
某些资源需要显式类型求值,如下表所示:
类型 | 常规引用 | 表达式引用 |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
事件处理
通过数据绑定,您可以编写从视图分派的表达式处理事件(例如,onClick() 方法)。事件特性名称由监听器方法的名称确定,但有一些例外情况。例如,View.OnClickListener 有一个 onClick() 方法,所以该事件的特性为 android:onClick。
有一些专门针对点击事件的事件处理脚本,这些处理脚本需要使用除 android:onClick 以外的特性来避免冲突。您可以使用以下特性来避免这些类型的冲突
类 | Listener setter | 特性 |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
当然这些事件我们也可以自己进行定义,我们之后再说。
处理事件的方式
**方法引用:**在表达式中,您可以引用符合监听器方法签名的方法。当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。
**监听器绑定:**这些是在事件发生时进行求值的 lambda 表达式。数据绑定始终会创建一个要在视图上设置的监听器。事件被分派后,监听器会对 lambda 表达式进行求值。
方法引用
事件可以直接绑定到处理程序方法,类似于android:onClick可以分配给活动中的方法…
使用方式:
1… 定有一个类,类中有一个需要传递view 的方法
open class Click {
fun ztoastClcik(view : View){
}
}
- 在xml 中进行设置:在这里通过 @{类名(自己设置的variable 中的name):: 方法名称}
<?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="click"
type="com.chaunzhi.test.Click" />
<import type="com.chaunzhi.test.Utils" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{click::toastClcik}"
android:text="click"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
- 在代码中进行设置
mBinding.click = Click()
方法引用大家可以认为是一个官方原生的监听器,里面只可以传递view,不可以传递其他参数。我个人认为局限性太大。
监听器绑定
监听器绑定是在事件发生时运行的绑定表达式。它们类似于方法引用,但允许您运行任意数据绑定表达式。此功能适用于 Gradle 2.0 版及更高版本的 Android Gradle 插件。
使用方式:
- 定义一个类,类中定义一个方法
open class Click {
fun toastClcik(context:Context, msg: String) {
Toast.makeText(context,msg,Toast.LENGTH_SHORT).show()
}
}
- 在xml 文件中进行使用
这里我们使用的方式是 @{(view)->类名.方法名(所传递的参数)}. 我们可一点击进入(view)中发现他调用的其实是
public interface OnClickListener {
void onClick(View var1);
}
当然了,在这里也可以不传递。所传递的参数通过 <variable>进行设置。
<?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="msg"
type="String" />
<variable
name="contexts"
type="android.content.Context" />
<variable
name="click"
type="com.chaunzhi.test.Click" />
<import type="com.chaunzhi.test.Utils" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btns"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(view)->click.toastClcik(contexts,msg)}"
android:text="click"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
3.在代码中进行设置
mBinding.msg = "按钮被点击了"
mBinding.contexts = this
mBinding.click = Click()
需要注意的是,如果您监听的事件返回类型不是 void 的值,则您的表达式也必须返回相同类型的值。
监听器表达式功能非常强大,可以使您的代码非常易于阅读。另一方面,包含复杂表达式的监听器会使您的布局难以阅读和维护。这些表达式应该像将可用数据从界面传递到回调方法一样简单。您应该在从监听器表达式调用的回调方法中实现任何业务逻辑。
方法引用和监听器绑定之间的区别
监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。
导入、变量和包含
导入
通过导入功能,您可以轻松地在布局文件中引用类,就像在托管代码中一样。您可以在 data 元素使用多个 import 元素,也可以不使用。
当类名有冲突时,其中一个类可使用别名重命名。以下示例将 com.example.real.estate 软件包中的 View 类重命名为 Vista:
变量
您可以在 data 元素中使用多个 variable 元素。每个 variable 元素都描述了一个可以在布局上设置、并将在布局文件中的绑定表达式中使用的属性。
变量类型在编译时进行检查,因此,如果变量实现 Observable 或者是可观察集合,则应反映在类型中。如果该变量是不实现 Observable 接口的基类或接口,则变量是“不可观察的”。
如果不同配置(例如横向或纵向)有不同的布局文件,则变量会合并在一起。这些布局文件之间不得存在有冲突的变量定义。
在生成的绑定类中,每个描述的变量都有一个对应的 setter 和 getter。在调用 setter 之前,这些变量一直采用默认的托管代码值,例如引用类型采用 null,int 采用 0,boolean 采用 false,等等。
系统会根据需要生成名为 context 的特殊变量,用于绑定表达式。context 的值是根视图的 getContext() 方法中的 Context 对象。context 变量会被具有该名称的显式变量声明替换。
包含
通过使用应用命名空间和特性中的变量名称,变量可以从包含的布局传递到被包含布局的绑定。
<?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="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
数据绑定不支持 include 作为 merge 元素的直接子元素。