轉載請標明出處http://www.itdecent.cn/p/fb8d33168f57
前言:畢業(yè)快3年了,出來工作之后一直是傾向于 Android相關平臺逆向以及廣告SDK的開發(fā)和學習,一直想補一下Android開發(fā)相關的知識點,國慶前群里老哥們聊起了MVVM和DataBinding,我心里終究是按耐不住好奇心了,于是乎也打開了瀏覽器,學了起來,其實本來一開始是想學MVVM先,畢竟databinding也是配合MVVM的一套工具,一種實現(xiàn)。但是機緣巧合先學起了這個,后面再補一篇MVVM的學習。
1.DataBinding有什么用,能解決什么問題
DataBinding是一個Google官方發(fā)布的框架,是為了應對日常開發(fā)中數(shù)據(jù)層和UI層之間的傳遞的。我們日常開發(fā)中需要自己手動去寫findViewById,setOnClickListener這類厚重的代碼。這個框架就可以簡便的實現(xiàn)數(shù)據(jù)和UI的傳遞,UI對數(shù)據(jù)的監(jiān)聽,UI的刷新。
2.DataBinding的使用
DataBinding作為官方直推,更是不用添加依賴使用,而是gradle直接支持了,我們只需要在項目app下build.gradle之中的android下添加下面代碼就可以使用,如果添加了之后無法使用,可以嘗試升級AS版本。
android {
dataBinding {
enabled = true;
}
}
3.DataBinding的代碼實現(xiàn)
3.1.視圖與數(shù)據(jù)變量的綁定
<layout xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="text"
type="com.llyyyw.databinding.TextBean" />
<variable
name="activity"
type="com.llyyyw.databinding.MainActivity" />
</data>
<!--往下開始常規(guī)的UI布局-->
</layout>
DataBinding對XML布局格式有一定的要求,需要在xml最外層使用layout包裹,之后里面分別寫入data以及正常UI布局。其中data標簽中聲明你變量名以及變量類,我這里就是聲明了自己寫的TextBean類,命名為text。供下面使用。
3.2.視圖數(shù)據(jù)的使用以及控件事件監(jiān)聽
DataBinding會根據(jù)activity和xml自動生成一個ViewDataBinding類,這個類負責了view的管理和邏輯,事件監(jiān)聽以及變量的管理。我這個類是放在build文件夾下,這個看看它是如何實現(xiàn)的。

之后在想要操作view的類中進行viewDataBinding的實例化,設置數(shù)據(jù)。之后XML可以通過@{}的格式配合變量名獲取,比方說@{text.title}。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
textBean = new TextBean();
textBean.setTitle("dataBinding你好");
textBean.setText("dataBinding你好");
viewDataBinding.setActivity(this);
viewDataBinding.setText(textBean);
}
<TextView
android:id="@+id/tv_title2"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{text.title}" />
這個text就是傳遞過去的“dataBinding你好”。
除了數(shù)據(jù)的傳遞,還有對于view事件的監(jiān)聽,比如點擊事件。
<TextView
android:id="@+id/tv_title2"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{activity::updateTitle}"
android:text="@{text.title}" />
這里就是調用了傳遞進去的activity中的updateTitle方法,這里activity::updateTitle這種格式的調用,規(guī)定這個方法必須要是一個有View參數(shù)的方法。
public void updateTitle(View view) {
textBean.setTitle("dataBinding你好" + i);
i++;
}
當然這個activity完全可以是你想要調用的任意類,updateTitle也可以是類里的任意方法。只要你在data里綁定了數(shù)據(jù)。
除此之外,還有其他比如@{() -> activity.updateTitle(obj)},這種寫法無論帶不帶參數(shù),方法參數(shù)對應上即可。所以其實也可以實現(xiàn)在xml中傳遞參數(shù)到方法里。
3.3.BindingAdapter,視圖控件對于數(shù)據(jù)變化監(jiān)聽。
BindingAdapter是DataBinding提供的一個用于控件對于變量發(fā)生變化的監(jiān)聽的。
@BindingAdapter("android:text_title_check")
public static void checkTitle(TextView view, CharSequence text) {
CharSequence oldText = view.getText();
if (!haveContentsChanged(text, oldText)) {
return; // 數(shù)據(jù)沒有變化不進行刷新視圖
}
view.setText(text);
}
這里@BindingAdapter()中可以根據(jù)你自己的需要進行命名,方法名也可以隨意,但是要注意參數(shù)以及XML中調用時的對應。
<TextView
android:id="@+id/tv_title2"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text_title_check="@{text.title}"/>
這樣子,當傳入BindingAdapter標注的方法的變量發(fā)生變化時,就會調用該方法,該方法可以進行一定的邏輯操作,比如圖片的加載,文本的判定。
3.4.單向和雙向數(shù)據(jù)綁定
前面我們使用了單向數(shù)據(jù)綁定,數(shù)據(jù)變化引起視圖Textview中的更新。
DataBinding提供了BaseObservable、ObservableField、ObservableCollection三種方式實現(xiàn)。
我這里使用BaseObservable為例。
public class TextBean extends BaseObservable implements Serializable {
private String title;
private String text;
@Bindable
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
notifyPropertyChanged(BR.title);
}
@Bindable
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
// 需要手動刷新
notifyPropertyChanged(BR.text);
}
}
Bindable注解get方法,notifyPropertyChanged在set方法刷新。也可以使用ObservableField這個類,對BaseObservable綁定和解綁的進一步封裝。
BaseObservable是官方實現(xiàn)的觀察器,該接口自己實現(xiàn)了綁定和解綁,我們只需要專注于數(shù)據(jù)的傳遞更新。
這樣子配合@{}就可以達到數(shù)據(jù)變化,視圖也實時變化。
但是我現(xiàn)在有一個需求,我現(xiàn)在有一個輸入框editText,或者一個多選框,這個框的結果我必須知道并拿來使用,正常的開發(fā)中,我們會通過獲取控件之后getText之類的方法獲取框中的實時內容,很麻煩。但是DataBinding提供了我們雙向綁定。我們只需要通過@={}的寫法,就可以實現(xiàn)
視圖改變==>數(shù)據(jù)實時改變。
數(shù)據(jù)改變==>視圖實時改變。
<EditText
android:id="@+id/et_title"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={text.title}" />
效果如下:
像這樣,點擊textview的事件中對數(shù)據(jù)進行修改,由于textview單向綁定,editText雙向綁定,都指向同一個數(shù)據(jù),所以都實時的改變。editText修改的時候也同理。
3.5.DataBinding自提供變量
如果現(xiàn)在我們有一個需求,需要在布局中使用上下文的變量,那我們該怎么做。正常思路就是繼續(xù)使用data,Variable標簽。
其實DataBinding自己也提供了一些Variable的,比方說context。 是可以直接使用的. 代表View的getContext()。
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->activity.packageName(context.applicationContext.packageName)}"
android:text="@{context.applicationContext.packageName}"/>
我這里就配合上面講的控件事件,將context.applicationContext.packageName作為一個string參數(shù),傳遞給了packageName方法。
public void packageName(String packageName) {
Toast.makeText(this, packageName, Toast.LENGTH_SHORT).show();
}

3.6.DataBinding和include的使用
關于include的使用,其實主要是主activity和包含布局之間要使用 bind來傳遞數(shù)據(jù)。包含布局中也要用data和variable實現(xiàn)數(shù)據(jù)變量和view的綁定。注意bind:xxx和variable name="xxx",這里兩者xxx必須是一樣的才能匹配到。
<include
layout="@layout/include_main"
bind:textText="@{text.text}"/>
<layout>
<data>
<variable
name="textText"
type="String" />
</data>
<TextView
tools:context=".MainActivity"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{textText}">
</TextView>
</layout>
4.DataBinding的簡單分析
DataBinding的簡單使用可以說是很簡單,省去了很多代碼,簡化了數(shù)據(jù)和視圖之間的交互。不禁我們就想知道,他其實是怎么實現(xiàn)的,我也很好奇,然后點開了源碼,看的云里霧里,但是大概是,DataBinding對自己定義的結構的xml進行了解析,配合activity創(chuàng)建ViewDataBinding,獲取各種view以及監(jiān)聽數(shù)據(jù)的過程。
ActivityMainBinding對象的生成
由DataBinding對象獲取的方法DataBindingUtil.setContentView最終追溯到這個方法。
public class DataBindingUtil {
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
}
這個方法最終返回的其實就是相對應ActivityMainBindingImpl的對象。
ViewDataBinding實例化:
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");
}
if (USE_CHOREOGRAPHER) {
mChoreographer = Choreographer.getInstance();
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
mRebindRunnable.run();
}
};
} else {
mFrameCallback = null;
mUIThreadHandler = new Handler(Looper.myLooper());
}
}
并且ViewDataBinding會通過view創(chuàng)建對應的WeakListener,弱引用類型接口作為觀察者,利于被回收,同時很好的處理內存泄露的情況。之后則是update時的邏輯。
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.activity == variableId) {
setActivity((com.llyyyw.databinding.MainActivity) variable);
}
else if (BR.text == variableId) {
setText((com.llyyyw.databinding.TextBean) variable);
}
else {
variableSet = false;
}
return variableSet;
}
public void setText(@Nullable com.llyyyw.databinding.TextBean Text) {
updateRegistration(0, Text);
this.mText = Text;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.text);
super.requestRebind();
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, ObservableList observable) {
return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
}
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
}
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakListListener(viewDataBinding, localFieldId).getListener();
}
};
private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakMapListener(viewDataBinding, localFieldId).getListener();
}
};
最終流程是:
源代碼全部不好貼出來,口述一下。相對應ActivityMainBindingImpl的實例化,數(shù)據(jù)綁定在視圖UI線程中創(chuàng)建,以及調用父類ViewDataBinding的executePendingBindings直到executeBindings這個抽象方法,指向的是ActivityMainBindingImpl的executeBindings方法,注冊WeakListener監(jiān)聽。
當我們設置數(shù)據(jù),BindingImpl的setVariable調用控件set方法,調用updateRegistration監(jiān)聽。當BaseObservable變化,觸發(fā)notifyPropertyChanged,觸發(fā)回調,也就是對應的WeakListener。就會更新視圖。
這里我們講完了數(shù)據(jù)變化從而view變化的原理,那view變化引起數(shù)據(jù)變化呢。
首先要知道ViewDataBinding是如何具體保存view的。前面說明了ActivityMainBindingImpl才是整個DataBinding的核心實現(xiàn)類,也調用了它的executeBindings方法,這個方法里其實就是對控件的一系列操作,控件的設值,控件的各種監(jiān)聽事件。
if ((dirtyFlags & 0x15L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etTitle, textTitle);
com.llyyyw.databinding.dbAdapter.TextBindingAdapter.checkTitle(this.tvTitle2, textTitle);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle2, textTitle);
}
if ((dirtyFlags & 0x10L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etTitle, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etTitleandroidTextAttrChanged);
this.tvTitle.setOnClickListener(mCallback1);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvTitle, contextApplicationContextPackageName);
}
if ((dirtyFlags & 0x19L) != 0) {
// api target 1
this.mboundView01.setTextText(textText);
}
if ((dirtyFlags & 0x12L) != 0) {
// api target 1
this.tvTitle2.setOnClickListener(activityUpdateTitleAndroidViewViewOnClickListener);
}
private androidx.databinding.InverseBindingListener etTitleandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of text.title
// is text.setTitle((java.lang.String) callbackArg_0)
java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(etTitle);
// localize variables for thread safety
// text.title
java.lang.String textTitle = null;
// text
com.llyyyw.databinding.TextBean text = mText;
// text != null
boolean textJavaLangObjectNull = false;
textJavaLangObjectNull = (text) != (null);
if (textJavaLangObjectNull) {
text.setTitle(((java.lang.String) (callbackArg_0)));
}
}
};
只看其中的EditText控件可以發(fā)現(xiàn),DataBinding是這樣通過etTitleandroidTextAttrChanged這個監(jiān)聽器去實時獲取控件的值進而對數(shù)據(jù)進行實時更新的。
5.總結
DataBinding的使用并不難,當然DataBinding還有許多其他的使用。深入去了解DataBinding可以發(fā)現(xiàn)里面的大奧秘,我這里云里霧里也只能理解這么冰山一角。學習使我快樂!