棉花糖給 Android 帶來的 Data Bindings(數據綁定庫)

jopen 8年前發布 | 49K 次閱讀 安卓開發 Android開發 移動開發

About the Speaker: Yigit Boyar 和 George Mount

George 是一位在谷歌 Android UI 工具團隊的軟件工程師,主要致力于動畫和過渡,包括 Activity 和 Fragment 的過渡效果。 在加入谷歌之前,George 曾經在 NVIDIA 公司、Intuit 和網景分別工作過,主要開發桌面端 UI 和服務端應用程序。George 獲得斯坦福大學碩士學位和俄勒岡州立大學的學士學位。Yigit 也是作為 Android UI 工具團隊的一員,致力于創建 UI 組件和提高布局性能。在加入谷歌之前,他在 Path.com 擔任 Android 項目主管,在那里他主要專注于創建應用程序的架構、實時拍照濾鏡和優化的 UI 界面流暢性。他在土耳其中東科技大學獲得計算機工程學士學位。

</div>

@yigitboyar

</div>

介紹(0:00)

我們是 George Mount 和 Yigit Boyar,在谷歌 Android UI 工具團隊。我們有很多關于 Data Binding 的信息要分享給你們。我們會討論到一些重要到方面,比如 Data Binding 是怎么工作的、如何集成到您到 App 當中、它的原理和如何與一些其他組件共同使用,同時,我們也會給出一些最佳實踐示例。

為什么要使用 Data Binding?(0:44)

你可能會想我們為什么決定去實現這么一個庫,關于這點,可以先看以下一些傳統代碼寫法的例子:

<LinearLayout …>
    <TextView android:id="@+id/name"/>
    <TextView android:id="@+id/lastName"/>
</LinearLayout>

這是一個你經常會看到的 Android UI。 假設你有一堆帶 ID 的視頻內容。你的設計師來了,說:“好吧,讓我們嘗試添加新的信息到這個布局,”這樣,當你添加任何視頻,你需要跟隨另外一個 ID。你需要回到你的 Java 代碼,修改 UI。

private TextView mName
protected void onCreate(Bundle savedInstanceState) {
  setContentView(R.layout.activity_main);
  mName = (TextView) findViewById(R.id.name);
}

public void updateUI(User user) { if (user == null) { mName.setText(null); } else { mName.setText(user.getName()); } }</pre></div>

你寫了一個新的 TextView,你通過 findViewById 在代碼中找到它,同時你把它賦值給了你的變量,使得當你需要更新用戶信息的時候,再通過它去給這個 TextView 設置上用戶信息。

private TextView mName
protected void onCreate(Bundle savedInstanceState) {
  setContentView(R.layout.activity_main);
  mName = (TextView) findViewById(R.id.name);
  mLastName = (TextView) findViewById(R.id.lastName);
}
public void updateUI(User user) {
  if (user == null) {
    mName.setText(null);
    mLastName.setText(null);
  } else {
    mName.setText(user.getName());
    mLastName.setText(user.getLastName());
  }
}

總的來說,僅僅為了增加一個 View 到你的 UI 當中,你得做好幾個步驟的事情。這似乎是比較愚蠢的樣板代碼,雖然有時它不需要任何腦力。

幸運的是,已經有一些漂亮的庫可以簡化我們的這些操作。比如你使用 ButterKnife 這個庫,能夠擺脫討厭的 findViewById 而獲得組件,它讓代碼更加簡潔易讀。通過它可以節省很多額外的代碼。

private TextView mName
protected void onCreate(Bundle savedInstanceState) {
  setContentView(R.layout.activity_main);
  ButterKnife.bind(this);
}
public void updateUI(User user) {
  if (user == null) {
    mName.setText(null);
    mLastName.setText(null);
  } else {
    mName.setText(user.getName());
    mLastName.setText(user.getLastName());
  }
}

這是很好的一步,但是我們想走得更遠。另外,我們可以說:”好吧,為什么我需要這些項目呢?有什么可以直接生成它。我有一個布局文件,我有它們的 IDs.” 所以,你可以使用 Holdr,它可以替你可以處理布局文件,然后為他們創建 View 組件。你通過 Holder,轉換 View 的 ID 到組件變量。

private Holdr_ActivityMain holder;
protected void onCreate(Bundle savedInstanceState) {
  setContentView(R.layout.activity_main);
  holder = new Holdr_ActivityMain(findViewById(content));
}
public void updateUI(User user) {
  if (user == null) {
    holder.name.setText(null);
    holder.lastName.setText(null);
  } else {
    holder.name.setText(user.getName());
    holder.lastName.setText(user.getLastName());
  }
}

這種方式也不錯,但其中仍然有很多是不必要的代碼。而且這里還有一個可能我們沒有想到的麻煩、一個我們無論如何無法減少代碼量的地方,它也是很簡單的代碼:我有一個 User 對象,我只是想把數據內容從這個 User 對象轉移到 View 當中,但我們往往改了一個地方,忘了另一個地方,最終導致了產品運行崩潰。這也是我們關注的一部分,我們想擺脫所有這些比較蠢的代碼。

當你使用 Data Binding,它很像 Holder 模式,而且你只要做一點點事情,其余的內容 Data Binding 會幫你完成。

private ActivityMainBinding mBinding;
protected void onCreate(Bundle savedInstanceState) {
  mBinding = DataBindingUtil.setContentView(this,
                           R.layout.activity_main);
}

public void updateUI(User user) { mBinding.setUser(user); }</pre></div>

“幕后”(3:53)

那么,Data Binding 在幕后是怎么工作的呢? 在此之前可以先看看我們的布局文件:

<LinearLayout …>
  <TextView android:id="@id/name"    />
  <TextView android:id="@id/lastName"    />
</LinearLayout>

我們有這些 Views 的 ID,但如果我們能夠直接通過 Java 代碼找到它們,為什么還需要這些 ID 呢?嗯,我們現在已經不需要它們了,所以我們可以擺脫它們,在這些地方,我可以放一些更加明顯的內容。

<LinearLayout …>
  <TextView android:text="@{user.name}"/>
  <TextView android:text="@{user.lastName}"/>
</LinearLayout>

現在,當我看這些布局文件,我可以知道這些 TextView 要顯示什么,它變得非常明顯,所以我沒必要再回到我當 Java 代碼去閱讀它們到底是干什么的。我們設計 Data Binding 庫其中一個原因就是不想去用一些看起來不明顯不直接的表達方式。使用 Data Binding,你只要簡單地告訴它:“我們用這種類型的用戶標記這個布局文件,現在我們將要找到它。”而如果你的產品設計經理要求你添加另一個新的 View 進去這個布局,你只要在這個布局文件中加上它,無需改變其他 Java 代碼:

<layout>
    <data>
        <variable name="user"
                  type="com.android.example.User"/>
    </data>
    <LinearLayout …>
        <TextView android:text="@{user.name}"/>
        <TextView android:text="@{user.lastName}"/>
        <TextView android:text='@{"" + user.age}'/>
    </LinearLayout>
</layout>

同時,它也非常容易尋找 bug. 你可以看著類似上面的代碼,然后說:“哦,這是空的字符串加上 user.age!” 這樣寫是安全正確的,而如果你只是把整形設置給 text 了,它會誤以為這是資源索引 resId,找不到對應的資源從而導致崩潰。

但是它怎么工作的呢?(5:57)

Data Binding 做的第一件事就是進入并處理您的布局文件,其中的“進入”指的是,在你的程序代碼正在被編譯的過程中,它會找出布局文件中所有關于它的內容,獲取到它所需要的信息,然后刪掉它們,刪掉它們的原因是如果繼續存著視圖系統并不認得它們。

第二步驟就是通過語法來解析這些表達式,例如:

<TextView android:visibility="@user.isAdmin ? View.VISIBLE : View.GONE}"/>

這個 user 是一個索引,之后的 View 也是個索引,另一個 View 也是個索引。它們都是索引或稱標識,像是真正的對象,在這邊我們真的不知道它們是什么。同樣的對于 VISIBLE 和 GONE 也是如此。這里面有對象數據訪問,是一個三目運算表達式,這就是目前為止我們所理解到的。而對于我們的庫,它的工作就是從這些文件中把東西解析出來,了解里面有什么。

第三步就是在你代碼編譯過程中解決相關依賴問題。在這一步中,例如,我們看一下 user.isAdmin ,想:“這是在運行時獲取到 User 類對象中的一個布爾值。”

最后一步就是 Data Binding 會自動生成一些你不需要再寫的類文件,總之到這里你只要享受它帶來的好處:moneybag:就是了。

一個針對上述的示例(7:40)

這是一個真實場景中的布局文件:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="user" type="com.android.example.User"/>
    </data>
   <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        <TextView android:text="@{user.name}"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"/>
        <TextView android:text="@{user.lastname}"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"/>
    </RelativeLayout>
</layout>

當編譯時 Data Binding 進入,它會讀懂并丟掉所有視圖系統不認識的內容,連接它們,同時放上我們的綁定 tags(標志),變成這樣:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        <TextView android:tag="binding_1"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"/>
        <TextView android:tag="binding_2"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"/>
    </RelativeLayout>

這實際上就是我們如何做到數據綁定向后兼容,如果沒有這樣,當你把它放在一個舊的系統設備上,這個可憐的家伙可能根本不知道發生了什么事。

表達式(8:01)

<TextView android:text="@{user.age < 18 ? @string/redacted : user.name}"/>

上面這是另一個示例。當我們解析它的時候,它會在編譯的時候被換成對應的一些表達式代碼,以至于當程序開始運行的時候,程序能知道該怎么做。我們檢查到這個表達式左邊是一個布爾值,右邊是一個字符串,資源索引的也是一個字符串。就是說,這里有一個布爾值,一個字符串,和另一個字符串,這是一個三目運算,同時這個整體也是一個字符串。所以,我們知道布局文件這里有一個 text 屬性,和它的字符串,那么 Data Binding 要怎么把它們連接在一起呢?

答案就是通過代碼上 setText(CharSequence) 這個方法。現在,Data Binding 知道怎么把布局表達式轉換為 Java 代碼了。如果你要看一下具體示例,可以看看類似如下的 TextView 和 ImageView :

<TextView android:text="@{myVariable}"/>
textView.setText(myVariable);
<ImageView android:src="@{user.image}"/>
imageView.setSrc(user.image);

上面的這段代碼里有一個問題,ImageView 有一個布局屬性為 src,那么根據上面的示例,它會被轉換為 setSrc ? 當然不,因為 ImageView 類并沒有這么一個方法,而是有一個不叫這個名字的方法用來設置它的圖片源,可是,Data Binding 怎么能夠知道?

src 它被稱作源屬性,一旦你使用了這樣的屬性,Data Binding 也得能夠支持它。

<TextView …/>
textView.setText(myVariable);
<ImageView android:src="@{user.image}"/> 
imageView.setImageResource(user.image);
          @BindingMethod(
              type = android.widget.ImageView.class,
              attribute = "android:src",
              method = "setImageResource")

我們創造了一些注解,借助它們我們可以簡單地說:“在 ImageView 這個類中,屬性 src 對應這個方法” 我們只要這么寫一次就好,我們實際上也只是形成一次框架。我們提供了它,但你可能有很多自定義 View 也面臨需要這么添加對應關系。使用了這些注解方法,數據綁定就能夠知道如何解決。同樣,這一切都是發生在編譯期間。

Data Binding 特別吸引人的地方(9:54)

Data Binding 使你的工作更加簡單。讓我們來看一下我們所支持的語言:支持 絕大部分 的 Java 寫法,它允許變量數據訪問、方法調用、參數傳遞、比較、通過索引訪問數組,甚至還支持三目運算表達式。 這基本上能夠滿足你的需求,但也有一些事情無法辦到,比如 new ,我們真的不想你在布局中的表達式里寫 new .

我們的基本目標就是讓你的布局文件中的表達式盡可能短和可讀,我們不像讓你不得不寫超級長的表達式,而只為了訪問你的聯系人的名字。我們希望你能夠使用 contact.name ,我們會想:“這是一個可訪問的屬性變量,或者是一個 getter?”又或者你可能有一個叫做 “name” 的函數方法。

我們也會自動檢查是否為 null,這實際上非常酷。如果你想訪問 contact 的 name 變量,而 contact 是 null 的,以前你不得不痛苦地寫 contact null ? null : contact.friend null ? : ,而現在,如果 contact 是 null 的,那么整個表達式就是 null 的,你無需判斷也不會出錯。

我們還提供了一個 null 的合并運算符號 ?? ,你可能已經從其他語言中看到過了,這是一個三目運算符的簡便寫法:

contact.lastName ?? contact.name
contact.lastName != null ? contact.lastName : contact.name

它表達的是,如果左邊不是 null 的,那么使用左邊的值,否者使用右邊的值。

我們還可以使用中括號操作符來訪問 list 或者 map,如果你有使用 contacts[0] ,它有可能是一個 list 或者是一個數組,都可以。使用中括號來訪問項目,更加容易簡潔。

資源內容(12:20)

我們希望你能在你的表達式中使用資源引用內容,因此你可以在你的表達式中使用資源和字符串格式化方法。

在表達式中:

android:padding="@{isBig ? @dimen/bigPadding : @dimen/smallPadding}"

內聯字符串格式:

android:text="@{@string/nameFormat(firstName, lastName)}"

內聯復數:

android:text="@{@plurals/banana(bananaCount)}"

自動屬性(13:00)

以下我們有一個 DrawerLayout…

<android.support.v4.widget.DrawerLayout
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:scrimColor="@{@color/scrim}"/>

drawerLayout.setScrimColor(
  resources.getColor(R.color.scrim))

我們使用了 app:scrimColor 屬性,但實際上 DrawerLayout 并沒有提供這么一個 xml 布局屬性,但我們會將它和 DrawerLayout 類的 setScrimColor 方法聯系起來。當我們看到有一個布局屬性名為 scrimColor 同時我們找到了一個 setScrimColor ,我們就會判斷它的參數類型是否匹配布局中的值。首先我們找到這個 color,它是一個 int 類型,而如果 setScrimColor 接受一個 int 類型的參數,我們就視為它們是匹配的,這多么方便啊!

事件處理(13:41)

我不知道你們寫過多少的 clicked 來響應一個 button 或者 view 的點擊,我們使用 Data Binding 也是支持點擊響應的。你可以使用 clicked ,同時,另外的一些事件也都支持。當然, 支持到 Android 2.3 版本。你甚至可以通過表達式指定程序處理者(我并不是建議你這么做,但是它可以做到!)。你還可以做一些偏門的監聽器,比如 onTextChanged ,TextWatcher 有三個方法,但大部分人都只關心它的 onTextChanged ,借助 Data Binding,你可以直接訪問任意一個或全部你想訪問的:

<Button android:onClick="clicked" …/>

<Button android:onClick="@{handlers.clicked}" …/>

<Button android:onClick="@{isAdult ? handlers.adultClick : handlers.childClick}" …/>

<Button android:onTextChanged="@{handlers.textChanged}" …/></pre></div>

詳細的可觀測性(14:56)

很多時候,我們不知道數據和更新 View 的關系。想像一下我們有一個商店,我們有一個商品最近更新了價格。這就需要我們自動跟隨著更新我們的 UI,怎么辦?通過 Data Binding,這件事可以變得非常簡易。

首先我們需要創建一個項目,包含一些可以被觀測的對象。在這里,我已經繼承了個 BaseObservable,然后我們在這個新的類當中加入我們的屬性。

public class Item extends BaseObservable {
    private String price;

@Bindable
public String getPrice() {
    return this.name;
}

public void setPrice(String price) {
    this.price = price;
    notifyPropertyChanged(BR.price);
}

}</pre></div>

我們使用 notifyPropertyChanged 來進行數據改變完成通知,但我們怎么通知一個數據即將改變?我們不得不寫一個 @Bindable 注解在 getPrice 。這將會自動產生一個 BR.price ,這個 BR 很像我們經常使用的 R 類文件,我們通過這些注解會自動生成它。但是,你可能不想讓我們入侵你的整個代碼體系,所以我們允許你去實現這些可被觀測的類。自己實現的示例如下:

public class Item implements Observable {
    private PropertyChangeRegistry callbacks = new …
    …
    @Override
    public void addOnPropertyChangedCallback( 
            OnPropertyChangedCallback callback) {
        callbacks.add(callback);
    }
    @Override
    public void removeOnPropertyChangedCallback( 
            OnPropertyChangedCallback callback) {
        callbacks.remove(callback);
    }
}

我們有一個很方便的類 PropertyChangedRegistry ,讓你很方便地進行回調和通知。你們中的一些人可能會覺得這是一件很討厭的事情,他們更多的是想要一個可觀測的屬性變量。從本質上講,它們每一個都是一個可觀測都對象。你可以很方便地說,accessImage, 它實際上就會去訪問對應圖片地內容,如果你要訪問 price,它就會去訪問價格的字符串內容。

關于這些對象的特別之處在于,以前使用 Java 代碼,你必須得調用 set 或者 get 方法,但在你的綁定表達式中,你只要寫 item.price ,我們能夠自動知道你需要調用它的 getter 方法,所以當價格發生變化時,它才設定它的值。

public class Item {
    public final ObservableField<Drawable> image =
            new ObservableField<>();
    public final ObservableField<String> price =
            new ObservableField<>();
    public final ObservableInt inventory =
            new ObservableInt();
}

item.price.set("$33.41");</pre></div>

在其他情況下,你可能有更多的“細碎、不確定”的數據,這通常發生在你開發周期的一開始,特別是在原型階段,可能你會有很多鍵值數據對,并且你很不想定義這些類,所以你可能會想使用 map 來存儲這些數據對。這種情況下,你可以使用一個可觀測的 ObservableMap 來放你的項目,然后就可以訪問它們了。不幸的是,你只能使用中括號類似訪問數組那樣訪問它們:

ObservableMap<String, Object> item =
        new ObservableArrayMap<>();

item.put("price", "$33.41");</pre></div>

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" 
    android:text='@{item["price"]}'/>

在任意線程中進行通知(18:29)

這里的一個方便的是,你不必在UI線程進行通知:你可以在任何你想要的線程上進行更新。然而,我們仍然是在將在UI線程進行響應,所以你必須小心。此外,對于列表請不要這樣做的!對于列表,你仍然應該在UI線程上通知,因為我們將在UI線程讀取到,并且我們將需要在UI線程讀取到它的長度,我們不做任何類型的同步。你可能已經知道這樣對于 RecyclerView 和 ListView 會造成很多問題。這是因為列表本身的問題,而不是因為這些類的問題。

性能(19:21)

也許很多人最關心的是它的性能。數據綁定這類庫是臭名昭著的緩慢,所以在 Android 上,我們考慮了很多,我們相信我們能做出比較好的性能。

對于性能的最重要的方面是,我們基本上是零反射。一切都發生在編譯期。有時候,這么做是不方便的,因為它發生在編譯時,但在長期運行,我們關心不到。當應用程序運行時,我們不希望解決任何問題。

其次,你還可以得到一些額外的好處。讓我們來討論在一個布局中使用數據綁定,你把一個對象命名為price,然后價格變化了。新的價格來了,通知來了。數據綁定會更新 TextView,TextView 自動進行重新測量。如果你是用手工寫這些功能的代碼,那你就不太可能寫下這樣的代碼,而是會重新關閉再加載新內容。所以這是一個額外的好處。

Data Binding 的另一個性能好處是在你有兩個表達式的情況下,例如:

<TextView android:text="@{user.address.street}"/>
<TextView android:text="@{user.address.city}"/>

你有一個 user.address 和另一個 user.address ,Data Binding 將會為此生成如下代碼:

Address address = user.getAddress();
String street = address.getStreet();
String city = address.getCity();

它將把 address 移到一個本地變量,然后進行操作。現在想象一下,有一些重復計算是比較耗費性能的。數據綁定只會做一次。這是另一個好處,不需要你手動去做這些優化。

關于性能的另一個積極作用是針對 findById 。當你在Android系統上使用代碼 findById ,它實際上是對它對所有子內容說:“孩子,你能通過ID找到這個 view 嗎?”那個孩子(子內容)問自己的孩子,然后找不到就去問下一個孩子,直到你找到了這個 view。然后,你的代碼 findViewById 對于其他 View 會再重新做一次,重復的沒必要的同樣事情再次發生。

然而,當你初始化數據綁定時,我們實際上知道在編譯時我們對應的 View,所以我們有一個方法來找到我們想要的所有 View。我們只需遍歷布局層次一次,就可以收集到所有的 View。這個計算只對于一個布局只發生一次。當第二次我們需要另一個其中的子 View 的時候,不需要再次遍歷所有子 View,因為我們已經找到了所有的 View。

性能有時關鍵就在一些小細節上。你在你的代碼中包含一個庫,一些行為會改變,但有時也會有一些成本。但有了這些性能上的優化,我認為我們做了它值得了,甚至有時比你寫的代碼還好,這是非常重要的。

RecyclerView 和 Data Binding(22:14)

使用 ViewHolders 對于 ListView 是很常見的,在 RecyclerView 中也是強制實施這個模式。如果你看看 Data Binding 生成的代碼,你會發現它實際上會產生 ViewHolder。并帶有變量屬性,它綁定著那些 View。你也可以很容易地在 RecyclerView 里面中使用。我們創建了一個 ViewHolder,有一些基本方法,和一個靜態方法,它傳遞參數給 UserItemBinding(根據用戶的布局文件自動生成的)。你需要調用 UserItemBinding 的 inflate。現在你有一個非常簡單的 ViewHolder 類,綁定方法類似這樣:

public class UserViewHolder extends RecyclerView.ViewHolder {

static UserViewHolder create(LayoutInflater inflater, ViewGroup parent) { UserItemBinding binding = UserItemBinding .inflate(inflater, parent, false); return new UserViewHolder(binding); }

private UserItemBinding mBinding;

private UserViewHolder(UserItemBinding binding) { super(binding.getRoot()); mBinding = binding; }

public void bindTo(User user) { mBinding.setUser(user); mBinding.executePendingBindings(); }

}</pre></div>

其中有一個小細節要小心,就是調用這個 executePendingBindings ,當你的數據還無效的時候,數據綁定是等到下一個動畫幀之前才設置布局。這就讓我們不可以一次性批量綁定完所有的數據內容,因為 RecyclerView 的機制并不是這樣。當要綁定一個數據的時候,RecyclerView 會調用 BindView,讓你去準備測量這個布局。這就是為什么我們叫這個方法為 executePendingBindings ,它使數據綁定刷新所有掛起的更改。否則,它將視為另一個布局失效了。

對于 onCreateViewHolder ,它只是調用第一個方法,和 onBind 傳到 ViewHolder 對象。就是這樣,我們沒有寫 findViewById ,沒有設置。一切都已經在你的布局文件中封裝好了。

public UserViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
  return UserViewHolder.create(mLayoutInflater, viewGroup);
}

public void onBindViewHolder(UserViewHolder userViewHolder, int position) { userViewHolder.bindTo(mUserList.get(position)); }</pre></div>

在前面的代碼中,我們顯示了一個非常簡單直接的實現。比如說,用戶對象的名稱更改了。綁定系統將關聯它,并重新布局在下一個動畫幀。下一個動畫幀開始,計算出發生了什么變化,和更新 TextView。然后,TextView 說,“好吧,我的文字已經變了,我已經重新布局,現在的情況是我不知道我的新尺寸。讓我們去告訴 RecyclerView 它的這個孩子有點困難吧,它需要重新布局它本身。”當這一切發生的時候,你不會得到任何的動畫,因為你在一切都 發生了之后 才告訴 RecyclerView。RecyclerView 將嘗試修復自身。 結果:就沒有動畫了 ,但這不是我們想要的。

我們希望發生的是,當用戶的對象是無效的,我們告訴適配器項目已經改變了。反過來,它會告訴 RecyclerView,“嘿,你的一個孩子要改變,自己做好準備。”RecyclerView 知道了布局和那些子 View 已經改變了,它會指導他們重新綁定。當他們重新綁定,TextView 會說,“好吧,我的文字設置好了,我需要布局。”RecyclerView 會說,“好了,別擔心,我準備好了,讓我量量你。” 結果:很多動畫 。你會得到所有的動畫,因為一切都發生 RecyclerView 控制下。

綁定回調和有效載荷(25:50)

這實際上是我們需要發布為一個庫的內容,但在此期間,我想讓你知道如何做到這一點。在數據綁定時,我們有一個API,你可以添加一個綁定回調。當數據綁定將要計算,通過這個回調,可以得到通知。例如,也許你可能希望凍結更改用戶界面。你可以“勾住” onPreBind ,此時會返回一個布爾值,你可以說,“不,不用再綁定它了。”如果一個監聽器插入進來,就會說,“嘿,我取消了綁定。你得先調用我,因為我準備告訴你不要做任何事情。”

現在我們要做的是如果 RecyclerView 不計算你的布局,返回 false。View 不需要更新。這個新的 RecyclerView API,將會在今年夏季發布。當 onCanceled 來臨的時候,我們只是告訴適配器,“嘿,這項改變了,去把它的位置找出來。”我們已經知道它在 holder 中的位置了后,讓 RecyclerView 去更新它:

public UserViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
  final UserViewHolder holder = UserViewHolder.create(mLayoutInflater,
viewGroup);

holder.getBinding().addOnRebindCallback(new OnRebindCallback() {

public boolean onPreBind(ViewDataBinding binding) {
  return mRecyclerView != null && mRecyclerView.isComputingLayout();
}

public void onCanceled(ViewDataBinding binding) {
  if (mRecyclerView == null || mRecyclerView.isComputingLayout()) {
    return;
  }
  int position = holder.getAdapterPosition();
  if (position != RecyclerView.NO_POSITION) {
    notifyItemChanged(position, DATA_INVALIDATION);
  }
}

}); return holder; }</pre></div>

以前,我們只有一個 onBind 方法,所以我們開始寫這個新的 RecyclerView API,通過這些 API 你可以得到載荷的列表。它是 ViewHolder 中內容改變的列表。關于這個 API 很酷的是,只有當你的 RecyclerView 要重新綁定相同的 View 時候,你才會收到這些載荷。你知道的,那個 View 已經顯示了相同的項目,但也有一些不一樣的變化你想執行。我們返回的數據無效標識會到這里。如果接受到它,我們就可以調用 executePendingBindings 。你還記得我們沒有讓它更新自己嗎?現在,是時候更新本身,因為 RecyclerView 叫它這么做了。

Data Binding 是怎么做到的呢,其實是通過簡單遍歷的這些載荷,并檢查該數據,驗證是它收到的唯一的有效載荷。例如,也許別人也在發送一些你不了解的載荷,你應該將它們傳遞出來,因為你并不知道這邊變化是什么。

public void onBindViewHolder(UserViewHolder userViewHolder, int position) {
  userViewHolder.bindTo(mUserList.get(position));
}

public void onBindViewHolder(UserViewHolder holder, int position, List<Object> payloads) { notifyItemChanged(position, DATA_INVALIDATION); ... }</pre></div>

我們會將這發布為一個庫,因為它給了你更好的表現,給了你動畫,和讓一切都變得更好,讓 RecyclerView 使用起來更加愉快。Data Binding 將更像是它的一個快樂孩子!

“數據無效”只是一個簡單的對象,但我想你可能比較好奇它是什么樣的,所以我可以給你看一下:

static Object DATA_INVALIDATION = new Object();
private boolean isForDataBinding(List<Object> payloads) { 
  if (payloads == null || payloads.size() == 0) {
    return false;
  }
  for (Object obj : payloads) {
    if (obj != DATA_INVALIDATION) {
      return false;
    }
  }
return true;
}

多種類型的視圖項目(28:50)

另一個使用數據綁定的案例是多種視圖類型。經常會發生:你有一個頭視圖,或者類似從谷歌獲得的搜索結果,在那里你可能有一個照片結果,或一個鏈接文本結果。如果是 RecyclerView,你要怎么去構建?你有一個布局文件,使用一個變量,你把它命名為“data”,這個名字“data”是很重要的,因為你將重用相同的名字。你使用常規布局文件:

<layout>
    <data>
        <variable name="data" type="com.example.Photo"/>
    </data>
    <ImageView android:src="@{data.url}" …/>
</layout>

如果你需要另一種類型的結果,比如說叫“place”,那么你就需要有一個完全不同的布局,另一個XML文件:

<layout>
    <data>
        <variable name="data" type="com.example.Place"/>
    </data>
    <ImageView android:src="@{data.url}" …/>
</layout>

這兩個布局文件之間唯一共享的,就是變量名,即所謂的“data”。當我們這樣做時,我們創造的東西稱為 DataBoundViewHolder :

public class DataBoundViewHolder extends RecyclerView.ViewHolder {

private ViewDataBinding mBinding;

public DataBoundViewHolder(ViewDataBinding binding) { super(binding.getRoot()); mBinding = binding; }

public ViewDataBinding getBinding() { return mBinding; }

public void bindTo(Place place) { mBinding.setPlace(place); mBinding.executePendingBindings(); }

}</pre></div>

然后我們看一下下面這個,這是一個和上面幾乎一樣的 holder,這是一個能夠保持綁定的數據綁定基類,這個基類將作為給所有生成的代碼的父類,這就是為什么這個類能保持綁定的引用。我們之前創建的那個綁定 user 的 bindTo 方法,現在它將可以綁定 place。

不幸的是,在這個基類當中并沒有一個 setPlace 方法,但幸運的是我們提供了另外一個 API 叫做 setVariable :

public void bindTo(Object obj) {
  mBinding.setVariable(BR.data, obj);
  mBinding.executePendingBindings();
}

你可以提供需要綁定的目標 ID 和對應的整個對象,自動生成的代碼會去檢查它的類型,并將其賦值。

這個 setVariable 看起來像這樣:“如果傳進來的 ID 是我知道的,我就將它轉型并且賦值。”

boolean setVariable(int id, Object obj) {
  if (id == BR.data) {
    setPhoto((Photo) obj);
    return true;
  }
  return false;
}

對于 onBind , onCreate 方法類似以往的寫法,比較特別的就是 getItemViewType ,我們返回布局文件的 ID 作為這個方法的返回值。RecyclerView 會將它傳遞給 onCreateViewHolder , onCreateViewHolder 將通過 DataBindingUtil 去創建對應的綁定類。這樣所有項目都會有它們的布局,你不需要手動去創建這些不同布局類型的對象了。

DataBoundViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
  return DataBoundViewHolder.create(mLayoutInflater, viewGroup, type);
}

void onBindViewHolder(DataBoundViewHolder viewHolder, int position) { viewHolder.bindTo(mDataList.get(position)); }

public int getItemViewType(int position) { Object item = mItems.get(position); if (item instanceof Place) { return R.layout.place_layout; } else if (item instanceof Photo) { return R.layout.photo_layout; } throw new RuntimeException("invalid obj"); }</pre></div>

當然,如果你是在一個生產應用程序中,你可能會保留做實例檢查。你應該有一個基本的類,知道如何返回布局,這是通常的想法。

綁定適配器和回調(31:27)

根據民意測驗,接下來講的可能是最酷最受歡迎的特點…(我所做的)。它甚至可能是 Android 開發中最酷的功能。好吧,也許是我在炒作,哈哈。

讓我們想象一下,有什么比 setText 更復雜,或許可以是一個圖像的URL。你想設置為 ImageView,你需要設置一個圖片的網址。當然,你不想在 UI 線程上做這件事(請記住,這些東西是在 UI 線程上進行評估)。你想用 Picasso 或其他圖片加載庫。于是或許你會這么做:

<ImageView …
    android:src="@{contact.largeImageUrl}" />

這基本上無法正常工作。context 要從哪里來,你把它放在什么地方?這里取不到 view。所以正確的寫法應該是創建一個 BindingAdapter:

@BindingAdapter("android:src")
public static void setImageUrl(ImageView view, String url) {
    Picasso.with(view.getContext()).load(url).into(view);
}

現在這里的 BindingAdapter 是一個注解。這是為了我們設置的 android:src 和 Android 源碼的關聯。這是一個靜態的方法,它需要2個參數。它需要一個View 和一個字符串,但注意它也可以采取其他類型。如果你想要一個不同的方法,接受一個 int 參數或 drawable,你也是可以做到的。你可以把任何你想要的填入這里。在這種情況下,我們已經支持了 Picasso 這個庫的使用了。我們所有的代碼都在那里。既然我們有了這個 View,我們就可以得到它的背景。我們可以寫任何我們想寫的代碼。現在我們可以在用戶界面上把圖像加載出來,如你所愿。

利用屬性(33:12)

你可能想做一些更復雜的內容,比如說,我們想設置一個 PlaceHolder,設置它的圖片源文件,或者它的圖片 URL:

<ImageView …
   android:src="@{contact.largeImageUrl}"
   app:placeHolder="@{R.drawable.contact_placeholder}"/>

我們有兩個不同的屬性,同時他們對應的有兩個不同的靜態方法,但數據綁定卻不知道,所以目前還不能直接如你所愿地進行工作。實際上,現在我們在 BindingAdapter 注解中有兩個同樣的屬性,你只要取得這里他們的值,把他們傳遞給 Picasso 正確的方法即可:

<ImageView …
   android:src="@{contact.largeImageUrl}"
   app:placeHolder="@{R.drawable.contact_placeholder}"/>

@BindingAdapter(value = {"android:src", "placeHolder"},
                requireAll = false)
public static void setImageUrl(ImageView view, String url,
                               int placeHolder) {
    RequestCreator requestCreator =
        Picasso.with(view.getContext()).load(url);
    if (placeHolder != 0) {
        requestCreator.placeholder(placeHolder);
    }
    requestCreator.into(view);
}

如果你現在有三個屬性呢?有一個是 Android 源碼的圖片源屬性,一個是占位圖片屬性(PlaceHolder),還有一個是設置圖像的 URL 屬性。所有這些不同的 BindingAdapter。真的,我有點太懶了,所以讓我們做點別的了。我們可以有一個 BindingAdapter 注解需要所有這些屬性,也可以是只要一個或兩個,或它們的任意組合。我們所要做的就是把所需的一切都設為 false,然后我們獲得所有這些參數。如果有值就會傳遞過來,如果沒有提供,它會將它們作為默認值傳遞給它們。如果你沒有在你的布局中寫一個占位符屬性,那么占位符將為零。我們在 Picasso 中調用 setter 的時候會進行檢查。

舊的值(34:55)

你可能也需要一些舊的值。在這個例子中,我們有一個 OnLayoutChanged:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view,
        View.OnLayoutChangeListener oldValue,
        View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

我們想在我們添加一個新的值的時候,刪除一個舊的值,但在這個場景中,我們不知道舊的那個值是什么。我們可以添加新的,這很容易,但我們如何刪除舊的呢?嗯,其實你也可以獲得這個值,我們會給你的。如果你有這種代碼,我們將會知道要把舊的直給你。對于這樣的情況,它將在你的內存中,這樣很好。每次它改變,比如開始正確的動畫,你會想知道它之前是什么。

只要使用這個 API,我們的數據綁定庫就會實現你想要的。你只需考慮如何響應這個變化。當然,你也可以使用多屬性來實現同樣的結果。我們會把值都傳給你。

依賴注入(36:20)

讓我們想象一下我們有一個這樣的適配器:

public interface TestableAdapter {
    @BindingAdapter("android:src")
    void setImageUrl(ImageView imageView, String url);
}

public interface DataBindingComponent { TestableAdapter getTestableAdapter(); }

DataBindingUtil.setDefaultComponent(myComponent); ‐ or ‐ binding = MyLayoutBinding.inflate(layoutInflater, myComponent);</pre></div>

顯然將會發生的事情是,它會調用我設置的這個綁定目標 setImageUrl 。如果我有一些狀態,我想在我的 BindingAdapter 中獲取呢?或者說,我有各種不同的 BindingAdapter 取決于我在我的應用程序中想做的。在這種情況下,它會是一種痛苦。我們想要的只是其中的一個 BindingAdapter 實例。它要從哪里來?

我們所能做的,就是創建一個綁定組件, DataBindingComponent ,這是一個接口。當您有一個實例方法,我們將通過這個接口生成我們具體的適配器。它是由你來實現。我們不知道你是如何實現它,但你實現它,然后你可以設置默認組件(component)。

你也可以按每一個布局來做這件事。在這種情況下,一個設置好的默認值,它可以使用在所有的布局。然后我們知道用什么組件來得到你的適配器。

你可能還想用你的組件作為參數。例如,我們剛才看到了這 setImageURL 方法。

@BindingAdapter("android:src")
public static void setImageUrl(MyAppComponent component, 
                               ImageView view, 
                               String imageUrl) {
    component.getImageCache().loadInto(view, imageUrl);
}

我們要用某種狀態。讓我們想象一下,圖像緩存,我們要加載的圖像與圖像緩存。那這些項相關狀態來自哪里?我們要做的是使用組件。你可以在這里放置你想要的任意方法:在這種情況下,它是[somestate].get[somestate]。你要把它作為你的 BindingAdapter 第一個參數,然后你可以做任何你想做的事情。我們不知道你的組件在做什么,對嗎?這是你想做的,很方便的。

Event Handlers(事件處理)(38:56)

我們有一個 onClick 屬性,我們還有一個 clicked 方法在 handler 上, clicked 有可能對應的是 getClicked 或者 isClicked 方法,也可能是一個變量屬性,”clicked”,所以這種情況下我們要怎么才能確定這個事情呢?

<Button …
 android:onClick="@{isAdmin ? handler.adminClick : handler.userClick}" />

// View 中并沒有 "setOnClick" 方法,需要開發者幫忙去尋找到屬性對應的方法
@BindingMethods({
    @BindingMethod(type = View.class,
                   attribute = "android:onClick",
                   method = "setOnClickListener"}) 
// 在 View 中尋找 setOnClickListener
void setOnClickListener(View.OnClickListener l)

// 在 OnClickListener 中尋找到單一的一個抽象方法
void onClick(View v);</pre></div>

首先,我們需要明白 onClick 對應的意思。我們知道 onClick 并不是 setOnClick ,因為我們找不到 setOnClick 方法,但有一個綁定方法。這個綁定方法說:“ onClick 表示 setOnClickListener ,所以我們著眼于 setOnClickListener 方法,它需要一個 onClickListener 參數,讓我們來看一下它。

在 onClickListener 中只有一個抽象函數,所以我們可以確定地知道它就是這個事件監聽的處理者(handler)。現在我們看看這個處理者,我們找到一個方法,叫做 clicked .

static class OnClickListenerImpl1 implements OnClickListener {
    public Handler mHandler;
    @Override
    public void onClick(android.view.View arg0) {
        mHandler.adminClick(arg0);
    }
}
static class OnClickListenerImpl2 implements OnClickListener {
    public Handler mHandler;
    @Override
    public void onClick(android.view.View arg0) {
        mHandler.userClick(arg0);
    }
}

我們找到了這個 `clicked 方法,它需要一樣的參數,我們正好有匹配的參數,我們知道這就是一個事件處理者,所以我們知道該怎么做了:我們會把它視為一個事件,這個事件有它的處理者。

那么你在 TextWatcher 的情況下怎么辦?因為 TextWatcher 沒有單一的抽象方法,而是有三個抽象方法。在這種情況下,你所要做的就是你彌補自己的接口,然后你將它們合并在一起。實際上,這就是我所做的,我把他們合并放在一起。從本質上講,你在做什么,就等于合并所有“在什么之前”和“在什么之后”的變化。如果他們是為 null,那么你就不做任何操作,如果他們不是 null,那么你做一些事情,結合你所需要的做就行了。

最佳實踐(41:32)

數據綁定正在逐漸變得更好,我們想分享這個信息。這是一個非常新的庫,所以我們仍然在調查人們如何使用它,以及人們在什么場景中使用它。也有一些事情我們知道需要小心的。在使用數據綁定時,這里有一些是你應該記住的事情。

首先,你 在 XML 中使用一些表達式,但并不意味著你 應該 這么做。在表達式中應該沒有業務邏輯!因為這不是這個庫的目的。如果你試著這么做:“當這個按鈕被調用,讓我通過網絡服務發送信息給某人”,你可能認為“太好了,現在我不需要寫任何 Java 代碼了!”

<ImageView android:click="@{webservice.sendMoneyAsync}"/>

可是對不起,你錯了。這不是關于不用寫任何 Java 代碼的問題,而是如果你這樣做,你的代碼架構將崩潰。正確的做法應該是,任何你在這里放的東西應該是關于用戶界面的。

<ImageView android:click="@{presenter.onSendClick}"/>

如果用戶點擊了某個界面交互的部分,那么最好的就是調用你的處理者來處理點擊。其余的是你的應用程序邏輯,它們是數據綁定的范圍之外的,所以不要嘗試把它放在那里。

不過,簡單的用戶界面邏輯是值得歡迎的。這是另一個例子:

<ImageView android:src="@{user.age > 18 ? @drawable/adult : @drawable/kid}"/>

這正是你想展示的用戶界面,所以把代碼邏輯放在表達式里。當我看到這個 ImageView,我清楚地明白它綁定的圖片源是什么。這意味著,這是最好的開發實踐,試圖找到相關的代碼,并找出在這種情況下,它需要顯示的是什么。只要它是簡單和清晰的,就可以把它放在 XML 表達式那里。

保持你的表達式簡單。它要能夠易讀,以至于你看一眼就知道它要顯示的。下面是另一個例子。你能看出它要顯示的是什么嗎?

<TextView android:text='@users.age > 18 ? user.name.substring(0,1).toUpperCase() + "." + user.lastName : @string/redacted'/>

不要這么做,這么做不好。這樣就不再易讀了,因為有人試圖把東西擠在里面。相反,將它移到另一個方法,通過方法名來顯露它的意圖,這樣才好。

<TextView android:text='@{user.age > 18 ? user.displayName : @string/redacted}'/>

@Bindable public String getDisplayName() { return mName.substring(0, 1).toUpperCase()

  + "." + mLastName;

}

public void setLastName(String lastName) { … notifyPropertyChanged(BR.displayName); }</pre></div>

現在這個布局文件是可讀的。把復雜的邏輯代碼放到你的 Java 代碼去。你只需要告訴數據綁定,什么時候該更新它自己,然后去調度需要通知事件即可。盡量保持簡單明了最好了。

試想一下使用一個 ViewModel。ViewModel 的作用就是,保持你想要在 UI顯示的信息。通過你的 XML 訪問對象是完全可以的,只要他們是一目了然的代碼,所以把 user.name 寫在那里是可取的,因為數據對象應該只持有和它相關的對象。但是,如果你把顯示名放在那里,這是你的用戶界面信息,你不想把它放進你的對象。這顯然也是一種情況,這些事情沒有一個“一定要怎么做”的結論。這取決于你的團隊和你的喜好,但你仍然可以使用這個ViewModel。

我的用戶界面顯示一個好友計數和訂閱日期:

public class ProfileViewModel {
  public final ObservableInt friendCount;
  public final ObservableField<Date> subscriptionDate;
}

設置這些東西,你的XML文件將引用視圖模型。但有時你想要更好的分離。

public class ProfileViewModel extends BaseObservable {
  private @Bindable String mDisplayName;

public void setUser(User user) { mDisplayName.set(user.getName() + " " + user.getLastName()); notifyPropertyChange(BR.displayName); }

public String getDisplayName(){…} }</pre></div>

這個視圖模型(view model)僅僅是一個值的持有者,你將它設置給你的 activity 或者你的 presenter。如果你不喜歡它,或者如果你想對這些東西做一些抽象,你可以寫你的 ViewModels,同時繼承基礎 observer 類。

以下這有一個私有的屬性,我知道它會接受用戶的信息,因為這是關于用戶的配置文件。無論它想顯示什么到這個界面,他們都需要有一個用戶。他們把它放到你的用戶界面,然后你設置你的變量屬性。當然,你需要嘗試改變,你需要一個 getter,使數據綁定可以接收那個屬性。我們知道這是可綁定的,但是數據綁定將永遠不會使用反射,所以沒有辦法得到私有屬性內容。所以,你必須為這個屬性提供一個公開的 getter 方法。

public class ProfileViewModel implements Observable {
  private @Bindable String mDisplayName;
  PropertyChangeRegistry mRegistry = new PropertyChangeRegistry();

public void setUser(User user) { mDisplayName.set(user.getName() + " " + user.getLastName()); mRegistry.notifyPropertyChange(BR.displayName); }

public String getDisplayName(){…}

public void addOnPropertyChangedCallback(OnPropertyChangedCallback cb) mRegistry.add(cb); }

public void removeOnPropertyChangedCallback(OnPropertyChangedCallback cb) { mRegistry.remove(cb); } }</pre></div>

這對于測試是很友好的。它是孤立的,它與用戶界面無關,但它也可以給用戶界面內容。如果你不想擴展代碼,你可以實現基礎 observable 接口。你可以在內部使用 PropertyChangedRegistry 這個輔助類。只是將所有的這些方法都轉發到這個類,然后你想要的任何對象都可以被觀察到。

這就是為什么它是最流行的功能。我看到人們使用這個他們渴望了很多年的功能實現了各種很酷的東西,真是高興。下面是一個例子,一個 Android 開發者創建的:

<TextView app:font="@{Source-Sans-Pro-Regular.ttf}"/>

public class AppAdapters {

@BindingAdapter({"font"}) public static void setFont(TextView textView, String fontName){ AssetManager assetManager = textView.getContext().getAssets(); String path = "fonts/" + fontName; Typeface typeface = sCache.get(path); if (typeface == null) { typeface = Typeface.createFromAsset(assetManager, path); sCache.put(path, typeface); } textView.setTypeface(typeface); } }</pre></div>

開發者想要這個字體屬性,它原本是不存在的,所以她決定使用數據綁定來創建它。其實很多人都夢想過有這么一個方法!文件也將更清晰明了,事情也會更加集中。

這里是另一個例子,使用 DataBindingComponent。這些適配器的大部分優點就是他們都是靜態的,但靜態的在測試方面并不是很好,他們沒有很好的可測試性。所以,你創建一個 ImageAdapter,方法不要再是靜態的了。

public class MockImageAdapter extends ImageAdapter {

@BindingAdapter(value={"photoUrl", "default"}, requireAll = false) public void setUrl(ImageView imageView, String url, Drawable defaultImage) { Glide.with(imageView.getContext()) .load(url).into(imageView) .onLoadStarted(defaultImage); } }

public class AppComponent implements DataBindingComponent { ImageAdapter mImageAdapter = new ImageAdapter();

public ImageAdapter getImageAdapter() { return mImageAdapter; } }</pre></div>

你寫這個 app component,知道如何提供 GetImageAdapter。很酷的是你可以創建一個 MockImageAdapter 繼承自你有的。然后在一些與用戶界面無關的測試進行模擬運行,這里不用任何圖片的網址,我們只是使用ImageDrawable。當然,您可以創建我們在測試中使用的測試組件,并將其提供給返回模擬圖像組件的數據綁定。

如果你是使用依賴注入,這些組件能夠非常好地與其一起工作。你實際上可以只注入這個:

public class AppComponent implements DataBindingComponent { 
  @Inject ImageAdapter mImageAdapter;

public ImageAdapter getImageAdapter() { return mImageAdapter; } }</pre></div>

最后,這是一個真實的 Dagger 2 示例:

@Component(modules = ProductionModule.class)
public interface ProductionComponent extends DataBindingComponent {}

@Module public class ProductionModule { @Provides TestableAdapter provideTestableAdapter() { return new ProductionAdapter(); } } DataBindingUtil.setDefaultComponent( ProductionComponent.builder().build());</pre></div>

你僅需要做的是,有一個組件,繼承數據綁定組件。然后就可以編譯了。數據綁定將產生這個數據綁定組件,然后將 Dagger 將采取這一生產組件,并生成相應的方法給你。他們一起能很好地共同工作。所以說,這真的是一個免費而且特別好的庫。

問與答(50:35)

問: 幕后自動重寫的方法會和我自己實現的沖突嗎?比如 setTag 方法。如果是這樣,我就沒有辦法使用 Data Binding 了嗎?

Yigit:這個問題實際上有兩個答案。一是,如果你在除了根布局的布局中使用 tag,那就完全可以了。另外,當我們做布局文件的解析時,我們會把你的 tag 去掉,然后把它放回我們的數據綁定。

**問: 是否有方法可以創建一個注解或控制一些 Java 代碼的生成?我知道使用 Gradle 可以提供某種布局處理器,那么數據綁定也可以嗎?
**

Yigit:這實際上是一種 hack 做法。如果你嘗試使用它作為一個單獨的插件,我們還沒有集成。我不知道我們是否會提供一個接口,但如果有一些用例,也許你應該向工具組報告。

問: 你們是否有計劃增加一些功能,例如在 xml 中定義的表達式中支持 lint?

George:我們有另一個可謂懸而未決的功能,如果你編譯失敗,你可以點擊并進入錯誤。你將能夠更準確地獲得錯誤報告。這功能會隨著時間的推移而改善。現在,我們還處于“嬰兒階段”。它只是進入Android Studio。如你所說,我們談論的是 Gradle 插件,但我們沒有成為 Android Studio 插件的一部分。隨著時間的推移,它會變得越來越好。我想在某個時候,你不會再想不使用數據綁定,它會你的應用程序必要的一部分。

問: 如果你要生成圖形用戶界面的代碼,你可以使用 Data Binding 嗎?

George:不可以。

問: 如果你要綁定適配器,你能預先進行一些定制,當屏幕上有某些變量的變化,你可以在代碼中安裝一個回調來廣播這個事件嗎?

George:你可以在布局中添加一個事件監聽器,它可以調用回你的代碼。然后你可以在你的代碼中做任何你想做的事,包括改變或觸發通知的變更。

問: 可以使用嵌套布局嗎,使用包含(include)和合并(merge)?

George:您可以使用 include。唯一限制是你不能把它們作為根標簽。不過你通常也無法將它們作為 root。在正常布局中,如果你有一個 merge 標簽,你可以有多個 include 在根布局中。在數據綁定中,你不能這樣做,但這是唯一的限制。

Yigit:如果你包含一個不繼承任何變量的布局,這個布局有它自己的變量,你可以將所有的變量傳遞到這個包含的布局中,還可以對這些變量進行重命名。

George:你也可以有一些綁定表達式,發送一些不一樣的內容到 include 布局中。

問: 這些實現會影響 debug 調試嗎?能否支持單步調試?

Yigit:目前來說,如果你只是使用數據綁定庫,你不能直接看到生成的代碼。你可以配置 Android 的視圖,但默認情況下,任何視圖的代碼都會自動完成,如果你要綁定你的用戶,當你點擊它,它將帶你到 XML 文件。這是他們的聯系。然而,如果你結合 APT 使用它,你可以看到生成的代碼。但是,如果你使用生成的代碼,你就會失去對代碼的實時更新。現在,結合 APT,如果你改變了你的 XML 文件,您可以輕松地開始使用新的 Java 文件,而無需重新編譯。我們實際上已經提供了你想要的了,在這些開發的地方,你可以和 XML 相互作用。但是,如果你在調試時嘗試去那些類,它得帶你到執行的代碼行。但它還沒有,它還需要一段時間。

問: 你是在哪里遇到 Data Binding 的?是什么促使你把它添加到 Android 開發當中?在其他框架中,你看到過類似的東西嗎?

George:我不知道這么說是否具體。我們在別的一些框架上也用過 Data Binding,不過我們還是把它做得很 Android 化,所以說它并不是從其他一些數據綁定庫中復制過來的。

Yigit:在我們以前的專業作品中,我在做 Adobe Flex 做了很多工作,所以你可以說它是相似的。然而,這些平臺都遭受了數據綁定的性能問題。

如果你去看 維基百科中關于 MVVM 到文章 , 他們提到了數據綁定的性能問題。從第一天起,我們就覺得:Android 的數據綁定是永遠不能慢。我們最好不要釋放出一些慢的東西,因為這樣它不會有幫助。

</div>

問: 對于這樣一個新的 XML 代碼層內容在測試方面有什么變化?

George:我們正在做的本質上是想,如果你寫你自己的代碼,你會做什么。我們在幕后執行方法,所以在這方面沒有什么變化。

Yigit:在測試方面,它取決于你的應用程序的偏好。如果你的團隊喜歡寫集成測試,一些人就這樣做,他們寫的是重的集成測試,測試用戶界面,這樣他們會被覆蓋。如果你的團隊更喜歡做單元測試的樣式,最好使用視圖模型,你可以輕松測試。如果一些東西改變了,你只需要測試的視圖模型(view model)。這是數據綁定庫,這不是你的代碼。但這確實取決于你的應用架構。我們提供這些靈活的組件,所以你可以在測試階段注入不同的東西。在可測性方面,沒有什么變化。事實上,我認為我們提供了一個更好的模型,使它可以更容易測試。有一個好消息就是,Android Studio 會持續變得越來越好,我可以想象在一些遙遠的未來日期,它將允許您將數據注入到您的布局,并能夠看到它。

See the discussion on Hacker News .

Sign up to be notified of new videos — we won’t email you for any other reason, ever.

</div>

來自: https://realm.io/cn/news/data-binding-android-boyar-mount/

</span></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code></code>

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!