DataBinding使用详解,一篇就够了

5,275 阅读6分钟

DataBinding的意义

1、布局文件通常只负责UI控件的布局工作,页面中通过代码对控件需要进行各种操作,承担了绝大部分的工作量

2、DataBinding让布局文件承担了部分原本属于页面的工作,也使得布局文件和页面的耦合度进一步降低

3、使得UI控件能够直接合数据模型中的字段绑定,甚至能响应用户的交互。方便实现MVVM

一、DataBinding简单使用

1、启动DataBinding

在模块下的build.gradle文件中,启动dataBinding。

android {
    dataBinding {
        enabled = true
    }
}
高版本采用
android {
    buildFeatures {
        dataBinding = true
    }
}    

2、将普通布局文件转换为DataBinding布局文件

可在布局文件根标签右键中使用IDE的功能自动转换

在这里插入图片描述

转换后如下:

<?xml version="1.0" encoding="utf-8"?>
<!--DataBinding布局根标签是layout标签-->
<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>
        <!--在这里写布局文件中需要引用到的类或变量-->
    </data>

    <!--原布局文件内容-->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="HelloWorld"
            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>

3、实例化布局文件

将Activity的setContentView(R.layout.xxx)改成DataBindingUtil.setContentView()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //实例化DataBinding对象
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
    }

4、将数据传递到布局文件

第3步后,布局文件转化为了实例对象,那么可以为该实例绑定数据,并在布局文件中使用数据

布局文件中使用数据:

<?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>
    	<!--绑定book对象,类型为com.breeze.jetpackstudy.model.Book-->
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
		
        <!--android:text="@{book.name}",TextView的text属性绑定book对象的name成员-->
        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <!--android:text="@{book.author}",绑定book对象的author成员-->
        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/name"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{book.author}"
            android:layout_marginTop="20dp" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

为布局文件实例,绑定book数据对象,DataBinding为了使用方便,生成了set方法,如本例中binding.book可直接调用

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.book = Book("DataBinding Study", "Breeze")
    }

5、运行结果 在这里插入图片描述

二、在布局文件中引用静态类

有许多场景我们需要对数据的展示做一些处理,比如网络返回的数据bean字段是数字,而展示时要转成对应文案。以往的做法是在代码中去做转化,后重新设置到view上显示。使用databinding可以在布局文件中直接使用静态类转化。

静态工具类:

class BookRatingUtil {

    companion object {
        @JvmStatic
        fun getRatingString(rate : Int) =
            when (rate) {
                0 -> "零星"
                1 -> "一星"
                2 -> "二星"
                3 -> "三星"
                4 -> "四星"
                5 -> "五星"
                else -> ""
            }
    }

}

布局文件:

<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>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
        <!--import引用工具类-->
        <import type="com.breeze.jetpackstudy.BookRatingUtil"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/name"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{book.author}"
            android:layout_marginTop="20dp" />

        <!--使用工具类对book.rate做转化-->
        <TextView
            android:id="@+id/rate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/author"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{BookRatingUtil.getRatingString(book.rate)}"
            android:layout_marginTop="20dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

三、二级页面的绑定

布局文件较复杂时,难免使用include标签将布局进行拆分。此时使用DataBinding该如何传递数据呢?

一级页面如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
        <import type="com.breeze.jetpackstudy.BookRatingUtil"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
		
        <!--给二级页面传递数据-->
        <include layout="@layout/include_layout"
            app:book="@{book}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

二级页面接收传递的数据:

<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>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
        <import type="com.breeze.jetpackstudy.BookRatingUtil"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".SecondLevelActivity">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/author"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/name"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{book.author}"
            android:layout_marginTop="20dp" />

        <TextView
            android:id="@+id/rate"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@id/author"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:text="@{BookRatingUtil.getRatingString(book.rate)}"
            android:layout_marginTop="20dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

四、DataBinding响应事件

1、布局内容如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="eventHandler"
            type="com.breeze.jetpackstudy.EventHandler" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
		
        <!--注意这里传的是方法引用,没有带括号或者参数,写法可以是eventHandler.onBtnClick或eventHandler::onBtnClick,个人觉得::更合适一点-->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Click Me"
            android:onClick="@{eventHandler.onBtnClick}"/>

    </LinearLayout>
</layout>

2、EventHandler类

class EventHandler(private val context : Context) {
	//这里的方法形参view:View不能省略,要和OnClickListener保持相同的方法参数
    fun onBtnClick(view: View) {
        Toast.makeText(context, "I am Clicked", Toast.LENGTH_SHORT).show()
    }

}

3、绑定

class EventActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityEventBinding>(this, R.layout.activity_event)
        binding.eventHandler = EventHandler(this)
    }

}

五、自定义BindingAdapter

以为ImageView增加自定义属性为例

1、编写ImageViewBindingAdapter类,注意方法需要写成静态方法

class ImageViewBindingAdapter {

    companion object {
        @JvmStatic
        @BindingAdapter("image")
        fun setImage(imageView : ImageView, imageUrl : String) {
            if (!TextUtils.isEmpty(imageUrl)) {
                Picasso.with(imageView.context)
                    .load(imageUrl)
                    .into(imageView)
            }
        }
        
        //这里支持重载,并且支持配多个参数,上面的image属性,在下面多个参数的情况出现,实测这种情况执行的是多个参数的这个
        @JvmStatic
        @BindingAdapter(value = ["image", "defaultImageResource"], requireAll = false)
        fun setImage(imageView: ImageView, imageUrl: String, imageResource : Int) {
            Log.e("Breeze", "setImage, image defaultImageResource")
            if (!TextUtils.isEmpty(imageUrl)) {
                Picasso.with(imageView.context)
                    .load(imageUrl)
                    .into(imageView)
            } else {
                imageView.setImageResource(imageResource)
            }
        }
    }

}

2、布局中使用

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="networkImage"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
		<!--这里直接使用app:image属性-->
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:image="@{networkImage}"/>

    </LinearLayout>
</layout>

3、绑定networkImage

val binding = DataBindingUtil.setContentView<ActivityBindingadapterBinding>(this, R.layout.activity_bindingadapter)
        binding.networkImage = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606037072884&di=425fa1754c78e7288acf2bf8797750ba&imgtype=0&src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F00%2F89%2F93%2F6156ee970479161.jpg"

4、DataBinding自身定义的BindingAdapter

ViewBindingAdapter源码,定义了view属性相关的绑定 在这里插入图片描述

ImageViewBindingAdapter源码: 在这里插入图片描述

这些方法在什么时候调用的咱后续再分析~

六、BaseObservable实现双向绑定

上面的案例中实现的都是单向绑定(在数据发生变化时,能做到界面跟着变化,但没有做到界面发生变化时,数据跟着变化)。双向绑定要达到的效果便是除了数据影响界面,界面变化也要使得数据发生变化。比如EditText输入内容时,绑定的数据bean要跟着变化。使用BaseObservable便能实现这一点

class LoginModel(var userName : String)

//固定写法,必须继承BaseObservable
class TwoWayBindingViewModel : BaseObservable() {

    private val loginModel : LoginModel = LoginModel("Breeze")

    //固定写法,get方法必须有Bindable注解
    @Bindable
    fun getUserName() : String {
        return loginModel.userName
    }

    fun setUserName(userName : String) {
        //关键点,此处必须判断数据是否发生变化,否则会发生死循环。当界面发生变化时,会回调到这里
        if (userName != loginModel.userName) {
            loginModel.userName = userName
            Log.e("ZXX", "${loginModel.userName}")
            //固定写法,这里要调用notifyPropertyChanged
            notifyPropertyChanged(BR.userName)
        }
    }
}

布局中使用

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="loginModel"
            type="com.breeze.jetpackstudy.model.TwoWayBindingViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!--这里使用@=进行双向绑定-->
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={loginModel.userName}"/>

    </LinearLayout>
</layout>

Activity注入数据

class TwoWayActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityTwowayBinding>(this, R.layout.activity_twoway)
        binding.loginModel = TwoWayBindingViewModel()
    }

}

七、使用ObservableField-更优雅的双向绑定

使用BaseObservable实现双向绑定时,多处写到"固定写法",这是一种比较不优雅的实现方式,因为容易出错。

在布局文件和Activity中绑定数据代码不变情况下,修改TwoWayBindingViewModel实现为以下方式,使用ObservableField将数据bean进行包装

class TwoWayBindingViewModel {

    private val loginObservableField : ObservableField<LoginModel> = ObservableField()

    init {
        loginObservableField.set(LoginModel("Breeze"))
    }

    fun getUserName():String {
        return loginObservableField.get()?.userName?:""
    }

    fun setUserName(userName:String) {
        loginObservableField.get()?.userName = userName
        Log.e("ZXX","${loginObservableField.get()?.userName}")
    }
}

八、实际使用-更优雅地封装RecyclerView

1、实现思路,使用BindingAdapter为RecyclerView自定义属性

class RecyclerViewBindingAdapter {

    companion object {

        @JvmStatic
        @BindingAdapter("adpter")
        fun setAdapter(view : RecyclerView, adapter : RecyclerView.Adapter<*>) {
            view.adapter = adapter
        }

        @JvmStatic
        @BindingAdapter("list")
        fun setList(view : RecyclerView, vm : RecyclerViewModel) {
            (view.adapter as BookAdapter).setList(vm.bookLiveData.value)
        }

    }

}

2、使用RecyclerView的布局

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="adapter"
            type="com.breeze.jetpackstudy.adapter.BookAdapter" />
        <variable
            name="vm"
            type="com.breeze.jetpackstudy.RecyclerViewModel" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:adapter="@{adapter}"
            app:list="@{vm}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            />

    </RelativeLayout>

</layout>

可以看到,这里adapter,LayoutManager,数据List都使用dataBinding进行设置,在Activity中就不需要再为RecyclerView设置这些以往必须要加的属性了

3、Activity代码

class RecyclerViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityRecyclerviewBinding>(this, R.layout.activity_recyclerview)
        val model = ViewModelProviders.of(this).get(RecyclerViewModel::class.java)
        binding.apply {
            //这个lifecycleOwner用到了ViewModel则必须设置
            lifecycleOwner = this@RecyclerViewActivity
            vm = model
            adapter = BookAdapter()
        }
    }

}

4、列表每一项的布局文件,也是用DataBinding绑定数据

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="book"
            type="com.breeze.jetpackstudy.model.Book" />
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp">
		<!--绑定数据-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}" />

    </RelativeLayout>
</layout>

5、Adapter编写

class BookAdapter : RecyclerView.Adapter<BookAdapter.BookViewHolder>() {

    private var list : List<Book>? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = DataBindingUtil.inflate<BookItemBinding>(inflater, R.layout.book_item, parent,false)
        return BookViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return list?.size?:0
    }

    override fun onBindViewHolder(holder: BookViewHolder, position: Int) {
        val book = list!![position]
        //刷新数据使用databinding实现,不用再写各种findViewById设置值了~
        holder.binding.book = book
    }

    fun setList(list : List<Book>?) {
        if (list != null) {
            this.list = list
            notifyDataSetChanged()
        }
    }

    class BookViewHolder constructor(val binding : BookItemBinding) : RecyclerView.ViewHolder(binding.root)

}

九、DataBinding实现原理

简单地以MainActivity中的案例分析,只分析单向绑定

看最简单的binding过程,layout的数据如何绑定上的。使用jadx反编译工具查看demo代码,MainActivity代码:

public final class MainActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
   		这里生成binding
        ActivityMainBinding binding = (ActivityMainBinding) DataBindingUtil.setContentView(this, R.layout.activity_main);
        Book book = new Book("DataBinding Study", "Breeze", 5);
        Intrinsics.checkExpressionValueIsNotNull(binding, "binding");
        //这里设置数据后,刷新界面
        binding.setBook(book);
        new Handler().postDelayed(new MainActivity$onCreate$1(binding, book), 1000);
    }
}

ActivityMainBinding的生成过程:

DataBindingUtil.setContentView源码:

    public static <T extends ViewDataBinding> T setContentView(Activity activity, int layoutId, DataBindingComponent bindingComponent) {
        //这里已经调用了setContentView
        activity.setContentView(layoutId);
        //这里生成DataBinding对象,
        return bindToAddedViews(bindingComponent, (ViewGroup) activity.getWindow().getDecorView().findViewById(16908290), 0, layoutId);
    }

bindToAddedView代码会走到bind方法

  static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root, int layoutId) {
        return sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
    public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
        //这里是根据layoutId获取序号的逻辑,布局被依次排序为1,2,3...
        int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
        if (localizedLayoutId <= 0) {
            return null;
        }
        //DataDinding处理布局会给布局加上tag
        Object tag = view.getTag();
        if (tag != null) {
            switch (localizedLayoutId) {
                    //这里省略了很多case,方便查看
                ...
                case 3:
                    if ("layout/activity_main_0".equals(tag)) {
                        return new ActivityMainBindingImpl(component, view);
                    }
                    throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
                ...
                default:
                    return null;
            }
        } else {
            throw new RuntimeException("view must have a tag");
        }
    }

关键点来了,ActivityMainBindingImpl中的代码实现的逻辑

//生成的类是继承ActivityMainBinding类的
class ActivityMainBindingImpl extends ActivityMainBinding
//ActivityMainBinding又是继承ViewDataBinding类的
abstract class ActivityMainBinding extends ViewDataBinding

private ActivityMainBindingImpl(DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0, bindings[2], bindings[1], bindings[3]);
        this.mDirtyFlags = -1;
        this.author.setTag((Object) null);
        ConstraintLayout constraintLayout = bindings[0];
        this.mboundView0 = constraintLayout;
        constraintLayout.setTag((Object) null);
        this.name.setTag((Object) null);
        this.rate.setTag((Object) null);
        setRootTag(root);
        //这里真正开始处理界面数据
        invalidateAll();
    }

	//真正执行绑定数据的方法,下面会分析如何执行的。
   public void executeBindings() {
        long dirtyFlags;
        synchronized (this) {
            dirtyFlags = this.mDirtyFlags;
            this.mDirtyFlags = 0;
        }
        String bookName = null;
        int bookRate = 0;
        Book book = this.mBook;
        String bookRatingUtilGetRatingStringBookRate = null;
        String bookAuthor = null;
        if ((dirtyFlags & 3) != 0) {
            if (book != null) {
                bookName = book.getName();
                bookRate = book.getRate();
                bookAuthor = book.getAuthor();
            }
            bookRatingUtilGetRatingStringBookRate = BookRatingUtil.getRatingString(bookRate);
        }
        if ((3 & dirtyFlags) != 0) {
        	//揭秘了,其实最终就是用的各种BindingAdapter给view设置数据
            TextViewBindingAdapter.setText(this.author, bookAuthor);
            TextViewBindingAdapter.setText(this.name, bookName);
            TextViewBindingAdapter.setText(this.rate, bookRatingUtilGetRatingStringBookRate);
        }
    }

  public void invalidateAll() {
        synchronized (this) {
            this.mDirtyFlags = 2;
        }
        //请求绑定,实现在父类的父类ViewDataBinding类中
        requestRebind();
    }

ViewDataBinding类中requestRebind方法内的逻辑

//这里只列举重要逻辑
private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
//判断了版本大于16,就采用Choreographer在收到vsync信号时执行回调,mFrameCallBack实际只是包装了一下RebindRunnable
if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }

//RebindRunnable中的代码
   public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.executePendingBindings();
        }
    }
//最终executeBindingsInternal执行的是ActivityMainBindingImpl类中的executeBindings方法,于是绑定到了数据。在初始化时ActivityMainDataBinding时。

下面分析setBook时如何刷新数据的:

    public void setBook(Book Book) {
        this.mBook = Book;
        synchronized (this) {
            this.mDirtyFlags |= 1;
        }
        notifyPropertyChanged(2);
        //可以看到这里实际上也是走到requestRebind方法
        super.requestRebind();
    }

总结,单相绑定其实最终执行的就是调用各种BindingAdapter类绑定数据,实现界面刷新。而界面刷新的时机是设置数据后,vsync信号来临时(版本大于16时,但现在16以下应该没了吧)