天天看點

資料綁定(data binding )1

2015年的google i/o大會釋出了很多新的android庫和工具,data binding就是其中之一。在本系列文章中,我們會探索它的強大之處。

值得注意的是:我寫這篇文章的時候,data binding庫正在測試中,是以很多api可能會在釋出的時候有所改動。

data binding庫提供了一個連結待顯示資料與ui元件的機制,将原本非常被動的資料變成某種形式的資料源。在本篇文章中我們使用一個非常簡單的twitter用戶端作為例子,将twitter api與data binding一起使用。我不會在這裡介紹api以及app設計,我僅僅是使用twitter4j庫抽取了你的twitter首頁上50條按時間排序的資訊,并将他們展示在<code>recyclerview</code>中。所有的代碼都是公開釋出的,你可以放心地使用它了解它。這裡面最有意思的就是其中的資料及它們與view綁定的過程。

我們來看看這篇例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

public class status {

    private final string name;

    private final string screenname;

    private final string text;

    private final string imageurl;

    public status(@nonnull string name, @nonnull string screenname, @nonnull string text, @nonnull string imageurl) {

        this.name = name;

        this.screenname = screenname;

        this.text = text;

        this.imageurl = imageurl;

    }

    public string getname() {

        return name;

    public string getscreenname() {

        return screenname;

    public string gettext() {

        return text;

    public string getimageurl() {

        return imageurl;

}

這個類維持了幾個需要展示給使用者的基本元素,每一個在<code>recyclerview</code>中的機關都會綁定一個<code>status</code>對象。它裡面的資訊可以從<code>twitter4j.status</code>(由twitter4j的api擷取)中得到,是以我們還要建立一個将<code>twitter4j.status</code>轉化為<code>status</code>的類:

publicclassstatusmarshaller{

    publiclist&lt;status&gt;marshall(list&lt;twitter4j.status&gt;statuses){

        list&lt;status&gt;newstatuses=newarraylist&lt;&gt;(statuses.size());

        for(twitter4j.statusstatus:statuses){

            newstatuses.add(marshall(status));

        }

        returnnewstatuses;

    privatestatusmarshall(twitter4j.statusstatus){

        useruser=status.getuser();

        returnnewstatus(user.getname(),user.getscreenname(),status.gettext(),user.getbiggerprofileimageurl());

這裡沒有用到任何技巧,隻是一個簡單的java操作,跟data binding無關,甚至跟android也無關。

不過需要指出的是,我們本可以直接将view與<code>twitter4j.status</code>綁定在一起,這樣無疑效率會更高。但是data

binding庫使用了mvvm(model-view-viewmodel)設計模式 – model是<code>twitter4j.status</code>,view是ui元件,viewmodel是我們的<code>status</code>對象。viewmodel代表着專門為view設計的一個資料結構,它與view的适配性比model更好。雖然model與viewmodel很像,在目前你可能還感覺不出他們的差別,但是随着我們一步步深入下去,我相信你會明白這樣設計的深意。

接下來我們看一下<code>recyclerview</code>的adapter是怎麼設計的:

32

33

34

35

36

37

38

39

40

41

42

public class statusadapter extends recyclerview.adapter&lt;statusviewholder&gt; {

    private final list&lt;status&gt; statuses;

    private final statusmarshaller marshaller;

    public static statusadapter newinstance() {

        list&lt;status&gt; statuses = new arraylist&lt;&gt;();

        statusmarshaller marshaller = new statusmarshaller();

        return new statusadapter(statuses, marshaller);

    statusadapter(list&lt;status&gt; statuses, statusmarshaller marshaller) {

        this.statuses = statuses;

        this.marshaller = marshaller;

    @override

    public statusviewholder oncreateviewholder(viewgroup parent, int viewtype) {

        context context = parent.getcontext();

        layoutinflater inflater = layoutinflater.from(context);

        view statuscontainer = inflater.inflate(r.layout.status_item, parent, false);

        return new statusviewholder(statuscontainer);

    public void onbindviewholder(statusviewholder holder, int position) {

        status status = statuses.get(position);

        holder.bind(status);

    public int getitemcount() {

        return statuses.size();

    public void setstatuses(list&lt;twitter4j.status&gt; statuses) {

        this.statuses.clear();

        this.statuses.addall(marshaller.marshall(statuses));

        notifydatasetchanged();

終于看到了跟android有關的地方了吧,實際上沒什麼特殊的地方——這隻是一個基本的<code>recyclerview.adapter</code>而已,跟data binding其實關系不大。這裡面唯一跟mvvm有關系的就是在<code>setstatuses()</code>中将<code>twitter4j.status</code>轉換成<code>status</code>。我們馬上将會在statusviewholder中看到怎麼進行資料綁定,首先我們來看看layout是怎麼定義的。

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

&lt;?xmlversion="1.0"encoding="utf-8"?&gt;

&lt;layoutxmlns:android="http://schemas.android.com/apk/res/android"&gt;

  &lt;data&gt;

    &lt;importtype="android.view.view"/&gt;

    &lt;variable

      name="status"

      type="com.stylingandroid.databinding.data.status"/&gt;

  &lt;/data&gt;

  &lt;relativelayout

    android:id="@+id/status_container"

    android:layout_width="match_parent"

    android:layout_height="match_parent"&gt;

    &lt;imageview

      android:id="@+id/status_avatar"

      android:layout_width="64dp"

      android:layout_height="64dp"

      android:layout_alignparentleft="true"

      android:layout_alignparentstart="true"

      android:layout_alignparenttop="true"

      android:layout_margin="8dip"

      android:contentdescription="@null"/&gt;

    &lt;textview

      android:id="@+id/status_name"

      style="@style/status.name"

      android:layout_width="wrap_content"

      android:layout_height="wrap_content"

      android:layout_margintop="8dip"

      android:layout_toendof="@id/status_avatar"

      android:layout_torightof="@id/status_avatar"

      android:text="@{status.name}"/&gt;

      android:id="@+id/status_screen_name"

      style="@style/status.screenname"

      android:layout_alignbaseline="@id/status_name"

      android:layout_marginleft="4dip"

      android:layout_marginstart="4dip"

      android:layout_toendof="@id/status_name"

      android:layout_torightof="@id/status_name"

      android:text="@{&amp;quot;@&amp;quot;

+ status.screenname}"/&gt;

      android:id="@+id/status_text"

      style="@style/status.text"

      android:layout_width="match_parent"

      android:layout_alignleft="@id/status_name"

      android:layout_alignparentend="true"

      android:layout_alignparentright="true"

      android:layout_alignstart="@id/status_name"

      android:layout_below="@id/status_name"

      android:maxlines="2"

      android:singleline="false"

      android:text="@{status.text}"/&gt;

  &lt;/relativelayout&gt;

&lt;/layout&gt;

這部分代碼是data binding運作的核心。其中id為<code>status_container</code>的<code>relativelayout</code>是一個普通的layout控件,但是它的父控件<code>&lt;layout&gt;</code>就不是那麼熟悉了。<code>&lt;layout&gt;</code>是data

binding庫的一個元件,它含有一個<code>&lt;data&gt;</code>子元件說明了需要綁定的資料對象(在此處為<code>status</code>),然後我們就可以在此layout中引用該資料對象。

在此布局中textview的<code>android:text</code>屬性值可能跟你正常見到樣子的不大一樣-它們其實都使用了<code>status</code>中的getter。使用<code>@{}</code>包裝說明這是一個data

binding表達式,<code>status.name</code>等同于java中的<code>status.getname()</code>。這是data

binding工作的核心,但是這隻是冰山一角,它還有更多有趣的功能值得我們探索。

你看id為<code>status_screen_name</code>的textview,它的text設定為<code>@{&amp;quot;@&amp;quot; + status.screenname}</code>。你可能看到覺得很困惑,實際上<code>&amp;quot;@&amp;quot;</code>就代表着@,用<code>&amp;quot;</code>将它包裝起來是為了防止@被xml轉義。這個語句告訴我們data

binding語句是很強大的,我們會進一步挖掘它的強大之處。

在定義完layout之後,需要将它和實際資料對象結合,這也是<code>statusviewholder</code>做的事:

publicclassstatusviewholderextendsrecyclerview.viewholder{

    privatestatusitembindingbinding;

    publicstatusviewholder(viewitemview){

        super(itemview);

        binding=databindingutil.bind(itemview);

    publicvoidbind(statusstatus){

        binding.setstatus(status);

它比正常使用的<code>viewholder</code>(通常用于維持子view對象)更簡單一點,在<code>bind()</code>方法中将對象賦予它綁定的layout。首先要注意到我們使用了<code>statusitembinding</code>,可以通過<code>databindingutil.bind()</code>函數擷取到它,這個函數及<code>statusitenbinding</code>類都是data

binding庫生成的。

在下一章中我們會讨論更多的細節,不過此處我們已經有一個基本的應用能夠讓你對data binding的作用有一定認識:

資料綁定(data binding )1

繼續閱讀