0x1、引言
继续回来学穿Jetpack,带来第三个组件 DataBinding
(数据绑定)。在前面的章节 《【Jetpack】学穿:ViewBinding → 视图绑定》 剥源码的时候就有看到 DataBinding 相关的代码。
ViewBinding(视图绑定) 的作用和原理一言以蔽之:
- 作用 → 代替findViewById 的同时,还能保证 空安全 和 类型安全,且 支持Java;
- 原理 → AGP为模块中的每个XML生成绑定类,本质上还是findViewByid,只是自动生成控件实例,并一一对应;
可以把 ViewBinding 看做 DataBinding 功能的 子集,它有的DataBinding都有,而且还多了 数据绑定。
何为数据绑定? 在维基百科中的定义如下:
是将 "提供器" 的数据源与 "消费者" 绑定并使其同步的一种通用技术。通常用两种不同语言的数据/信息源完成,如XML数据绑定。在UI数据绑定中,相同语言但不同逻辑功能的数据与信息对象被绑定在一起(例如Java UI元素到Java对象)。在数据绑定过程中,每个数据更改会由绑定到数据的元素自动反射。术语"数据绑定"也指一个外部数据表示随元素更改产生变化,并且底层数据自动更新以反映此更改。
又长又臭,举个简单例子就秒懂了:
一个存储数量的变量count,一个显示数量的TextView,两者绑定,当修改count的值时,TextView自动刷新。
数据源(Model)更新,绑定视图(View) 自动更新
,不用开发仔再去手动setXxx(),道理就这么简单。
这种玩法又叫 单向绑定
,还有一种 双向绑定
,绑定视图发生改变时,数据源也跟着改变,比如:
点击显示数量的TextView,显示的数量自增1,存储数量的变量也自增1。
互相影响,这就是双向绑定。咳...都是些浅显的概念,具体怎么做?
用 观察者模式 实现,数据变量与View实例关联,数据变量有更新时,遍历回调关联View实例对应设置值的方法。
自己造轮子,可以,但Duck不必~
Jetpack库中的 DataBinding组件
已经封装好一套了,要做的就是熟读文档,然后大胆使用~
API变化日新月异,建议以官方文档为准《数据绑定库》,本文也是基于此文档展开的学习。
0x2、写个最简单的例子
通过一个超简单的例子来帮助大家了解DataBinding,先有基础认知,再往下学就容易多了。
① 启用DataBinding
DataBinding与AGP捆绑,无需声明这个库的依赖,在模块级别的 build.gradle
添加下述配置启用即可 ( 区分AS版本 )。
apply plugin: 'kotlin-kapt' android { ... // AS 4.0以下 dataBinding{ enabled true } // AS 4.0及以上 buildFeatures { dataBinding true } // 还可以这样写 buildFeatures.dataBinding = true }
② 用DataBinding之前
未使用DataBinding之前,先写布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/tv_test" android:layout_width="match_parent" android:layout_height="48dp" android:gravity="center" android:text="计数器:0" /> <Button android:id="@+id/bt_test" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="加1" /> </LinearLayout>
再写Activity:
class TestActivity : AppCompatActivity() { private var mCount: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) findViewById<Button>(R.id.bt_test).setOnClickListener { findViewById<TextView>(R.id.tv_test).text = "计数器:${++mCount}" } } }
运行效果如下(点击按钮,计数器自增1):
司空见惯的常规操作,代码中主动setText()去更新TextView的文本,接着换成DataBinding试试看。
③ 用DataBinding之后
来到布局xml文件,鼠标点到 根布局
LinearLayout,按 Alt + Enter
,点击 Convert to data binding layout
,自动生成一波DataBinding所需的布局。
生成后的文件内容:
多了两个标签,接着开始改造,data标签中添加属性,修改TextView的android:text指向属性:
接着到Activity:
运行后,点击加1按钮,计数+1,效果与setText()一致,修改属性值,绑定的TextView文本跟着自动刷新。
看着 灰常简单!接着系统过一波详细用法,读者按需查阅即可~
0x3、详细用法
① xml布局文件
先是必须遵守的铁律:
根结点必须为<layout>,只能存在一个<data>和一个直接子View结点。
1) variable (变量标签)
变量的 属性名name不能包含_下划线,否则再kt文件里会找不到变量,有时可能需要 指定自定义类型:
<variable name="user" type="cn.coderpig.awayfornoise.entity.User"/> <!-- 也可以先import,然后直接用简写类名 --> <import type="cn.coderpig.awayfornoise.entity.User"/> <variable name="user" type="User" /> <!-- 当需要使用两个同名但不同包名的类,可以使用alias别名属性 --> <import type="com.example.User" /> <import type="cn.coderpig.awayfornoise.entity.User" alias="CpUser" /> <variable name="user" type="CpUser" />
2) data (数据标签)
它有个属性class,可以自定义DataBinding生成的类名及路径 (一般不需要):
<!--自定义类名--> <data class="CustomDataBinding"></data> <!--自定义生成路径以及类型,自动在包名下生成包以及类--> <data class=".CustomDataBinding"></data> <!-- 一般没必要自定义路径,生成位置直接全局搜 CustomDataBindingImpl -->
3) @{}表达式
支持下述运算符和关键字:
- 算术运算符
+ - / * %
- 字符串连接运算符
+
- 逻辑运算符
&& ||
- 二元运算符
& | ^
- 一元运算符
+ - ! ~
- 移位运算符
>> >>> <<
- 比较运算符
== > < >= <=
(请注意,< 需要转义为<
;) instanceof
- 分组运算符
()
- 文字 - 字符、字符串、数字、
null
- 类型转换
- 方法调用
- 字段访问
- 数组访问
[]
- 三元运算符
?:
使用示例如下:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
不支持关键字及操作:this、super、new、显式泛型调用。
null合并运算符(??):如果左边不为Null,取左边,否则取右边,示例如下:
android:text="@{user.displayName ?? user.lastName}" <!-- 等价于 --> android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用:表达式中可以引用类的属性,如:
android:text="@{user.lastName}"
空安全:DataBinding生成的代码会 自动检查null值并避免出现空指针异常。
如user为null,会为user.lastName分配默认null值,如果引用user.age,age为int,分配默认值0。
View引用:可以通过ID引用布局中其他的View,会将ID转换为 驼峰式大小写,示例如下:
<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}"/>
集合:可以使用[]
运算符访问集合,如Array、List、Map等,示例如下:
<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]}" <!-- 也可以用object.key表示法在map中引用值 --> android:text="@{map.key}"
注:变量的元素类型type的值不能包含 '<' 字符,直接 List<String>
这样写会引起XML语法错误。需要对 '<' 做下 转义,即 <
。
字符串
可以用 单引号('') 包裹特征值,这样就可以在表达式中使用双引号了,示例如下:
android:text='@{map["firstName"]}'
可以用双引号扩住特征值,然后用 反单引号(``) 将字符串括起来,示例如下:
android:text="@{map[`firstName`]}"
还支持用 +
号拼接字符串哦~