添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
傻傻的凳子  ·  azul zulu OpenJDK 17 ...·  1 年前    · 
严肃的吐司  ·  rsa/ecb/pkcs1padding ...·  1 年前    · 

DataBinding 介绍

DataBinding是什么?

DataBinding 是谷歌官方发布的一个框架,顾名思义即为 数据绑定 ,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。

DataBinding将布局xml中将控件和数据进行绑定,使数据变化可以驱动控件改变,控件改变可以驱动数据改变。

减少了Activity中对控件的初始化、设置监听、显示数据等操作。

使用databinding你就不需要使用findviewbyid()、setText()等。

MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常。

ViewBinding

视图绑定组件

配置ViewBinding

android {
        viewBinding {
            enabled = true
 

通过视图绑定功能,您可以更轻松地编写可与视图交互的代码。在模块中启用视图绑定之后,系统会为该模块中的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。在大多数情况下,视图绑定会替代 findViewById。

与findViewById的区别:空安全和类型安全,不存在因引用了一个错误的id而导致的空指针异常或者类型转换异常。

与databinding的区别:databinding仅处理使用 layout代码创建的数据绑定布局;ViewBinding不支持布局变量或布局表达式,因此它不能用于在xml中将布局与数据绑定。

与Android Kotlin Extensions的区别:在使用上,后者简单粗暴,直接id进行访问,而View Binding需要创建绑定类的实例;后者有一些不友好的地方,比如相同的id存在于多个xml,容易导错包,如果包导错了,会有可能别的View用错id导致空指针,而View Binding显然不会有这种情况。

2020年11月11日更新:Android Stuidio 4.1及以上版本,新创建的项目已默认移除kotlin-android-extensions插件

DataBinding基本使用

启用 DataBinding

  • 启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对DataBinding 的支持。
apply plugin: 'kotlin-kapt'//必须
android {
 dataBinding {
    enabled = true
	  //AS 4.1之后
  bindingFeature{
		dataBinding = true
		// for view binding :
		// viewBinding = true

就是这么简单,一个简单的databinding配置之后,就可以开始使用数据绑定了。

生成DataBinding布局

我们来看看布局文件该怎么写,首先布局文件不再是以传统的某一个容器作为根节点,而是使用layout作为根节点,在layout节点中我们可以通过data节点来引入我们要使用的数据源。

启用 DataBinding 后,打开原有的布局文件,选中根布局的 根布局,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则。

  • 转换后的内容为:
<?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>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道。

设置基本类型数据

在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。

<?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">
        <variable
            name="userName"
            type="String" />
        <variable
            name="age"
            type="Integer" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{userName}"
            android:textColor="#f00"
            android:textSize="20sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
		<!--int 要转为string,使用拼接,或者valueOf,toString都可以。-->
        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(age)}"
            android:textColor="#f00"
            android:textSize="20sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_name" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

MainActivity

class MainActivity : AppCompatActivity() {
	private val binding: ActivityMainBinding by lazy {
        DataBindingUtil.setContentView(this, R.layout.activity_main)
    override fun onCreate(savedInstanceState:




    
 Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
       // val binding =DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.userName = "xyh"
        binding.age = 20

设置对象数据

  • 定义一个实体类

要使用数据绑定,我们得首先创建一个实体类:

data class User(val name:String,val age:Int,val phoneNum:String)
  • Model 与 布局文件关联,设置数据

在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。

这里声明了一个 User 类型的变量 user,我们要做的就是使这个变量与TextView 控件挂钩,通过设置 user的变量值同时使 TextView 显示相应的文本。

通过 @{user.name} 使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法。

<?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">
        <variable
            name="user"
            type="com.example.jetpack.bean.User" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <!--注意:这里age是int类型,必须转化为String,否则会运行时异常-->
        <TextView
            android:id="@+id/tvAge"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{String.valueOf(user.age)}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvName" />
        <TextView
            android:id="@+id/tvPhoneNum"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.phoneNum==null?user.phoneNum:`17817318877`}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvAge" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • Activity 中通过 DataBindingUtil 设置布局文件

Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法。

每个数据绑定布局文件都会生成一个绑定类,ViewDataBinding 的实例名是根据布局文件名来生成,将之改为首字母大写的驼峰命名法来命名,并省略布局文件名包含的下划线。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding=DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
        binding.user= User("赵丽颖",20,"17817318859")

一个简单的dataBinding案例就已经完成。

代码中获取布局文件中的控件

使用binding对象可获取布局文件中的各个对象,根据控件设置的id来获取。如

        <Button
            android:id="@+id/btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
binding.btn.setOnClickListener {
       Toast.makeText(this,"点击了按钮",Toast.LENGTH_SHORT).show()

DataBinding在布局文件中的一些常用用法

在data中定义的variable节点,name属性表示变量的名称,type表示这个变量的类型,实例就是我们实体类的类的全路径。

<variable name="user" type="com.example.jetpack.bean.User" /> </data>

1. 自定义 ViewDataBinding 的实例名

可以通过如下方式自定义 ViewDataBinding 的实例名

<data class="CustomBinding">
</data>
   private val binding: CustomBinding by lazy {
        DataBindingUtil.setContentView(this, R.layout.activity_main)

2. import

如果 User 类型要多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用

<import type="com.example.jetpack.bean.User"/> <variable name="user" type="User" /> </data>

可以导入java或kotlin文件中的系统类,比如 import 集合 list:

<import type="java.util.List"/>

3. alias

先使用import节点将User导入,然后直接使用即可。但是如果这样的话又会有另外一个问题,假如我有两个类都是User,这两个UserBean分属于不同的包中,又该如何?这时候就要用到alias了。

在import节点中还有一个属性叫做alias,这个属性表示我可以给该类取一个别名,我给User这个实体类取一个别名叫做Lenve,这样我就可以在variable节点中直接写Lenve了。

<import type="com.example.jetpack.bean.User" alias="Lenve"/> <variable name="user" type="Lenve" /> </data>

4. 字符串拼接

如activity_main中如下属性

  android:text="@{`名字`+user.name}"

5. 设置默认值:默认值无需加引号,只在预览视图中显示

由于 TextView在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号。

        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name,default=morenzhi}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

6. 三目运算

       <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.phone==null?user.phone:`147522444`}"
            android:textSize="18sp" />

7. 布局中要使用某个类的方法

public class StringUtils {
    public static String getNewStr(String str) {
        return str+"-new";

在 data 标签中导入该工具类:

 <import type="com.example.jetpack.StringUtils" />

然后就可以像对待一般的函数一样来调用了:

<TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{StringUtils.getNewStr(userInfo.name)}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

8. 资源引用

dataBinding 支持对尺寸和字符串这类资源的访问。

 <string name="title">标题</string>
    <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{@string/title}"
            android:textColor="#f00"
            android:textSize="20sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    <dimen name="paddingBig">190dp</dimen>
    <dimen name="paddingSmall">150dp</dimen>
<string name="format">%s is %s</string>
        <variable
            name="flag"
            type="boolean" />
    </data>       
    <Button
         android:layout_width




    
="match_parent"
         android:layout_height="wrap_content"
         android:paddingLeft="@{flag ? @dimen/paddingBig:@dimen/paddingSmall}"
         android:text='@{@string/format("leavesC", "Ye")}'
         android:textAllCaps="false" />

9. 属性控制

可以通过变量值来控制 View 的属性:

 <TextView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:text="可见性变化"
     android:visibility="@{user.male  ? View.VISIBLE : View.GONE}" />

10. 避免空指针异常

DataBinding 也会自动帮助我们避免空指针异常:

例如,如果 “@{userInfo.password}” 中 userInfo 为 null 的话,userInfo.password会被赋值为默认值 null,而不会抛出空指针异常。

11. 运算符

DataBinding 支持在布局文件中使用以下运算符、表达式和关键字:

目前不支持以下操作:

在这里插入图片描述
此外,DataBinding 还支持以下几种形式的调用:

空合并运算符 ?? 会取第一个不为 null 的值作为返回值

 <TextView
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:text="@{user.name ?? user.password}" />
android:text="@{user.name != null ? user.name : user.password}"

在 Fragment中使用Databinding

class DemoFrgament : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding = DataBindingUtil.inflate<FragmentDemoBinding>(
            inflater,
            R.layout.fragment_demo,
            container,
            false
		//val binding = FragmentDemoBinding.inflate(inflater, container, false)
        return binding.root

DataBinding事件绑定

严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已,事件绑定可用于以下多种回调事件:

  • android:onClick
  • android:onLongClick
  • android:afterTextChanged
  • android:onTextChanged

Databinding事件绑定,分两种方式:方法引用和监听绑定,下面分别用案例介绍两种事件绑定的异同。

方式1:直接获取控件设置点击事件

binding.btn1.setOnClickListener {
        Toast.makeText(this,"点击了按钮1",Toast.LENGTH_SHORT).show()

方式2:方法引用

传入OnClickListener的变量:

<variable
        name="listener"
        type="android.view.View.OnClickListener" />

在Button中给android:onClick设置listener的变量:

<Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{listener}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{listener}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />

通过setListener传入点击监听给listener对象:

    binding.setListener {
            when (it.id) {
                R.id.btn1 -> Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
                R.id.btn2 -> Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()

方式3:方法引用

   <variable
            name="handlers"
            type="com.example.jetpack.MainActivity" />

调用语法可以是@{handlers::onClickFriend}或者@{handlers.onClickFriend}:

  <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickFriend}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
        <Button
            android:id="@+id/btn2"
            android:layout_width=




    
"match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::onClickFriend}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        binding.user = User("赵丽颖", 20, "17817318859", url)
        binding.handlers = this
    fun onClickFriend(view: View) {
        when (view.id) {
            R.id.btn1 -> Toast.makeText(this, "点击了按钮1", Toast.LENGTH_SHORT).show()
            R.id.btn2 -> Toast.makeText(this, "点击了按钮2", Toast.LENGTH_SHORT).show()

方式4:方法引用

<variable name="user" type="com.example.jetpack.bean.User" /> <variable name="handlers" type="com.example.jetpack.MainActivity.MyClickHandlers" /> </data> <Button android:id="@+id/btn1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="改变name属性" android:onClick="@{handlers.onClickChangeName}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" /> <Button android:id="@+id/btn2" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{handlers::onClickChangAage}" android:text="改变age属性" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toBottomOf="@id/btn1" />

在 Activity 内部新建一个类来声明 onClickChangeName() 和 onClickChangAage() 事件相应的回调方法:

class MainActivity : AppCompatActivity() {
    lateinit var user: User
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        user = User("赵丽颖", 20, "17817318859", url)
        binding.user = user
        binding.handlers = MyClickHandlers()
    inner class MyClickHandlers {
        fun onClickChangeName(v: View?) {
            user.name = "赵丽颖2"
            binding.user=user
        fun onClickChangAage(v: View?) {
            user.age = 18
            binding.user=user

方式5:方法引用

把回调方法单独写到一个接口。

  <variable
            name="handlers"
            type="com.example.jetpack.UserClickListener" />
 <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="按钮"
            android:onClick="@{handlers.userClicked}"
            app:layout_constraintLeft_toLeftOf="parent"




    

            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers::userClicked}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />
interface UserClickListener {
    fun userClicked(view: View?)
class MainActivity : AppCompatActivity(), UserClickListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       val  binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        val user = User("赵丽颖", 20, "17817318859", url)
        binding.user = user
        binding.handlers = this
    override fun userClicked(view: View?) {
        Toast.makeText(this, "方法引用",Toast.LENGTH_SHORT).show();

方式6:监听绑定(重要)

   onclick="@{()->vm.click()}"
   onclick="@{(v)->vm.click(v)}"
   onclick="@{()->vm.click(context)}"
   onclick="@{BindHelp::staticClick}"
   onclick="@{callback}"

将对象直接传回点击方法中。

   <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{()->handlers.showUser(user)}"
            android:text="按钮"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPhoneNum" />
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val url =
            "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
        var user = User("赵丽颖", 20, "17817318859", url)
        binding.user = user
        binding.handlers = MyClickHandlers()
    inner class MyClickHandlers {
        fun showUser(user: User) {
            Toast.makeText(this@MainActivity, user.name, Toast.LENGTH_SHORT).show()

include和viewStub中使用DataBinding

DataBinding不支持merge标签。

include

对于 include 的布局文件,一样是支持通过 dataBinding 来进行数据绑定,此时一样需要在 include 的布局中依然使用 layout 标签,声明需要使用到的变量。

  • view_include.xml
<?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">
        <variable
            name="user"
            type="com.example.jetpack.bean.User" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf=




    
"parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • 在主布局文件中将相应的变量传递给 include 布局,从而使两个布局文件之间共享同一个变量:
<?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">
        <variable
            name="user"
            type="com.example.jetpack.bean.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <include
            layout="@layout/view_include"
            bind:user="@{user}" />
    </LinearLayout>
</layout>

viewStub

dataBinding 一样支持 ViewStub 布局。

在布局文件中引用 viewStub 布局:

如果需要为 ViewStub 绑定变量值,则 ViewStub 文件一样要使用 layout 标签进行布局,主布局文件使用自定义的 bind 命名空间将变量传递给 ViewStub。

<?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">
        <variable
            name="user"
            type="com.example.jetpack.bean.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <ViewStub
            android:id="@+id/view_stub"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            bind:user="@{user}"
            android:layout="@layout/view_include"/>
    </LinearLayout>
</layout>

获取到 ViewStub 对象,由此就可以来控制 ViewStub 的可见性:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val user = User("赵丽颖", 20, "17817318859")
        binding.user = user
        //拿到ViewStub的实例之后,调用inflate()方法将隐藏的布局给加载出来
        val view = binding.viewStub.viewStub?.inflate()

如果在 xml 中没有使用 bind:userInfo="@{userInf}"对 ViewStub 进行数据绑定,则可以等到当 ViewStub Inflate 时再绑定变量,此时需要为 ViewStub 设置 setOnInflateListener回调函数,在回调函数中进行数据绑定:

       binding.viewStub.viewStub?.setOnInflateListener { stub, inflated ->
            //如果在 xml 中没有使用 bind:userInfo="@{userInf}" 对 viewStub 进行数据绑定
            //那么可以在此处进行手动绑定
            val viewIncludeBinding = DataBindingUtil.bind<ViewIncludeBinding>(inflated)
            viewIncludeBinding?.user = user

dataBinding 支持在布局文件中使用数组,集合

dataBinding 也支持在布局文件中使用数组、Lsit、Set 和 Map,且在布局文件中都可以通过 list[index] 的形式来获取元素。

而为了和 variable 标签的尖括号区分开,在声明 Lsit< String > 之类的数据类型时,需要使用尖括号的转义字符。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
        <import type="java.util.List" />
        <import type="java.util.Map" />
        <import type="java.util.Set" />
        <import type="android.util.SparseArray" />
        <variable
            name="array"
            type="String[]" />
        <variable
            name="list"
            type="List&lt;String&gt;" />
        <variable
            name="map"
            type="Map&lt;




    
String, String&gt;" />
        <variable
            name="set"
            type="Set&lt;String&gt;" />
        <variable
            name="sparse"
            type="SparseArray&lt;String&gt;" />
        <variable
            name="index"
            type="int" />
        <variable
            name="key"
            type="String" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".Main7Activity">
        <TextView
            android:text="@{array[1]}" />
        <TextView
            android:text="@{sparse[index]}" />
        <TextView
            android:text="@{list[index]}" />
        <TextView
            android:text="@{map[key]}" />
        <TextView
            android:text='@{map["leavesC"]}' />
        <TextView
            android:text='@{set.contains("xxx")?"xxx":key}' />
    </LinearLayout>
</layout>

BindingAdapter:自定义属性注解

DataBinding 提供了 BindingAdapter 这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用。

  • 作用于方法,这个方法可以写在任何地方。
  • 这个注解面向一个public static方法。
  • {}内部表示使用时这个属性的名字。例如:@BindingAdapter({“imageUrl”}),imageUrl为属性的名称。
  • BindingAdapter也可以设置多个属性。

对于一个 ImageView ,我们希望在某个变量值发生变化时,可以动态改变显示的图片,此时就可以通过 BindingAdapter 来实现。

在java中使用

在java中使用dataBinding展示图片很简单,只需要配置一个静态的BindingAdapter就可以了。

需要先定义一个静态方法,为之添加 BindingAdapter 注解,注解值是为 ImageView 控件自定义的属性名,而该静态方法的两个参数可以这样来理解:

当 ImageView 控件的 url 属性值发生变化时,dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法,从而可以在此动态改变 ImageView 的相关属性。

public class DataBindingUtils {
    @BindingAdapter("imageUrl")  //imageUrl:控件的属性名
    public static void loadImage(ImageView imageView, String url) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(R.mipmap.ic_launcher)
                .error(R.mipmap.ic_launcher)
                .into(imageView);
       <ImageView
            android:id="@+id/ivNet"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            app:imageUrl="@{user.url}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn" />

当ImageView中使用imageUrl属性时,会自动调用loadImage方法。

  • BindingAdapter多参数
public class ImageHelper {
    @BindingAdapter({"imageUrl","errorDrawableId","placeDrawableId"})
    public static void loadImage(ImageView imageView,String url,int errorDrawableId,int placeDrawableId ){
        Glide.with(imageView.getContext())
                .load(url)
                .error(errorDrawableId)
                .placeholder(placeDrawableId)
                .into(imageView);
<?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">
        <variable
            name="url"
            type="String" />
        <variable
            name="url2"
            type="int" />
    </data>
    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        <




    
ImageView
            android:layout_width="90dp"
            android:layout_height="90dp"
            android:scaleType="centerCrop"
            app:imageUrl="@{url}"
            app:placeDrawableId="@{url2}"
            app:errorDrawableId="@{url2}"/>
    </LinearLayout>
</layout>
  • requireAll
    @android.databinding.BindingAdapter(value = {"app:imgUrl", "app:placeholder"}, requireAll = false)
    public static void loadImg(ImageView imageView, String url, Drawable placeholder) {
        GlideApp.with(imageView)
                .load(url)
                .placeholder(placeholder)
                .into(imageView);

这里 requireAll = false 表示我们可以使用这两个两个属性中的任一个或同时使用,如果requireAll = true 则两个属性必须同时使用,不然报错。默认为 true。

在kotlin中使用

kotlin需要加@JvmStatic注解

首先:kotlin中没有static关键字,但是提供了companion object{}代码块和使用object关键字。

object关键字声明一种特殊的类,这个类只有一个实例,因此看起来整个类就好像是一个对象一样,这里把类声明时的class关键字改成了object,这个类里面的成员默认都是static的。

@JvmStatic注解:与伴生对象搭配使用,将变量和函数声明为真正的JVM静态成员。

要加上kapt插件:

apply plugin: 'kotlin-kapt'
  • 第一种方式:使用 companion object
class DataBindingUtils {
    companion object {
        @BindingAdapter("imageUrl")
        @JvmStatic
        fun loadImage(view: ImageView, url: String) {
            Glide.with(view.context).load(url).into(view)
  • 第二种方式:使用object
object DataBindingUtils {
	//imageUrl:就是要在布局文件中使用的属性
    @BindingAdapter("imageUrl")
    @JvmStatic
    fun loadImage(view: ImageView, url: String) {
        Glide.with(view.context).load(url).into(view)
val url="https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3796319594,2802761532&fm=26&gp=0.jpg"
binding.user= User("赵丽颖",20,"17817318859",url)
       <ImageView
            android:id="@+id/ivNet"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            app:imageUrl="@{user.url}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn" />

可以覆盖 Android 原先的控件属性

BindingAdapter 更为强大的一点是可以覆盖 Android 原先的控件属性。

例如,可以设定每一个 TextView 的文本都要加上后缀:“-赵丽颖”

    @BindingAdapter("android:text")
    public static void setText(TextView view, String text) {
        view.setText(text + "赵丽颖");
     <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{`名字`+user.name,default=morenzhi}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

这样,整个工程中使用到了 “android:text” 这个属性的控件,其显示的文本就会多出一个后缀-赵丽颖。

可以用这个属性来加载本地图片:

public class ImageViewAdapter {
    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, Bitmap bitmap) {
        view.setImageBitmap(bitmap);
    @BindingAdapter("android:src")
    public static void setSrc(ImageView view, int resId) {
        view.setImageResource(resId);
    @BindingAdapter("imageUrl")
    public static void setSrc(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url)
                .placeholder(R.mipmap.ic_launcher)
                .into(imageView);
    @BindingAdapter({"app:imageUrl", "app:placeHolder"




    
, "app:error"})
    public static void loadImage(ImageView imageView, String url, Drawable holderDrawable, Drawable errorDrawable) {
        Glide.with(imageView.getContext())
                .load(url)
                .placeholder(holderDrawable)
                .error(errorDrawable)
                .into(imageView);

自定义属性

object DataBindingUtils {
     * View的显示和隐藏
    @BindingAdapter("isGone")
    @JvmStatic
    fun bindISGone(view: View, isGone: Boolean) {
        view.visibility = if (isGone) View.GONE else View.VISIBLE
        <variable
            name="hasPlantings"
            type="boolean" />
    </data>
  <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/garden_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:paddingLeft="@dimen/margin_normal"
            android:paddingRight="@dimen/margin_normal"
            app:isGone="@{!hasPlantings}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:listitem="@layout/list_item_garden_planting" />
        <TextView
            android:id="@+id/empty_garden"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@string/garden_empty"
            android:textSize="24sp"
            app:isGone="@{hasPlantings}" />

BindingConversion

dataBinding 还支持对数据进行转换,或者进行类型转换。

与 BindingAdapter 类似,以下方法会将布局文件中所有以@{String}方式引用到的String类型变量加上后缀-conversionString。

    @BindingConversion
    public static String conversionString(String text) {
        return text + "-conversionString";
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text='@{"xxx"}'
            android:textAllCaps="false"/>
      <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{userInfo.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

如果同时设置了BindingAdapter 和 BindingConversion ,都会同时生效了,而 BindingConversion 的优先级要高些

   @BindingConversion
    public static String conversionString(String text) {
        return text + "-conversionString";
    @BindingAdapter("android:text")
    public static void setText(TextView view, String text) {
        view.setText(text + "-TextView");

转换属性值的类型

此外,BindingConversion 也可以用于转换属性值的类型

看以下布局,此处在向 background 和 textColor 两个属性赋值时,直接就使用了字符串,按正常情况来说这自然是会报错的,但有了 BindingConversion 后就可以自动将字符串类型的值转为需要的 Drawable 和 Color 了。

   <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:background="红色"
        android:textColor="蓝色"
        android:layout_height="wrap_content"/>
    @BindingConversion
    public static Drawable convertStringToDrawable(String str) {
        if (str.




    
equals("红色")) {
            return new ColorDrawable(Color.parseColor("#FF4081"));
        if (str.equals("蓝色")) {
            return new ColorDrawable(Color.parseColor("#3F51B5"));
        return new ColorDrawable(Color.parseColor("#344567"));
    @BindingConversion
    public static int convertStringToColor(String str) {
        if (str.equals("红色")) {
            return Color.parseColor("#FF4081");
        if (str.equals("蓝色")) {
            return Color.parseColor("#3F51B5");
        return Color.parseColor("#344567");
  • @BindingConversion比@BindingAdapter先执行。
  • @BindingConversion使用companion object方式定义静态方法,会报异常@BindingConversion is only allowed on public static methods conversionString(String),采用Object方式定义静态方法即可。

BindingMethods

适配扩展支持binding的函数。

BindingMethods包含若干BindingMethod,BindingMethod是BindingMethods的子集。

indingMethods与BindingMethod用于类的注解,简单的可以理解为,定义xml中定义的属性与某个medthod(方法)绑定。

//这里BindMethods可以放在任何的类上面,重点在于内部属性声明
@BindingMethods(
    BindingMethod(
        type = AppCompatImageView::class,
        attribute = "image",
        method = "setImageDrawable"
object BdTool {
    @JvmStatic
    fun getTitle(type: String): String {
        return "type:$type"
<?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">
        <variable
            name="imgRes"
            type="android.graphics.drawable.Drawable" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.appcompat.widget.AppCompatImageView
            image="@{imgRes}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:ignore="MissingConstraints" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.apply {
            imgRes=getDrawable(R.mipmap.ic_launcher)

通过DataBinding进行绑定控件以及进行相关操作,但是,这遇到了一个瓶颈,就比如绑定的变量发生变化的时候,每次都要重新向 ViewDataBinding 传值进行更新操作之后才能刷新UI。那么怎么就能自动刷新UI了呢?那就得用 单向绑定 了!

实现数据变化自动驱动 UI 刷新的方式有三种:

  • BaseObservable
  • ObservableField
  • ObservableCollection

使用BaseObservable动态更新UI

一个纯净的 ViewModel 类被更新后,并不会让 UI 自动更新。而数据绑定后,我们自然会希望数据变更后 UI 会即时刷新,Observable 就是为此而生的概念。

BaseObservable 提供了 notifyChange()notifyPropertyChanged() 两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注释 @Bindable 生成,可以通过 BR notify 特定属性关联的视图。

package com.example.jetpack.bean;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.example.jetpack.BR;
 * Cerated by xiaoyehai
 * Create date : 2020/11/14 13:02
 * description :
 * 1.Student继承BaseObservable
 * 2.每个getter()加上注解@Bindable
 * 3.每个setter()加上notifyPropertyChanged(BR.xxx); - BR在rebuild后自动生成
public class UserInfo extends BaseObservable {
    // 如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
    private String name;
    //如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
    private String password;
    private String desc;
    public UserInfo(String name, String password, String desc) {
        this.name = name;
        this.password = password;
        this.desc = desc;
    @Bindable
    public String getName() {
        return name;
    public void setName(String name) {
        this.name = name;
        //只更新本字段:name变化时只会更新新本字段
        notifyPropertyChanged(BR.name);
    @Bindable
    public String getPassword() {
        return password;




    

    public void setPassword(String password) {
        this.password = password;
        //更新所有字段:password会更新所有字段
        notifyChange();
    public String getDesc() {
        return desc;
    public void setDesc(String desc) {
        this.desc = desc;

BR是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable标记过 getter方法会在BR中生成一个静态常量。

UserInfo用kotlin来实现:

class UserInfo : BaseObservable() {
    //由于kotlin的属性默认是public修饰,所以可以直接在属性上@Bindable, 如何设置了修饰符且不为public的话,
    // 则可使用@get BIndable(表示在get()方法上标记@Bindable)
    // 对name进行@Bindable标志,然后会生成BR.name
    @Bindable
    var name: String = ""
        set(value) {
            field = value
            // 当name,发生改变时只会刷新与name相关控件的值,不会刷新其他的值
            notifyPropertyChanged(BR.name)
    @get: Bindable
    var password: String = ""
        set(value) {
            field = value
            // 当password 发生改变时,也会刷新其他属性相关的控件的值
            notifyChange()
    var desc: String = ""
<?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">
        <variable
            name="userInfo"
            type="com.example.jetpack.bean.UserInfo" />
        <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{userInfo.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tvPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{userInfo.password}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvName" />
        <TextView
            android:id="@+id/tvDesc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{userInfo.desc}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPassword" />
        <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers.onClickChangeName}"
            android:text="改变name和desc"
            android:textAllCaps="false"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvDesc" />
        <Button
            android:id="@+id/btn2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick




    
="@{handlers.onClickChangePassword}"
            android:text="改变password和desc"
            android:textAllCaps="false"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/btn1" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class MainActivity : AppCompatActivity() {
    lateinit var userInfo: UserInfo
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        userInfo = UserInfo("赵丽颖1", "1234561","描述1")
        binding.userInfo = userInfo
        binding.handlers=MyClickHandlers()
    inner class MyClickHandlers {
        fun onClickChangeName(v: View?) {
            userInfo.name = "赵丽颖2"
            userInfo.desc = "描述2"
        fun onClickChangePassword(v: View?) {
            userInfo.name = "赵丽颖3"
            userInfo.password = "123456123"
            userInfo.desc = "描述3"

可以看到,name 值的改变没有同时刷新 desc ,而 password 刷新的同时也刷新了name 和 price 。

OnPropertyChangedCallback:属性变更监听

实现了 Observable 接口的类允许注册一个监听器,当可观察对象的属性更改时就会通知这个监听器,此时就需要用到 OnPropertyChangedCallback。

当中 propertyId 就用于标识特定的字段

  userInfo.addOnPropertyChangedCallback(object :Observable.OnPropertyChangedCallback() {
            override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
                when(propertyId) {
                    BR._all-> Log.e("xyh", "BR._all")
                    BR.name-> Log.e("xyh", "BR.name")
                    BR.password-> Log.e("xyh", "BR.password")

使用ObervablueFields动态更新

ObervablueFields,它其实也是为了能够减少代码量,当一个Bean没有那么多属性的时候,我们不需要写这么多的get和set方法,使用ObervablueFields只需要通过两行代码即可实现相同效果。

继承于 Observable 类相对来说限制有点高,且也需要进行 notify 操作,因此为了简单起见可以选择使用 ObservableField。ObservableField 可以理解为官方对 BaseObservable 中字段的注解和刷新等操作的封装,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField泛型来申明其他类型。

public class UserInfo  {
    private ObservableField<String> name;
    private ObservableInt age;
    private ObservableField<String> desc;
    public UserInfo(ObservableField<String> name, ObservableInt age, ObservableField<String> desc) {
        this.name = name;
        this.age = age;
        this.desc = desc;
    public ObservableField<String> getName() {
        return name;
    public void setName(ObservableField<String> name) {
        this.name = name;
    public ObservableInt getAge() {
        return age;
    public void setAge(ObservableInt age) {
        this.age = age;
    public ObservableField<String> getDesc() {
        return desc;
    public void setDesc(ObservableField<String> desc) {
        this.desc = desc;

对 ObservableField属性值的改变都会立即触发 UI 刷新,概念上与 Observable 区别不大。

class MainActivity : AppCompatActivity() {
    lateinit var userInfo: UserInfo
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val name = ObservableField<String>("赵丽颖1")
        val age = ObservableInt(20)
        val desc = ObservableField("描述")
        userInfo = UserInfo(name, age, desc)
        binding.userInfo = userInfo
        binding.handlers = MyClickHandlers()
    inner class MyClickHandlers {
        fun onClickChangeName(v: View?) {
            userInfo.name.




    
set("赵丽颖2")
            userInfo.age.set(21)
            userInfo.desc.set("描述2")
        fun onClickChangePassword(v: View?) {
            userInfo.name.set("赵丽颖3")

使用ObservableCollection动态更新UI

上面讲到 ObservableField 单向绑定,和 BaseObservable 相比之下,ObservableField 简单了许多,只需要用它的方法即可,提供了自动刷新UI的方法。

如果用到 Map 或者是 List,同样 DataBinding 还提供了 ObservableMap 和 ObservableList,dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap,当其包含的数据发生变化时,绑定的视图也会随之进行刷新。

下面来基本使用一下吧!

<?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">
        <import type="androidx.databinding.ObservableList" />
        <import type="androidx.databinding.ObservableMap" />
        <!--注意这个地方,一定要用 "&lt;"和 "&gt;",这里不支持尖括号-->
        <variable
            name="list"
            type="ObservableList&lt;String&gt;" />
        <variable
            name="map"
            type="ObservableMap&lt;String,String&gt;" />
        <variable
            name="index"
            type="int"/>
        <variable
            name="key"
            type="String"/>
        <variable
            name="handlers"
            type="com.example.jetpack.MainActivity.MyClickHandlers" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{list.get(index)}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tvPassword"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{list.get(1)}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvName" />
        <TextView
            android:id="@+id/tvDesc"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{map[key]}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvPassword" />
        <Button
            android:id="@+id/btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{handlers.onClickChange}"
            android:text="改变数据"
            android:textAllCaps="false"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvDesc" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout




    
>
class MainActivity : AppCompatActivity() {
    var list: ObservableList<String> = ObservableArrayList()
    var map: ObservableArrayMap<String, String> = ObservableArrayMap()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        list.add("赵丽颖1")
        list.add("赵丽颖2")
        binding.list = list
        binding.index = 0
        map["name"] = "赵丽颖3"
        map["age"] = "21"
        binding.map = map
        binding.key = "name"
        binding.handlers = MyClickHandlers()
    inner class MyClickHandlers {
        fun onClickChange(v: View?) {
            list[0]="zly1"
            map["name"] = "zly2"
 

Databinding是Google推出的一个支持View与ViewModel绑定的Library,可以说Databinding建立了一个UI与数据模型之间的桥梁,即UI的变化可以通知到ViewModel, ViewModel的变化同样能够通知到UI从而使UI发生改变,大大减少了之前View与Model之间的胶水代码,如findViewById;改变和获取TextView的内容还需要调用setText()、 getText(),获取EditText编辑之后的内容需要调用getText(),而有了Databinding的双向绑定,这些重复的工作都将被省去。下面我们就来看一下如何使用Databinding来双向绑定。

双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据。

现在要想到,在哪那些方面需要双向绑定,哪那些方面不需要双向绑定,生存还是死亡,这是个问题。

在以上三个单向绑定案例中,貌似双向绑定没多大用处,下面举例一种情况,在输入账号和密码的时候,UI更新同时数据也要更新,这就用到双向绑定,总的来说,双向绑定使用还是不算多的,双向绑定是安卓MVVM架构的基础。

Data Binding本身是不支持双向绑定的,我们想要实现双向绑定首先要改造一下实体类。让实体类继承BaseObservable。

目前双向绑定仅支持如text,checked,year,month,hour,rating,progress等绑定。

假设有一种情况,当我们在EditText里面输入内容的时候,如果此时我们的User已经和EditText关联,那么我们希望当输入框内容改变的时候,User对应的字段也发生变化,反之User发生变化的时候,输入框的内容也会跟着变化。这也是MVVM架构的思想,有了databinding框架,就可以帮我们快速实现一个MVVM架构。

看以下例子,当 EditText 的输入内容改变时,会同时同步到变量 userInfo,绑定变量的方式比单向绑定多了一个等号:android:text=“@={userInfo.name}”

双向绑定语法:

主要就是在布局文件中EditText中加上了一个 = (等于)号。

<?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">
        <variable
            name="userInfo"
            type="com.example.jetpack.bean.UserInfo" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <EditText
            android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={userInfo.name}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{userInfo.name}"
            android:textSize="16sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/et" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        val user = UserInfo("赵丽颖", 20)
        binding.userInfo = user

数据源可以使用使用 继承BaseObservable 方式:

public class UserInfo extends BaseObservable {
    //如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
    //如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
    private String name;
    private int age;
    public UserInfo(String name, int age) {
        this.name = name;
        this.age = age;
    @Bindable
    public String getName() {
        return name;
    @Bindable
    public int getAge() {
        return age;
    public void setName(String name) {
        this.name = name;
        //只更新本字段
        notifyPropertyChanged(BR.name);
        //更新所有字段
        //notifyChange();
    public void setAge(int age) {




    

        this.age = age;
        notifyPropertyChanged(BR.age);
 

数据源也可以使用 ObservableField 方式,来更好的更新UI,使用方式看上面的单向绑定中使用ObservableField 。

InverseBindingAdapter:反向绑定

双向绑定之基于InverseBindingAdapter的反向绑定

RecyclerView中使用DataBinding

activity_main.xml

<?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">
        <variable
            name="adapter"
            type="com.example.jetpack_demo.RvAdapter" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:itemCount="10"
            tools:listitem="@layout/item_rv" />
        <!--        app:adapter="@{adapter}"-->
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

item.rv.xml

<?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">
        <variable
            name="userBean"
            type="com.example.jetpack_demo.UserInfo" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="10dp">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{userBean.name}"
            android:textSize="20sp" />
        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(userBean.age)}"
            android:textSize="20sp" />
    </LinearLayout>
</layout>

UserInfo

data class UserInfo(val name:String,val age:Int)

MainActivity

class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by lazy {
        DataBindingUtil.setContentView(this, R.layout.activity_main)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding.recyclerView.addItemDecoration(
            DividerItemDecoration(
                this,
                DividerItemDecoration.VERTICAL
        var datas = mutableListOf<UserInfo>()
        for (i in 0..29) {
            datas.add(UserInfo("zly$i", 10 + i))
        val rvAdapter = RvAdapter(this, datas)
        binding.recyclerView.adapter = rvAdapter

RvAdapter

package com.example.jetpack;
 * Cerated by xiaoyehai
 * Create date : 2020/11/14 15:53
 * description :
public class RvAdapter extends RecyclerView.Adapter<




    
RvAdapter.ViewHolder> {
    private Context mContext;
    private List<UserInfo> mDatas;
    public RvAdapter(Context context, List<UserInfo> datas) {
        mContext = context;
        this.mDatas = datas;
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //用父类ViewDataBinding接收也可以,不过使用的时候需要强转
        /*ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext),
                R.layout.item_rv, parent, false);*/
        ItemRvBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext),
                R.layout.item_rv, parent, false);
        //设置点击事件
        binding.getRoot().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
        });
        return new ViewHolder(binding);
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        /*ViewDataBinding binding = holder.getBinding();
        binding.setVariable(BR.userBean, mDatas.get(position));//绑定数据
        binding.executePendingBindings();//立刻执行绑定,执行刷新*/
        ItemRvBinding binding = holder.getBinding();
        binding.setUserBean(mDatas.get(position));
        binding.tvName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
        });
        binding.executePendingBindings();//立刻执行绑定,执行刷新
    @Override
    public int getItemCount() {
        return mDatas.size();
    static class ViewHolder extends RecyclerView.ViewHolder {
        ItemRvBinding mDataBinding;
        public ViewHolder(ItemRvBinding binding) {
            super(binding.getRoot());
            mDataBinding = binding;
        public ItemRvBinding getBinding() {
            return mDataBinding;

RvAdapter Kotlin写法:

class RvAdapter(private val context: Context, private val data: List<UserInfo>) :
    RecyclerView.Adapter<RvAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding: ItemRvBinding =
            DataBindingUtil.inflate(
                LayoutInflater.from(context),
                R.layout.item_rv,
                parent,
                false
        return ViewHolder(binding)
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.userBean = data[position]
        //holder.binding.setVariable(BR.userBean,data[position])
        holder.binding.executePendingBindings() //立刻执行绑定,执行刷新
    override fun getItemCount(): Int = data.size
    class ViewHolder(var binding: ItemRvBinding) : RecyclerView.ViewHolder(binding.root)

LisView中使用DataBinding

```xml
<?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">
        <import type="com.xiaoyehai.mvvm.adapter.LvAdapter" />
        <variable
            name="adapter"
            type="LvAdapter" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".activity.LvActivity">
        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:adapter="@{adapter}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>




    

条目布局:

<?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">
        <variable
            name="user"
            type="com.xiaoyehai.mvvm.entity.UserBean" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">
        <ImageView
            android:layout_width="100dp"
            app:imageUrl="@{user.imgUrl}"
            android:layout_height="100dp" />
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{user.name}"
            android:textSize="20sp" />
    </LinearLayout>
</layout>
 * Created by : xiaoyehai
 * Create date : 2019/10/14 14:21
 * description :
public class LvAdapter extends BaseAdapter {
    private Context mContext;
    private List<UserBean> mDatas;
    private int mLayoutId;
    private final LayoutInflater mLayoutInflater;
    public LvAdapter(Context context, List<UserBean> datas, int layoutId) {
        mContext = context;
        this.mDatas = datas;
        this.mLayoutId = layoutId;
        mLayoutInflater = LayoutInflater.from(context);
    @Override
    public int getCount() {
        return mDatas.size();
    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    @Override
    public long getItemId(int position) {
        return position;
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ItemLvBinding binding;
        ViewHolder holder;
        if (convertView == null) {
            //获取item布局的binding
            binding = DataBindingUtil.inflate(mLayoutInflater, mLayoutId, parent, false);
            //获取布局
            convertView = binding.getRoot();
            holder = new ViewHolder();
            //缓存binding到holder
            holder.setItemLvBinding(binding);
            //设置Tag
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
            binding = (ItemLvBinding) holder.getItemLvBinding();
        binding.setUser(mDatas.get(position));
        return convertView;
     * viewholder类里只有一个binding对象和它的get,set方法
    private class ViewHolder {
        private ViewDataBinding binding;
        public void setItemLvBinding(ViewDataBinding binding) {
            this.binding = binding;
        public ViewDataBinding getItemLvBinding() {
            return binding;

DataBinding原理和源码解析

DataBinding使用了Gradle插件+APT技术,我们build项目时DataBinding会生成多个文件。

APT生成的目录结构

首先来看看ActivityMainBinding是什么时间生成的?

回顾一下,在activity_main.xml中把根布局改为layout标签之后,在回到对应的MainActivity就可以使用ActivityMainBinding对象了。这一点也可以得到证实:在引入layout之后,build项目会发现在Project目录下生成多个新的文件,目录大致如下:

此外,DataBinding还将原有的activity_main.xml文件进行了拆分,分别是activity_mian.xml和activity_main-layout.xml。

原有的activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <!--DataBinding的配置文件-->
        <!--name属性相当于声明了一个全局属性、type指定name对应的具体的数据类或是MVVM中VM-->
        <variable
            name="user"
            type="com.zly.ktdemo.User" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="




    
vertical"
        android:padding="50dp">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={user.name}" />
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.age)}" />
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

拆分后的activity_mian.xml(Android OS 渲染的布局文件):

通过上面的代码我们发现DataBinding将原有的layout和data标签去除了。并为根布局声明了一个layout/文件名_0的tag,为其他使用到@{}或@={}的控件按顺序添加了一个binding_X的tag。

拆分后的activity_main-layout.xml(DataBinding需要的布局控件信息):

  //我们声明的全局变量
  <Variables name="user" declared="true" type="com.zly.ktdemo.User">
      <Target tag="binding_1" view="EditText"> //tag对应的View类型
            <Expressions>
            	//控件绑定具体属性和Model中的具体属性
                <Expression attribute="android:text" text="user.name">
                    <Location endLine="19" endOffset="39" startLine="19" startOffset="12" />
                    <TwoWay>true</TwoWay> //是否是双向的
                    <ValueLocation endLine="19" endOffset="37" startLine="19" startOffset="29" />
                </Expression>
            </Expressions>
            <location endLine="19" endOffset="42" startLine="16" startOffset="8" />
        </Target>
 

结合生成文件和XML的位置和时间节点,大致可以看出生成的原理是Gradle插件+APT,这个插件是Gradle内置的,目前还没有查找到相关插件的实现在哪里。

数据是如何绑定(更新)到View的?

val binding =
        DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

通过activity的setContentView加载布局,并通过window找到id为content的ViewGroup,它是一个FrameLayout用于加载我们添加的布局文件。接下来就是bindToAddedViews方法。

    private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        final int endChildren = parent.getChildCount();
        final int childrenAdded = endChildren - startChildren;
        if (childrenAdded == 1) {
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            final View[] children = new View[childrenAdded];
            for (int i = 0; i < childrenAdded; i++) {
                children[i] = parent.getChildAt(i + startChildren);
            return bind(component, children, layoutId);
public class DataBindingUtil {
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    private static DataBindingComponent sDefaultComponent = null;
   public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);
   public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId, @Nullable DataBindingComponent bindingComponent) {
        activity.setContentView(layoutId);
        View decorView = activity.getWindow().getDecorView();
        ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
        return bindToAddedViews(bindingComponent, contentView, 0, layoutId);

parent中的子View就是我们布局文件中的根布局LinearLayout,所以走的是if中的代码

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,




    

            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);

先看看大致的调用关系,找到最终绑定View的地方:

setContentView
----->DataBinderMapperImpl#getDataBinder()
----->根据根布局标记的tag ActivityMainBindingImpl
----->内部构造方法调用invalidateAll();
----->mRebindRunnablemRebindRunnable
---->executePendingBindings()
---->executeBindingsInternal()
----> executeBindings()

数据绑定的具体实现是在ActivityMainBindingImpl#executeBindings()方法中:

   //绑定的数据一旦变化,就会调进这个方法
    //我们也可以调用自动生成的XXXBinding.executePendingBindings(),主动调入该函数来刷新UI
    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        java.lang.String userName = null;
        int userAge = 0;
        com.zly.ktdemo.User user = mUser;
        java.lang.String stringValueOfUserAge = null;
        if ((dirtyFlags & 0x7L) != 0) {
                if (user != null) {
                    // read user.name
                    userName = user.getName();
            if ((dirtyFlags & 0x5L) != 0) {
                    if (user != null) {
                        // read user.age
                        userAge = user.getAge();
                    // read String.valueOf(user.age)
                    stringValueOfUserAge = java.lang.String.valueOf(userAge);
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, com.zly.ktdemo.DataBindingUtils.conversionString(userName));
        if ((dirtyFlags & 0x4L) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView1androidTextAttrChanged);
        if ((dirtyFlags & 0x5L) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, com.zly.ktdemo.DataBindingUtils.conversionString(stringValueOfUserAge));

这里通过TextViewBindingAdapter.setText()去给UI控件赋值;而且在赋值之前会对model对象进行判空,这样就避免了set XX()方法时出现空指针异常。

到这里还没完,这个地方只能算是完成数据到View的映射,当我们更改Model的ObservableField属性去更新数据的时候,又是如何更新UI的呢?

ObservableField是对常用数据结构对包装类,它最终继承BaseObservable,它内部封装了观察者模式,可以监听数据的变化。

看一下它的set方法:

* Set the stored value. public void set(T value) { if (value != mValue) { mValue = value; notifyChange();

这里notifyChange()之后会通过观察者模式的OnPropertyChangedCallback回调到ViewDataBinding #WeakListListener#onChanged()。

 private static class WeakListListener extends ObservableList.OnListChangedCallback
            implements ObservableReference<ObservableList> {
        final WeakListener<ObservableList> mListener;
        public WeakListListener(ViewDataBinding binder, int localFieldId) {
            mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
        @Override
        public WeakListener<ObservableList> getListener() {
            return mListener;
        @Override
        public void addListener(ObservableList target) {
            target.addOnListChangedCallback(this);
        @Override
        public void removeListener(ObservableList target) {
            target.removeOnListChangedCallback(this);
        @Override
        public void onChanged(ObservableList sender) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            ObservableList target = mListener.getTarget();
            if (target != sender) {
                return; // We expect notifications only from sender
            binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
 

handleFieldChange
—>requestRebind
---->executePendingBindings()
---->executeBindingsInternal()
----> executeBindings()

最终又调用到了最终调用了executeBindings(),这里就是完成UI更新的地方。

观察View变化更新Model数据

再次回到更新UI的地方ActivityMainBindingImpl#executeBindings()。

 androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView1,
                    (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged) null,
                    (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged) null,
                    (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged) null,
                    mboundView1androidTextAttrChanged);

看一下这个setTextWatcher()方法,当数据发生变化的时候,TextWatcher在回调onTextChanged()的最后,会通过回调传入的ActivityMainBindingImpl # mboundView1androidTextAttrChanged # onChange()

  private androidx.databinding.InverseBindingListener mboundView1androidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of user.name
            //         is user.setName((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView1);
            // localize variables for thread safety
            // user.name
            java.lang.String userName = null;
            // user != null
            boolean userJavaLangObjectNull = false;
            // user
            com.zly.ktdemo.User user = mUser;
            userJavaLangObjectNull = (user) != (null);
            if (userJavaLangObjectNull) {
                user.setName(((java.lang.String) (callbackArg_0)));

这里就比较简单了,获取控件中最新的值,然后给ObservableField属性赋值。

Android DataBinding 从入门到进阶DataBinding 介绍DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常 虽然dataBi
Android标准化项目架构:MVVM+Jectpack 助力研发,本篇将对Jectpack 中的DataBinding进行简要分析 1.什么是DataBindingDataBinding是Google在2015年推出的组件库。Databinding支持双向绑定,可以大大减少绑定App逻辑于Layout的胶水代码。双向绑定,指的是将Model数据与界面绑定起来,当数据发生变化会直接体现在界面上,反过来界面发生变化也会同步到数据结构,使用DataBinding可以轻松实现MVVM模式。 2.开启DataB
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <variable name="bean"