本篇文章基于上篇的基礎文章,現在網上充斥著大量的復制粘貼文,各種關于databinding的誤解,我寫這系列的目的很簡單,教你們怎么正確使用databinding,不要被網上的復制粘貼文坑。
databinding可以很輕松的幫我們實現視圖和模型之間的綁定,但是他們是如何綁定的呢。
先來看一下文件結構:
看一個簡單的例子:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="presenter"
type="com.saka.fragmentdemo.MainActivity.Presenter"/>
<variable
name="text"
type="com.saka.fragmentdemo.models.Text"/>
<variable
name="ssd"
type="com.saka.fragmentdemo.MainActivity.Presenter"/>
<import type="com.saka.fragmentdemo.MainActivity.Presenter"/>
<import type="com.saka.fragmentdemo.MainActivity.Presenter"
alias="jjjj"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.saka.fragmentdemo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/add_one"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@{text.btn1}"
android:textSize="10sp"
android:onLongClick="@{()->presenter.onLongClick()}"
android:onClick="@{()->presenter.addFragmentOne(10)}"/>
<Button
android:id="@+id/add_two"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@{text.btn2}"
android:textSize="10sp"
android:onClick="@{()->presenter.addFragmentTwo()}"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="20dp"
android:background="@color/colorAccent"
/>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>
這個xml文件會由系統自動生成一個與之對應的ActivityMainBinding文件(假如不使用類名或者),它繼承的是android.databinding.ViewDataBinding類,實現了android.databinding.generated.callback.OnClickListener.Listener接口,這個接口就是在xml布局文件中的onclick方法,并且實現了android.databinding.generated.callback.OnLongClickListener.Listener,這個接口是xml布局文件中的onLongClick方法。
這個地方有一點要澄清,現在網上大部分教程都是寫的databinding不支持onLongClick方法,這種說法是錯誤的,databinding現在已經支持大部分控件的接口實現,只是有一些還不支持IDE提示,所以在寫的時候要手動去填寫,另外在上一篇文章已經講過,onLongClick需要返回一個boolean值,所以你自定義的方法類中必須也要為onlongclick方法設定為一個boolean值,否則編譯器會報錯。
下面是我定義的一個內部方法類(懶癌)
public class Presenter{
public void addFragmentOne(){
Log.d(TAG,"Click AddOne");
ft=fm.beginTransaction();
ft.add(R.id.fragment_container,new OneFragment()).commit();
}
public void addFragmentTwo(){
Log.d(TAG,"Click AddTwo");
ft=fm.beginTransaction();
ft.replace(R.id.fragment_container,new TwoFragment()).commit();
}
public boolean onLongClick(){
Log.d(TAG,"Click Long");
return true;
}
}
02-21 01:37:02.519 3523-3523/com.saka.fragmentdemo D/MainActivity: Click Long
可以看到,正常安裝并且運行實現了onLongClick方法。
BR文件概覽
BR文件位于根目錄下\app\build\generated\source\apt\debug\com\saka\fragmentdemo\BR.java位置。這是一個非常簡單的常量類,每個ID對應于xml布局文件中的variable。在本例中的主要代碼如下:
public class BR {
public static final int _all = 0;
public static final int presenter = 1;
public static final int ssd = 2;
public static final int text = 3;
}
所有的name都會自動生成一個int值(吐槽一下,為什么不是大寫),有多少varibale就會對應生成多少ID。
<data>
<variable
name="presenter"
type="com.saka.fragmentdemo.MainActivity.Presenter"/>
<variable
name="text"
type="com.saka.fragmentdemo.models.Text"/>
<variable
name="ssd"
type="com.saka.fragmentdemo.MainActivity.Presenter"/>
<import type="com.saka.fragmentdemo.MainActivity.Presenter"/>
<import type="com.saka.fragmentdemo.MainActivity.Presenter"
alias="jjjj"/>
</data>
Note:這里假如使用import導入的類或者為導入類設置別名均不會再BR文件中生成ID。
ActivityMainBinding概覽
要了解Binding類,首先看它繼承的父類
java.lang.Object
? android.databinding.BaseObservable
? android.databinding.ViewDataBinding
它繼承自BaseObservable,而BaseObsetvable直接繼承自Object。
ViewDatabing這個父類是為生成databindingclass(也就是本例中的ActivityMainBinding)而出現的類。它必須實現 bind(View) 或者 inflate(LayoutInflater, int, ViewGroup, boolean)這兩個靜態方法中的一個。
看一下其中的幾個方法:
void addOnRebindCallback (OnRebindCallback listener)
重新綁定字段調用的監聽器,它允許暫停自動更新,但不會停止對executePendingBindings()的顯示調用。
void executePendingBindings ()
更新已被掛起的任何具有綁定表達式的控件視圖,必須運行在主線程中,通常recyclerview或者listview經常使用。
View getRoot ()
返回與binding關聯的布局文件中的最外面的view,如果次幫帝國是用于merge布局文件,將返回第一個根標簽。在fragment的綁定或者自定義view的綁定中使用的較多。
boolean hasPendingBindings ()
返回一個是否在主線程中等待被刷新的數據。
void invalidateAll ()
使所有綁定表達式無效,并請求新的重新綁定以刷新UI。
void removeOnRebindCallback (OnRebindCallback listener)
移除在 addOnRebindCallback(OnRebindCallback)中所有的監聽器。
boolean setVariable (int variableId, Object value)
綁定值。variableId是BR文件中的id,value是你設定的類的實例。
返回的值假如為true表示你已經成功綁定。
void unbind ()
為表達式移除綁定監聽器。
void finalize ()
這是一個protected方法,很少使用,但是講一下原理:
看過thinkinjava的同學應該知道這一章,這個方法是在系統gc的時候調用的,但是我們很少在其中寫方法,因為這個方法不確定會不會被調用。
當垃圾回收器確定當前的對象沒有引用并且需要回收時,在ViewDataBinding的子類中就會調用該方法執行清理操作。
上面的方法中主要看一下其中幾個在ActivityMainBinding中的繼承使用:
setVariable
系統生成的setVariable方法已經自動啟動了責任鏈模式,與之對應的系統會自動生成相應的getter和setter方法。本例中使用了Presenter和Ssd兩個名字(但是是同一個類),方法如下:
public boolean setVariable(int variableId, Object variable) {
switch(variableId) {
case BR.text :
setText((com.saka.fragmentdemo.models.Text) variable);
return true;
case BR.presenter :
setPresenter((com.saka.fragmentdemo.MainActivity.Presenter) variable);
return true;
case BR.ssd :
return true;
}
return false;
}
此處需要注意的是假如你在xml文件沒有使用某個variable,但是你在java文件中定義了它,
binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.setPresenter(new Presenter());
binding.setSsd(new Presenter());
系統會生成ssd的getter和setter方法,但是在setVariable方法中不會調用,因為你沒有在xml中是使用這個variable,自動被系統忽略。也是精簡的一種辦法。
當然你可以直接調用setPresenter方法來綁定數據,和setVarible同樣的作用,而且比它還少了很多步驟,推薦書hi用setPresenter方法。
_internalCallback
這個方法是用來綁定監聽回調的。首先在xml文件中定義的事件方法都會在binding文件中生成一個對應的調用方法(例如onclik會生成_internalCallbackOnClick方法,onLongClick會生成_internalCallbackOnLongClick方法),并且會為每個回調方法指定一個sourceId(指定方法稍后講),通過判斷sourceId,來確定調用哪個方法。
public final void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0) {
switch(sourceId) {
case 3: {
com.saka.fragmentdemo.MainActivity.Presenter presenter = mPresenter;
boolean presenterObjectnull = false;
presenterObjectnull = (presenter) != (null);
if (presenterObjectnull) {
presenter.addFragmentTwo();
}
break;
}
case 2:
break;
}
}
這個方法是onClick生成的對應binging文件中的方法。注意你自定義的方法中假如傳參數的話會自動添加到你的方法中去。presenter.addFragmentTwo(10);
executeBindings
這個是用來綁定和監聽數據的方法
// batch finished
if ((dirtyFlags & 0x9L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.addOne, btn1Text);
android.databinding.adapters.TextViewBindingAdapter.setText(this.addTwo, btn2Text);
}
這例的this.addOne就是button1,btn1Text獲取的是variable中text的數據,可以看到源碼中已經自動判斷是否為空了,省了我們自己的步驟。
if ((dirtyFlags & 0x9L) != 0) {
if (text != null) {
// read text.btn2
btn2Text = text.getBtn2();
// read text.btn1
btn1Text = text.getBtn1();
}
}
所有的監聽回到方法類
此文件位于generated.callback文件夾下,這個文件夾包含了所有的回調方法類,類似于(OnClickListener,OnLongClickListener)。
這個文件實現了view.OnClickListener接口,所以可以被bingding類實現,它的功能就是添加sourceId。在binding文件中綁定:
public OnClickListener(Listener listener, int sourceId) {
mListener = listener;
mSourceId = sourceId;
}
mCallback1 = new android.databinding.generated.callback.OnLongClickListener(this, 1);