原文首发于微信公众号:躬行之(jzman-blog)
前面总结了 ViewModel、LiveData 及 Lifecycle 架构组件的使用,可先阅读下面文章详细了解:
本篇主要侧重 dataBinding 的基本使用,主要内容如下:
使用 dataBinding 需要在 app module 下面的 build.gradle 文件中进行配置,具体如下:
//设置支持dataBinding dataBinding { enabled = true } 复制代码
Data Binding Library 会自动生成将布局中的视图和数据对象绑定所需要的类,Data Binding Library 的布局文件中以 layout 标签为根标签,然后是具体的数据元素和视图元素,此视图元素是绑定布局文件的位置,布局文件参考如下:
<?xml version="1.0" encoding="utf-8"?> <!--dataBinding必须以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"> <!--dataBinding中具体属性值的配置在"@{}"中进行配置--> <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。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="java.util.Map" /> <import type="java.util.List" /> <!--Map--> <variable name="map" type="Map<String,String>" /> <variable name="key" type="String" /> <!--List--> <variable name="list" type="List<String>" /> <variable name="index" type="int" /> </data> <!--注意Map和List取值方式--> <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 事件为例,具体如下:
<?xml version="1.0" encoding="utf-8"?> <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 事件为例,具体如下:
<?xml version="1.0" encoding="utf-8"?> <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 文件,具体如下:
<?xml version="1.0" encoding="utf-8"?> <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> 复制代码
然后,在主布局中引用这个布局,具体如下:
<?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.manu.databindsample.data.User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!--bind绑定变量--> <include layout="@layout/test_layout" bind:userTest="@{user}" /> </LinearLayout> </layout> 复制代码
这样通过 bind 就绑定了两个布局中使用到的 User 类型的变量,使得两个布局中使用的变量是同一个变量,此外,databinding 不支持 merge 标签,下篇继续 Binding adapters 的介绍。