一.簡(jiǎn)介
???????DataBinding是谷歌發(fā)布的一個(gè)實(shí)現(xiàn)數(shù)據(jù)和UI綁定的框架,從字面意思來看即為數(shù)據(jù)綁定,是 MVVM 模式在Android上的一種實(shí)現(xiàn),用于降低布局和邏輯的耦合性,使代碼邏輯更加清晰。
???????相對(duì)于 MVP,MVVM將Presenter層替換成了ViewModel層,關(guān)于MVC、MVP、MVVM架構(gòu)比較,可以參考文章Android MVC、MVP、MVVM比較分析。
???????DataBinding 能夠省去我們一直以來的 findViewById() 步驟,大量減少 Activity 內(nèi)的代碼,數(shù)據(jù)能夠單向或雙向綁定到 layout 文件中。
???????接下來本文通過一個(gè)例子來學(xué)習(xí)一下DataBinding的用法及原理分析。
二.實(shí)例分析
a.加入dataBinding支持
???????使用DataBinding框架需要在對(duì)應(yīng)Model的build.gradle文件Android{}內(nèi)加入以下代碼,同步后就能引入對(duì) DataBinding的支持:
dataBinding {
enabled = true
}
???????AndroidStudio高版本引入方式如下:
buildFeatures {
dataBinding = true
}
b.xml布局文件轉(zhuǎn)化
???????將常規(guī)的布局文件轉(zhuǎn)化為對(duì)應(yīng)的數(shù)據(jù)綁定布局文件,在布局文件的根布局上輸入Alt+Enter后,選擇Convert to data binding layout會(huì)將常規(guī)布局轉(zhuǎn)換為以下布局文件形式:
<?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">
<data>
//布局文件用到了View相關(guān)的方法,需要引入
<import type="android.view.View" />
<variable
//任意取名字,來作為type對(duì)應(yīng)的引用,可以通過guide代替類取可變變量
//在view中通過setGuide()建立引用關(guān)系
name="guide"
//定義可變變量的類
type="com.hly.learn.ViewModel" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/name"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:gravity="center"
android:textColor="@android:color/holo_red_light"
android:text="@{guide.rightImageName}" />
<ImageView
android:layout_width="200dp"
android:layout_height="160dp"
android:layout_below="@+id/name"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:scaleType="fitXY"
android:src="@{guide.rightImageRes}" />
<TextView
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_centerHorizontal="true"
android:layout_marginTop="200dp"
android:textColor="@color/colorAccent"
android:text="@{guide.rightImageDescription}" />
<TextView
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_below="@+id/name"
android:layout_marginLeft="50dp"
android:layout_marginTop="350dp"
android:gravity="center"
android:onClick="@{guide::upStep}"
android:text="@string/up"
android:textStyle="bold"
android:textColor="@color/cardview_shadow_start_color"
android:visibility="@{guide.step!=1?View.VISIBLE:View.INVISIBLE}" />
<TextView
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_below="@+id/name"
android:layout_marginLeft="230dp"
android:layout_marginTop="350dp"
android:gravity="center"
android:onClick="@{guide::nextStep}"
android:text="@string/next"
android:textStyle="bold"
android:visibility="@{guide.step!=3?View.VISIBLE:View.INVISIBLE}" />
</RelativeLayout>
</layout>
??????通過以上轉(zhuǎn)化,已經(jīng)將常規(guī)布局轉(zhuǎn)化為數(shù)據(jù)綁定布局文件,那么下面就一步一步的來講解實(shí)現(xiàn)。
c.View邏輯實(shí)現(xiàn)
??????將之前常規(guī)的UI操作從view里面移到數(shù)據(jù)綁定布局xml文件中,可以大大減少view的代碼量及相關(guān)的UI操作邏輯。
??????使用前,動(dòng)態(tài)設(shè)置ImageView的顯示,在view里面的操作如下:
ImageView mImg = findViewById(R.id.img);
mImg.setImageResource(R.drawable.rse);
??????使用databinding后,可以在布局文件中將布局變量賦值以@{}形式給ImageView的src屬性。當(dāng)rightImageRes變化時(shí),ImageView會(huì)動(dòng)態(tài)加載對(duì)應(yīng)的資源文件。
<ImageView
android:layout_width="200dp"
android:layout_height="160dp"
android:layout_below="@+id/name"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:scaleType="fitXY"
android:src="@{guide.rightImageRes}" />
??????使用數(shù)據(jù)綁定布局文件后,view對(duì)應(yīng)的類變?yōu)椋?/p>
package com.hly.learn.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.hly.learn.ViewModel;
import com.hly.learn.databinding.KeyboardLayoutBinding;
import com.hly.learn.R;
import androidx.databinding.DataBindingUtil;
public class KeyboardFragment extends BaseFragment {
private ViewModel mViewModel;
@Override
public int getLayoutId() {
return R.layout.keyboard_layout;
}
@Override
public void initData(View view) {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//-------------------分析1------------------------------------
KeyboardLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.keyboard_layout, container, false);
//獲取布局對(duì)應(yīng)的view
//--------------------分析2-----------------------------------
View view = binding.getRoot();
//-------------------分析3----------------------------------
mViewModel = new ViewModel(mContext);
binding.setGuide(mViewModel);
//-------------------分析4----------------------------------
view.viewpager.setAdapter(xx);
return view;
}
}
??????通過以上可以發(fā)現(xiàn),主要的分析點(diǎn)如下:
??????分析1:通過DataBindingUtil來替代inflater來解析數(shù)據(jù)綁定布局文件,KeyboardLayoutBinding是在編譯時(shí)根據(jù)keyboard_layout生成的(后面會(huì)有詳細(xì)分析);如果為Activity,通過setContentView()方法來解析生成KeyboardLayoutBinding;
??????分析2:通過getRoot()獲取布局文件對(duì)應(yīng)的View;
??????分析3:創(chuàng)建ViewModel,并通過setGuide()(名字不是固定的)將View與ViewModel里面的變量建立關(guān)聯(lián);
??????分析4:直接通過view.idName來獲取對(duì)應(yīng)的view,然后進(jìn)行操作;
??????使用了數(shù)據(jù)綁定布局后,view里面除了加載布局后,沒有任何相關(guān)的findViewById操作,確實(shí)減少了不少UI處理邏輯。
d.VM實(shí)現(xiàn)
??????數(shù)據(jù)綁定框架提供了可觀察的變量,像ObservableInt,ObservableBoolean來替代原始數(shù)據(jù)類型int,boolean,使其具備可觀察能力。提供了ObservableField來替代引用數(shù)據(jù)類型,使其具備可觀察能力。具體的代碼實(shí)現(xiàn)如下:
package com.hly.learn;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;
public class ViewModel {
private Context mContext;
public ViewModel(Context context) {
step.set(1);
mContext = context;
updateImageInfo(step.get());
}
//定義用到的可變變量類型
public ObservableInt step = new ObservableInt();
public ObservableField<Drawable> rightImageRes = new ObservableField<>();
public ObservableField<String> rightImageName = new ObservableField<>();
public ObservableField<String> rightImageDescription = new ObservableField<>();
public void nextStep(View view) {
step.set(step.get() + 1);
updateImageInfo(step.get());
}
public void upStep(View view) {
step.set(step.get() - 1);
updateImageInfo(step.get());
}
//更新ObservableField對(duì)應(yīng)的變量值
private void updateImageInfo(int step) {
rightImageRes.set(ModalData.getDrawable(mContext, step));
rightImageName.set(ModalData.getImageName(mContext, step));
rightImageDescription.set(ModalData.getImageDes(mContext, step));
}
}
??????DataBinding框架用在MVVM模式下,View是加載布局,ViewModel來處理布局對(duì)應(yīng)的交互,Model是來加載數(shù)據(jù)。ViewModel從Model里面去數(shù)據(jù),供UI顯示。
??????最后加入Model模塊:
e.Model實(shí)現(xiàn)
package com.hly.learn;
import android.content.Context;
import android.graphics.drawable.Drawable;
public class ModalData {
public static Drawable getDrawable(Context context, int index) {
if (index == 1) {
return context.getResources().getDrawable(R.drawable.ic_chuancai);
} else if (index == 2) {
return context.getResources().getDrawable(R.drawable.ic_lucai);
} else {
return context.getResources().getDrawable(R.drawable.ic_xiangcai);
}
}
public static String getImageName(Context context, int index) {
if (index == 1) {
return context.getResources().getString(R.string.chuancai);
} else if (index == 2) {
return context.getResources().getString(R.string.lucai);
} else {
return context.getResources().getString(R.string.xiangcai);
}
}
public static String getImageDes(Context context, int index) {
if (index == 1) {
return context.getResources().getString(R.string.chuancaides);
} else if (index == 2) {
return context.getResources().getString(R.string.lucaides);
} else {
return context.getResources().getString(R.string.xiangcaides);
}
}
}
f.事件綁定
??????事件綁定也是一種變量綁定,只不過設(shè)置的變量是回調(diào)接口而已。
??????點(diǎn)擊TextView響應(yīng),在view里面的操作如下:
TextView tv = view.findViewById(R.id.tv);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
upStep();
}
});
??????使用databinding后,可以在布局文件中將TextView的onClick屬性加入執(zhí)行方法即可,當(dāng)TextView點(diǎn)擊后,會(huì)執(zhí)行相應(yīng)的方法。
<TextView
android:layout_width="100dp"
android:layout_height="50dp"
android:onClick="@{guide::upStep}"
android:text="@string/up"
android:visibility="@{guide.step!=1?View.VISIBLE:View.INVISIBLE}"
三.DataBinding原理分析
??????DataBinding實(shí)現(xiàn)了數(shù)據(jù)與UI綁定,那是如何綁定的呢?數(shù)據(jù)變化后是如何對(duì)應(yīng)UI進(jìn)行更新的呢?一起看一下實(shí)現(xiàn)過程。
??????使用AndroidStudio進(jìn)行開發(fā)時(shí),對(duì)應(yīng)用編譯后,會(huì)在對(duì)應(yīng)目錄下生成額外的文件:
??????在build/generated/ap_generated_sources/debug/out目錄下會(huì)生成對(duì)應(yīng)的文件:DataBinderMapperImpl.java(兩個(gè)),KeyboardLayoutBindingImpl.java文件;
??????在build/generated/data_binding_base_class_source_out/debug/out目錄下會(huì)生成對(duì)應(yīng)的文件:KeyboardLayoutBinding.java;
??????在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目錄下會(huì)生成對(duì)應(yīng)的布局文件:keyboard_layout.xml;
??????接下來會(huì)一一進(jìn)行分析。
??????上面我們看到,在KeyBoardFragment內(nèi)部的onCreateView()內(nèi)部執(zhí)行的邏輯,再一起看一下:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
KeyboardLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.keyboard_layout, container, false);
View view = binding.getRoot();
mViewModel = new ViewModel(mContext);
binding.setGuide(mViewModel);
return view;
}
a.DataBindingUtil.java
??????在Fragement的onCreateView()內(nèi)部通過DataBindingUtil.inflate()來創(chuàng)建KeyboardLayoutBinding對(duì)象binding,先看一下DataBindingUtil的inflate()實(shí)現(xiàn):
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
public static <T extends ViewDataBinding> T inflate(@NonNull LayoutInflater inflater,
int layoutId, @Nullable ViewGroup parent, boolean attachToParent) {
return inflate(inflater, layoutId, parent, attachToParent, sDefaultComponent);
}
public static <T extends ViewDataBinding> T inflate(
@NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
boolean attachToParent, @Nullable DataBindingComponent bindingComponent) {
final boolean useChildren = parent != null && attachToParent;
final int startChildren = useChildren ? parent.getChildCount() : 0;
final View view = inflater.inflate(layoutId, parent, attachToParent);
if (useChildren) {
return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
} else {
return bind(bindingComponent, view, layoutId);
}
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
??????inflate()內(nèi)部會(huì)通過inflater,inflate()來加載對(duì)應(yīng)layoutId的view,由于attachToParent為false,所以在inflate()內(nèi)部直接執(zhí)行bind()方法,在bind()內(nèi)部會(huì)通過sMapper.getDataBinder()來返回ViewDataBinding的實(shí)現(xiàn)類對(duì)象,sMapper是DataBinderMapperImpl對(duì)象,接下來看一下DataBinderMapperImpl實(shí)現(xiàn)。
b.DataBinderMapperImpl.java
??????前面說到,DataBinderMapperImpl.java是編譯生成的文件,繼承了MergedDataBinderMapper類,內(nèi)部就一個(gè)構(gòu)造方法內(nèi)執(zhí)行了addMapper()方法:
package androidx.databinding;
public class DataBinderMapperImpl extends MergedDataBinderMapper {
DataBinderMapperImpl() {
addMapper(new com.hly.learn.DataBinderMapperImpl());
}
}
??????在創(chuàng)建DataBinderMapperImpl對(duì)象的時(shí)候,在構(gòu)造方法內(nèi)執(zhí)行addMapper()將創(chuàng)建com.hly.learn.DataBinderMapperImpl()對(duì)象作為參數(shù)傳入,后續(xù)通過sMapper.getDataBinder()來獲取對(duì)應(yīng)的ViewDataBinding的實(shí)現(xiàn)類時(shí),會(huì)最終調(diào)用到com.hly.learn.DataBinderMapperImpl.java中的getDataBinder()方法,接下來看一下com.hly.learn.DataBinderMapperImpl.java這個(gè)類的邏輯實(shí)現(xiàn):
c.DataBinderMapperImpl.java
??????此DataBinderMapperImpl.java不同于b步的DataBinderMapperImpl.java,注意區(qū)分,該類是主要的實(shí)現(xiàn)類:
public class DataBinderMapperImpl extends DataBinderMapper {
private static final int LAYOUT_KEYBOARDLAYOUT = 1;
private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
static {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.hly.learn.R.layout.keyboard_layout, LAYOUT_KEYBOARDLAYOUT);
}
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_KEYBOARDLAYOUT: {
if ("layout/keyboard_layout_0".equals(tag)) {
return new KeyboardLayoutBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for keyboard_layout is invalid. Received: " + tag);
}
}
}
return null;
}
......
}
??????DataBinderMapperImpl實(shí)現(xiàn)了DataBinderMapper類,定義了靜態(tài)變量LAYOUT_KEYBOARDLAYOUT和靜態(tài)數(shù)組INTERNAL_LAYOUT_ID_LOOKUP(可能有多個(gè)使用databinding的layout),在靜態(tài)代碼塊內(nèi)將R.layout.keyboard_layout與LAYOUT_KEYBOARDLAYOUT建立映射關(guān)系存入INTERNAL_LAYOUT_ID_LOOKUP內(nèi),在執(zhí)行g(shù)etDataBinder時(shí),通過創(chuàng)建時(shí)傳入的layoutId從INTERNAL_LAYOUT_ID_LOOKUP找到對(duì)應(yīng)的localizedLayoutId,然后根據(jù)view的tag關(guān)系來創(chuàng)建KeyboardLayoutBindingImpl對(duì)象,該tag體現(xiàn)在對(duì)應(yīng)生成的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" android:tag="layout/keyboard_layout_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/name"
.....
android:tag="binding_1" />
<ImageView
.......
android:tag="binding_2" />
<TextView
.......
android:tag="binding_3" />
<TextView
.......
android:tag="binding_4"
....../>
<TextView
........
android:tag="binding_5"
......./>
<TextView
.........
android:tag="binding_6" />
</RelativeLayout>
??????簡(jiǎn)單總結(jié)一下:在onCreateView()內(nèi)部執(zhí)行DataBindingUtil.inflate(),通過一步一步的調(diào)用后,最終返回的是KeyboardLayoutBindingImpl對(duì)象,該類是KeyboardLayoutBinding的實(shí)現(xiàn)類。
??????接下來看一下KeyboardLayoutBindingImpl.java這個(gè)類:
d.KeyboardLayoutBindingImpl.java
public class KeyboardLayoutBindingImpl extends KeyboardLayoutBinding {
private KeyboardLayoutBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 5
, (android.widget.TextView) bindings[1]
);
this.mboundView0 = (android.widget.RelativeLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView2 = (android.widget.ImageView) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.TextView) bindings[3];
this.mboundView3.setTag(null);
this.mboundView4 = (android.widget.TextView) bindings[4];
this.mboundView4.setTag(null);
this.mboundView5 = (android.widget.TextView) bindings[5];
this.mboundView5.setTag(null);
this.mboundView6 = (android.widget.TextView) bindings[6];
this.mboundView6.setTag(null);
this.name.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
@Override
public void invalidateAll() {
synchronized(this) {
mDirtyFlags = 0x40L;
}
requestRebind();
}
public void setGuide(@Nullable com.hly.learn.viewmodel.ViewModel Guide) {
this.mGuide = Guide;
synchronized(this) {
mDirtyFlags |= 0x20L;
}
notifyPropertyChanged(BR.guide);
super.requestRebind();
}
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeGuideRightImageDescription((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
......
}
@Override
protected void executeBindings() {
......
//注冊(cè)觀察者
updateRegistration(0, guideRightImageDescription);
......
//回調(diào)后進(jìn)行UI更新
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, guideRightImageDescriptionGet);
......
}
}
??????該類繼承了KeyboardLayoutBinding類,在構(gòu)造方法內(nèi),會(huì)調(diào)用父類的構(gòu)造方法,將root view[即對(duì)應(yīng)R.layout.keyboard_layout進(jìn)行inflate生成的view]傳給父類,在Fragment內(nèi)部的onCreateView()中通過getRoot()返回對(duì)應(yīng)layoutId創(chuàng)建的View,其他方法的邏輯執(zhí)行會(huì)在接下來的數(shù)據(jù)與UI綁定時(shí)進(jìn)行介紹。
e.KeyboardLayoutBinding.java
public abstract class KeyboardLayoutBinding extends ViewDataBinding {
@NonNull
public final TextView name;
@Bindable
protected ViewModel mGuide;
protected KeyboardLayoutBinding(Object _bindingComponent, View _root, int _localFieldCount,
TextView name) {
super(_bindingComponent, _root, _localFieldCount);
this.name = name;
}
public abstract void setGuide(@Nullable ViewModel guide);
@Nullable
public ViewModel getGuide() {
return mGuide;
}
.......
.......
}
??????KeyboardLayoutBinding是個(gè)抽象類,繼承了ViewDataBinding。
f.ViewDataBinding.java
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {
.......
.......
private final View mRoot;
.......
protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
mBindingComponent = bindingComponent;
mLocalFieldObservers = new WeakListener[localFieldCount];
this.mRoot = root;
if (Looper.myLooper() == null) {
throw new IllegalStateException("DataBinding must be created in view's UI Thread");
}
.......
}
public View getRoot() {
return mRoot;
}
.......
.......
??????在創(chuàng)建KeyboardLayoutBindingImpl后,構(gòu)造方法會(huì)一步一步的向上傳,最終將root view保存在ViewDataBinding中,然后通過getRoot()獲取到view。
四.數(shù)據(jù)與UI綁定分析
??????通過上述分析可以看到了DataBindingUtil.inflate創(chuàng)建KeyboardLayoutBinding的整個(gè)過程,那數(shù)據(jù)與UI任何綁定的呢?
??????在KeyboardLayoutBindingImpl的構(gòu)造方法內(nèi),會(huì)調(diào)用 invalidateAll(),接下來看一下綁定流程:
a.ObservableField進(jìn)行observe()
??????DataBinding使用的是觀察者模式,ObservableField數(shù)據(jù)注冊(cè)觀察者是在創(chuàng)建DataBinding的時(shí)候在構(gòu)造方法中就執(zhí)行了,先創(chuàng)建了對(duì)應(yīng)ObservableField數(shù)量的WeakListener數(shù)組,然后執(zhí)行流程如下:
??????------>invalidateAll()
????????????------>requestRebind()
??????????????????------>executePendingBindings()
????????????????????????------>executeBindingsInternal()
??????????????????????????????------> executeBindings()[Impl]
????????????????????????????????????------>updateRegistration(localFieldId, Observable observable)
??????????????????????????????????????????------>updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER)[創(chuàng)建WeakPropertyListener]
????????????????????????????????????????????????------>registerTo()[將上步中創(chuàng)建的WeakPropertyListener賦值給執(zhí)行創(chuàng)建的WeakListener對(duì)應(yīng)的數(shù)組值]
??????????????????????????????????????????????????????------>listener.setTarget(observable)
????????????????????????????????????????????????????????????------>WeakPropertyListener.addListener(Observable)
??????????????????????????????????????????????????????????????????------>Observable.addOnPropertyChangedCallback(this);
??????經(jīng)過以上邏輯執(zhí)行,Observable[ObservableField]注冊(cè)了OnPropertyChanged callback,如果數(shù)據(jù)變化后,會(huì)回調(diào)OnPropertyChanged()方法,流程圖如下:

??????以上就是數(shù)據(jù)與UI綁定過程,那數(shù)據(jù)變化后,是如何反饋到UI上呢?接下來看一下數(shù)據(jù)變化后UI更新流程:
b.ObservableField數(shù)據(jù)變化后UI更新
??????ObservableFiled數(shù)據(jù)變化后,最終UI更新執(zhí)行流程如下:
??????------>set(value)
????????????------>notifyChange()
??????????????????------>mCallbacks.notifyCallbacks(this, 0, null)[mCallbacks是PropertyChangeRegistry,通過上述addOnPropertyChangedCallback()加入mCallbacks列表]
????????????????????????------>mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2)
??????????????????????????????------>callback.onPropertyChanged(sender, arg)
????????????????????????????????????------>WeakPropertyListener.onPropertyChanged()
??????????????????????????????????????????------>handleFieldChange()
????????????????????????????????????????????????------>onFieldChange()------>requestRebind()----->......
??????????????????????????????????????????????????????------>executeBindings()[Impl]
??????在ObservableField數(shù)據(jù)變化后,最終會(huì)調(diào)用到Impl類里面的executeBindings()來更新UI,流程圖如下:

??????以上就是數(shù)據(jù)變化后UI更新的整個(gè)流程。
五.BindingAdapter
??????DataBinding提供了BindingAdapter這個(gè)注解用于支持自定義屬性,或者是修改原有屬性。注解值可以是已有的 xml 屬性,例如 android:src、android:text等,也可以自定義屬性然后在 xml 中使用。
??????例如,對(duì)于一個(gè)TextView ,希望在某個(gè)變量值發(fā)生變化時(shí),可以動(dòng)態(tài)改變顯示的文字,此時(shí)就可以通過 BindingAdapter來實(shí)現(xiàn)。
??????需要先定義一個(gè)靜態(tài)方法,為之添加 BindingAdapter 注解,注解值是為TextView控件自定義的屬性名,而該靜態(tài)方法的兩個(gè)參數(shù)可以這樣來理解:當(dāng)TextView控件的 step屬性值發(fā)生變化時(shí),DataBinding 就會(huì)將TextView實(shí)例以及新的step值傳遞給setProperty() 方法,從而可以根據(jù)此動(dòng)態(tài)來改變TextView的相關(guān)屬性。
<TextView
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_below="@+id/name"
android:layout_centerHorizontal="true"
android:layout_marginTop="400dp"
android:gravity="center"
android:textStyle="bold"
app:step="@{guide.step}"/>
??????BindingAdapter實(shí)現(xiàn)如下:
package com.hly.learn;
import android.util.Log;
import android.widget.TextView;
import androidx.databinding.BindingAdapter;
//可以單獨(dú)寫一個(gè)類,統(tǒng)一處理所有使用BindingAdapter注解的控件
public class ViewBinding {
@BindingAdapter(value = {"app:step"})
public static void setProperty(TextView textView, int step) {
Log.e("Seven", "step is: " + step);
//可以根據(jù)step來設(shè)置textView的屬性,例如改變文字,設(shè)置寬高等...
textView.setText(xxx);
}
@BindingAdapter(value = {"app:url"})
public static void updateImg(ImageView imageView, String url) {
Glide.with(imageView.getContext()).load(url).into(imageView);
}
}
六.效果圖


??????以上是結(jié)合實(shí)例及源碼對(duì)DataBinding及ObservableField進(jìn)行UI綁定及更新進(jìn)行了分析,詳細(xì)過程還需閱讀ViewDataBinding、BaseObservable、PropertyChangeRegistry、CallbackRegistry等類文件來進(jìn)一步分析。