Android架构之DataBinding(一)
DataBinding的意义和优势
我们知道,一般对控件进行操作,都是通过UI控件的id找到控件,接在再页面中通过代码对控件进行操作。 比如以下代码通过findViewById()来查找TextView控件并将其绑定到viewModel变量的userName属性.
TextView textView = findViewById(R.id.sample_text);
textView.setText(viewModel.getUserName());
可以说,在页面中有很多这样类似的代码,使得页面的代码越来越多。为了减轻页面的工作量。 Google提出了DataBinding.DataBinding的出现让布局文件承担了部分原本属于页面的工作,使得页面与布局文件之间的耦合度进一步降低。比如下面代码:
<TextView
android:text="@{viewmodel.userName}" />
直接就将viewModel变量的userName将其和TextView控件绑定在一起。
DataBinding具有如下几点优势:
- 项目更简洁,可读性更高。部分与UI控件相关的代码可以在布局文件中完成。
- 不再需要findViewById()方法,大量减少了Activity中的代码
- 布局文件可以包含简单的业务逻辑。UI控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互。
- 可以提高应用性能,有利于防止内存泄露以及避免发送Null指针异常.
- DataBinding更好地实现了MVVM架构。
DataBinding的简单绑定
现在我们有这样一个需求,在Activity中通过3个TextView控件,分别展示Book对象中的3个字段。
原始的数据绑定方式
1.布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBinding.DataBindingActivity">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="好看的女人"
android:textSize="25sp"
/>
<TextView
android:id="@+id/tvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小鑫啊"
android:textSize="25sp"
/>
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="8"
android:textSize="25sp"
/>
</LinearLayout>
2.Book对象
public class Book {
public String title;
public String author;
public int rating;
public Book(String title, String author, int rating) {
this.title = title;
this.author = author;
this.rating = rating;
}
}
3.Activity页面
public class DataBindingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_data_binding);
TextView tvTitle = findViewById(R.id.tvTitle);
TextView tvAuthor = findViewById(R.id.tvAuthor);
TextView tvRating = findViewById(R.id.tvRating);
Book book = new Book("好看的女人","小鑫",8);
tvTitle.setText(book.title);
tvAuthor.setText(book.author);
tvRating.setText(book.rating);
}
}
是不是感觉很臃肿。接下来让我们看一看采用DataBinding是如何完成的。
使用DataBinding完成
1.在app的build.gradle中启用数据绑定
android {
buildFeatures{
dataBinding = true
// for view binding :
// viewBinding = true
}
}
2.修改布局文件
在布局文件外层加入标签。 切记是在整个布局文件的外层添加标签
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingActivity">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp" />
<TextView
android:id="@+id/tvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp" />
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp" />
</LinearLayout>
</layout>
我们所做的修改仅仅是在UI布局最外层加上了一个标签,并将命名控件xmls从移到了标签中.
重新rebuild该项目。 DataBinding库会自动生成绑定该布局文件所需要的类。 即ActivityDataBindingBinding类
实例化布局文件
我们可以通过DataBindingUtil.setContentView()方法实例化布局文件。 该方法返回实例化后的布局文件对象,名字与布局文件的名字一致,并在后面加上了Binding. 比如说,如布局文件名未activity_main,则实例化后的布局文件类名ActivityMainBinding
本例的布局文件名: activity_data_binding.xml因此,对应的Binding类为ActivityDataBindingBinding
代码如下:
ActivityDataBindingBinding activityDataBindingBinding =
DataBindingUtil.setContentView(this,
R.layout.activity_data_binding);
将数据传递到布局文件
在绑定布局文件后,我们需要将数据传递给布局文件。首先,在布局文件中定义布局变量,布局变量指定对象的类型和名字,名字可以自定义。布局变量需要定义在标签中.
<data>
<variable
name="book"
type="com.example.jetpack.Book" />
</data>
接着我们便可以在Activity中,通过setVariable()方法,将Book对象传递给布局文件的布局变量了。
Book book = new Book("好看的人","小鑫啊",20);
activityDataBindingBinding.setVariable(BR.book,book);
注意,这里的BR类似于Android项目中常见的R类,是由DataBinding库自动生成的,用于统一存放所有布局变量的id;
DataBinding为了方便我们使用,为布局变量提供了Setter类。因此我们也可以直接使用setBook()方法,将Book对象传递给布局文件中对应的布局变量.
activityDataBindingBinding.setBook(book);
注意: 标签用于放置布局文件中各个UI控件所需要的所有数据,这些数据类型可以是自定义类型,如示例代码中的Book类,也可以是基本类型,如String
<variable
name="message"
type="String" />
绑定布局变量与成员变量
Book对象已经被我们传递到布局文件中了,此时,我们便可以将UI控件与Book对象的属性进行绑定。为此,DataBinding库提供了布局表达式,布局表达式以@{}的格式作为属性的值存在.
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_
android:layout_height="wrap
android:text="@{book.title}
android:textSize="25sp" />
绑定后,我们就不再需要在Activity中通过setText()方法对UI控件进行赋值了.
在布局文件中引用静态类
有时我们需要在布局文件中引用一些Java工具类,帮助我们处理一些简单的逻辑。例如,需要一个打分工具类,帮助我们将数字转换为中文
打分工具类
public class BookRatingUtil {
public static String getRatingString(int rating){
switch (rating){
case 0:
return "零星";
case 1:
return "一星";
case 2:
return "二星";
case 3:
return "三星";
case 4:
return "四星";
case 5:
return "五星";
}
return "小心心";
}
}
我们可以在布局文件中通过标签导入静态工具类
<data>
<import type="com.example.jetpack.BookRatingUtil"/>
</data>
接着在UI控件中调用静态方法
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{BookRatingUtil.getRatingString(book.rating)}"
android:textSize="25sp" />
最终的布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="book"
type="com.example.jetpack.Book" />
<import type="com.example.jetpack.BookRatingUtil"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingActivity">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.title}"
android:textSize="25sp" />
<TextView
android:id="@+id/tvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.author}"
android:textSize="25sp" />
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{BookRatingUtil.getRatingString(book.rating)}"
android:textSize="25sp" />
</LinearLayout>
</layout>
最终的Activity文件如下
public class DataBindingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityDataBindingBinding activityDataBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding);
Book book = new Book("好看的人","小鑫啊",20);
activityDataBindingBinding.setVariable(BR.book,book);
activityDataBindingBinding.setBook(book);
}
}
结果如下:
是不是发现使用了DataBinding之后,降低了页面和布局文件中的耦合度。接下来让我们深入了解下表达式的具体用法吧。
表达式语言
上面我们已经简单介绍过, 在布局文件中是通过**@{}**语法写入特性属性中。比如说:TextView文本被设置为Book变量的title属性
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.title}" />
但是表达式的功能不止如此,我们可以在表达式语言中使用以下运算符和关键字。
- 算术运算符 + - / * %
- 字符串连接运算符 +
- 逻辑运算符 && ||
- 二元运算符 & | ^
- 一元运算符 + - ! ~
- 移位运算符 >> >>> <<
- 比较运算符 == > < >= <=(请注意,< 需要转义为 <)
- instanceof
- 分组运算符 ()
- 字面量运算符 - 字符、字符串、数字、null
- 类型转换
- 方法调用
- 字段访问
- 数组访问 []
- 三元运算符 ?:
示例如下:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
Null合并运算符
如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。
android:text="@{user.displayName ?? user.lastName}"
这在功能上等效于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用
表达式可以使用以下格式在类中引用属性,这对于字段、getter 和 ObservableField 对象都一样:
android:text="@{user.lastName}"
就跟我们上面用法一致,ObservableField对象后面我们会讲到。
避免出现 Null 指针异常
生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。
视图引用
表达式可以通过以下语法按 ID 引用布局中的其他视图:
android:text="@{exampleText.text}"
在以下示例中,TextView 视图引用同一布局中的 EditText 视图:
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
注意: 绑定类将ID转换为驼峰式大小写。
集合
为方便起见,可使用 [] 运算符访问常见集合,例如数组、列表、稀疏列表和映射。
<data>
<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 形式,而是必须写成 List<String>。
您还可以使用 object.key 表示法在映射中引用值。例如,以上示例中的 @{map[key]} 可替换为 @{map.key}。
DataBinding响应事件
DataBinding事件绑定可运用于多种回调事件,这里我们通过Button,演示DataBinding如何响应OnClick事件
1.编写EventHandleListener类,用于接收和响应Button的onClick事件。Button被单击后,显示一个Toast;
public class EventHandlerListener {
private Context context;
public EventHandlerListener(Context context) {
this.context = context;
}
//点击Button会触发这个方法
public void onButtonClicked(){
Toast.makeText(context, "小鑫好好看啊!!", Toast.LENGTH_SHORT).show();
}
}
2.在Activity中绑定布局文件,接着实例化EventHandleListener类,并传入布局文件
public class DataBindingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityDataBindingBinding activityDataBindingBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding);
activityDataBindingBinding.setEventHandler(new EventHandlerListener(this));
}
}
3.通过布局表达式,调用EventHandleListener中的方法
android:onClick="@{()->EventHandler.onButtonClicked()}
完整的布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="EventHandler"
type="com.example.jetpack.EventHandlerListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
tools:context=".DataBindingActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CLick Me"
android:onClick="@{()->EventHandler.onButtonClicked()}"/>
</LinearLayout>
</layout>
运行程序,效果如下:
在上面的示例中,我们尚未定义传递给onClick(View)的view参数,比如我们想点击Button之后,Button的透明度变为0.5f,那么就需要用到View参数.
修改onButtonClicked方法,增加一个View参数
//点击Button会触发这个方法
public void onButtonClicked(View view){
Button button= (Button) view;
button.setAlpha(0.5f);
Toast.makeText(context, "小鑫好好看啊!!", Toast.LENGTH_SHORT).show();
}
点击时,将View参数传递到点击方法中
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CLick Me"
android:onClick="@{(theView)->EventHandler.onButtonClicked(theView)}"/>
theView可以随便命名,根据自己的喜好。
导入、变量和包含
数据绑定库提供了导入、变量和包含等功能。通过导入功能,可以轻松地在布局中引用类。通过变量功能,可以描述在绑定表达式中使用的属性。通过包含功能,可以在整个应用中重复使用复杂的布局。
导入
导入其实就跟使用类方法差不多。
通过导入功能,您可以轻松地在布局文件中引用类,就像在托管代码中一样。您可以在 data 元素使用多个 import 元素,也可以不使用。以下代码示例将 View 类导入到布局文件中:
<data>
<import type="android.view.View"/>
</data>
导入 View 类可让您通过绑定表达式引用该类。以下示例展示了如何引用 View 类的 VISIBLE 和 GONE
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
类型别名
当类名有冲突时,其中一个类可使用别名重命名。以下示例将 com.example.real.estate 软件包中的 View 类重命名为 Vista:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
您可以在布局文件中使用 Vista 引用 com.example.real.estate.View,使用 View 引用 android.view.View。
导入其他类
导入的类型可用作变量和表达式中的类型引用。以下示例显示了用作变量类型的 User 和 List:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
您还可以使用导入的类型来对表达式的一部分进行类型转换。以下示例将 connection 属性强制转换为类型 User:
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
使用类方法
上面我们已经使用过类方法,就是那个打分工具类。 与View OnClick特性相比,类方法是表达式在编译时就会进行处理,因此,如果该方法不存在或其签名不正确,则会收到编译错误。
1.首先定义一个静态方法:
public class BookRatingUtil {
public static String getTemp(int num){
return "小鑫"+num;
}
}
2.在data标签中导入该工具类
<import type="com.example.jetpack.BookRatingUtil"/>
3.在控件中使用方法
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{BookRatingUtil.getTemp(5)}"/>
变量
变量就是我们上面使用 标签引入的属性。使用很简单,这里就不多说了。 注意:变量类型会在编译时进行检查,大家小心使用。
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
包含
对于include进行引用的布局文件,我们称为二级页面。DataBinding也支持从一级页面将数据传递到二级页面.
一级页面传递数据A.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.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#acc">
<include
layout="@layout/B"
bind:userInfo="@{user}" />
</LinearLayout>
</layout>
二级页面接收数据B.xml
在二级页面B.xml中,需要定义一个与一级页面相同的布局变量,用于接收传递过来的数据,从而使两个布局文件之间共享同一个变量。在收到user对象后,便可以将user对象中的字段与UI控件进行绑定了。代码如下:
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Main6Activity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
这篇文章也有点长了,DataBinding讲解先到这里,剩下的知识下节我们继续。不足之处,望大家指出来,谢谢大家。
上一篇: 输入三个数按从小到大的顺序输出