原文首发于微信公众号:躬行之(jzman-blog)
前面总结了 ViewModel、LiveData 及 Lifecycle 架构组件的使用,可先阅读下面文章详细了解:
本篇主要侧重 dataBinding 的基本使用,主要内容如下:
使用 dataBinding 需要在 app module 下面的 build.gradle 文件中进行配置,具体如下:
//设置支持dataBinding
dataBinding {
enabled = true
}
Data Binding Library 会自动生成将布局中的视图和数据对象绑定所需要的类,Data Binding Library 的布局文件中以 layout 标签为根标签,然后是具体的数据元素和视图元素,此视图元素是绑定布局文件的位置,布局文件参考如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.manu.databindsample.data.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.name,default=姓名}"/>
LinearLayout>
layout>
在 “@{user.name}” 中的 name 属性最终映射调用数据对象的 getter 方法,也就是 getter 方法,当然,如果数据对象中有对应的 name 方法,在没有与之对应 getter 方法的时候会调用与之同名的方法,如果两者都存在,则会优先调用与之对应的 getter 方法,参考如下:
/**
* 数据实体
* Powered by jzman.
* Created on 2018/11/28 0028.
*/
public class User {
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
//两者存在优先调用
public String getName() {
return name;
}
//getter方法不存在会调用
public String name() {
return "name";
}
//...
}
dataBinding 会为内个布局文件生成对应的绑定类,默认情况下,类的名称基于布局文件的名称,如布局文件名为 activity_main,则该布局文件对应的绑定类是 ActivityMainBinding,该类包含数据对象到布局文件的所有绑定,那么如何绑定数据和视图呢,在 Activty 中绑定方式如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//生成绑定类
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
//绑定视图与数据
User user = new User("张三");
binding.setUser(user);
}
}
在 Fragment 中绑定方式如下:
//inflate方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
FragmentOneBinding oneBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_one,container,false);
User user = new User("小明");
oneBinding.setUser(user);
return oneBinding.getRoot();
}
//bind方法
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
View view = inflater.inflate(R.layout.fragment_one,container,false);
FragmentOneBinding oneBinding = FragmentOneBinding.bind(view);
User user = new User("小明");
oneBinding.setUser(user);
return view;
}
其他布局的绑定方式基本是都是使用某个生成的绑定类的 inflate 方法和 bind 方法就可以完成。
//完整写法
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
//简写
android:text="@{user.displayName ?? user.lastName}"
生成的绑定类会自动检查 null 值以避免 NullPointerException,在表达式 @ {user.name} 中,如果 user 为 null,则为user.name 分配其默认值 null。 如果引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值0。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="java.util.Map" />
<import type="java.util.List" />
<variable
name="map"
type="Map<String,String>" />
<variable
name="key"
type="String" />
<variable
name="list"
type="List<String>" />
<variable
name="index"
type="int" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map.key}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[0]}" />
LinearLayout>
layout>
对于 Map 类型的数据可以在表达式 @{} 中使用 map.key 来获取 Map 集合中 key 对应的 value 值,List 类型的数据直接使用索引来取值,此外在 variable 标签中使用到的 < 要进行转义,及使用 < 来代替 <,否则报错如下:
> Error: 与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。
如何在 @{} 表达式中使用字符串而不是字符串变量呢,有两种方式,具体如下:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{map["key"]}' />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`key`]}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/app_name}"/>
使用 databinding 时可以采用方法引用或监听绑定的方式来设置事件监听,这两者的区别是前者的事件监听器是在数据绑定时创建的,而后者是在事件触发时绑定。
事件可以直接绑定在事件处理方法上,与普通的 android:onClick 属性相比较,这种配置方式会在编译时进行相关处理,如果该方法不存在或该方法签名不正确,则会收到编译时错误。首先创建一个事件处理方法如下:
/**
* Powered by jzman.
* Created on 2018/11/30 0030.
*/
public class MyHandler {
/**
* @param view
*/
public void onClickEvent(View view){
Toast.makeText(view.getContext(), "click me", Toast.LENGTH_SHORT).show();
}
}
然后,在对应的布局文件中配置具体的 onClick 等事件,这里以 onClick 事件为例,具体如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="handler"
type="com.manu.databindsample.handler.MyHandler"/>
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click me"
android:onClick="@{handler::onClickEvent}"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click me"
android:onClick="@{handler.onClickEvent}"/>
LinearLayout>
layout>
最后,在对应的 Activity 中设置数据对象 handler 即可,具体参考如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
binding.setHandler(new MyHandler());
}
这样通过方法引用事件就成功绑定了。
这种方式是在事件发生时创建事件监听器,相较方法引用可以传递自定义参数在事件回调中,首先,创建一个事件回调方法如下:
/**
* 监听绑定
* Powered by jzman.
* Created on 2018/12/3 0003.
*/
public class MyPresenter {
private Context mContext;
public MyPresenter(Context mContext) {
this.mContext = mContext;
}
public void onClickEvent(User user) {
Toast.makeText(mContext, user.getName(), Toast.LENGTH_SHORT).show();
}
}
然后,在对应的布局文件中配置具体的 onClick 等事件,这里以 onClick 事件为例,具体如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.manu.databindsample.data.User" />
<variable
name="presenter"
type="com.manu.databindsample.handler.MyPresenter" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onClickEvent(user)}"
android:text="click me 3" />
LinearLayout>
layout>
最后,在对应的 Activity 中设置数据对象 presenter 即可,具体参考如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityEventHandlerBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_event_handler);
binding.setUser(new User("android"));
binding.setPresenter(new MyPresenter(this));
}
这样通过事件监听事件就成功绑定了,在上面 xml 中调用事件方法时,可以在配置当前 View,具体如下:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(view) -> presenter.onClickEvent(user)}"
android:text="click me 3" />
则对应的事件回调方法如下:
public class MyPresenter {
public void onClickEvent(View view, User user){}
}
此外,也可以在事件绑定时使用三目运算符,此时可将 void 作为操作符使用,使用方式参考如下:
android:onClick="@{(v) -> v.isVisible() ? presenter.doSomething() : void}"
从前面可知,默认状态下绑定类名称是由布局文件名称决定,那么如何自定义绑定类呢,在布局文件 data 标签上使用 class 属性指定自定义的绑定类名即可,当然也可以在自定义类名前面添加完成的包路径,参考如下:
<!--自定义绑定类-->
<data class="com.manu.test.CustomBinding">
<variable name="user" type="com.manu.databindsample.data.User"/>
</data>
在 databinding 中使用 import 关键字导入相关的类,java.lang.* 下面的相关类默认自动导入,如果有相同名字的 View 可以使用使用 alias 来区分,参考如下:
<import type="android.view.View"/>
<import type="com.manu.View"
alias="MView"/>
使用 variable 关键字定义要在 xml 布局中使用的变量,如果使用了 include 布局,则要使用 bind 绑定 include 包含的布局与主布局使用同样的变量,创建一个 include 包含的布局 test_layout.xml 文件,具体如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="userTest"
type="com.manu.databindsample.data.User" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`this is include content...`+userTest.getName(),default=user}" />
LinearLayout>
layout>
然后,在主布局中引用这个布局,具体如下:
<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.manu.databindsample.data.User" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/test_layout"
bind:userTest="@{user}" />
LinearLayout>
layout>
这样通过 bind 就绑定了两个布局中使用到的 User 类型的变量,使得两个布局中使用的变量是同一个变量,此外,databinding 不支持 merge 标签,下篇继续 Binding adapters 的介绍。