Android MVVM框架 DataBinding

開篇廢話

公司走了一個(gè)人,那個(gè)人寫的程序使用到了DataBinding腌乡,既然這樣,我就必須學(xué)習(xí)DataBinding,盡快接手這個(gè)項(xiàng)目繁调。
DataBinding解決了Android UI編程中的一個(gè)痛點(diǎn)晒来,官方原生支持MVVM模型可以讓我們在不改變既有代碼框架的前提下竭宰,非常容易地使用這些新特性豫喧。

MVVM的介紹

MVVM是Model-View-ViewModel的簡寫,這個(gè)模式提供對View和View Model的雙向數(shù)據(jù)綁定谦絮,使得View Model的狀態(tài)改變可以自動傳遞給View题诵。

  • Model:數(shù)據(jù)層洁仗,負(fù)責(zé)處理數(shù)據(jù)的加載或者存儲。
  • View:視圖層性锭,負(fù)責(zé)界面數(shù)據(jù)的展示赠潦,與用戶進(jìn)行交互。
  • ViewModel:負(fù)責(zé)完成View于Model間的交互,負(fù)責(zé)業(yè)務(wù)邏輯草冈。

MVVM的模型關(guān)系圖:

MVVM的模型關(guān)系圖

準(zhǔn)備工作

DataBinding是一個(gè)support library她奥,所以它可以支持所有的android sdk,最低可以到android2.1(API7)怎棱。

如果是Android studio的版本在2.1以上哩俭,Android studio內(nèi)置就支持了 DataBiding。如果是2.1之前的版本最好是升級一下拳恋,然后只需要在對應(yīng)的Module的build.gradle中添加這么一句話即可凡资。

dataBinding {
    enabled=true
}

dataBinding.enabled=true

如果Android studio的版本不在2.1以上,那么就需要使用以下方面了谬运,如果高于2.1隙赁,直接跳到基礎(chǔ)操作看。

使用DataBinding需要Android Gradle插件的支持梆暖,版本至少在1.5以上伞访,需要的Android studio的版本在1.3以上。用如下方法導(dǎo)入轰驳。

AS 2.1以下修改build.gradle

再次提示一下厚掷,如果Android studio的版本在2.1以下,那么就需要使用以下方面了级解,如果高于2.1冒黑,直接跳到基礎(chǔ)操作看。
修改 Project 的build.gradle蠕趁,為 build script 添加一條依賴薛闪,Gradle 版本為 1.2.3辛馆。

classpath 'com.android.tools.build:gradle:1.2.3'
classpath 'com.android.databinding:dataBinder:1.0-rc0'

為用到 DataBinding 的模塊添加插件俺陋,修改對應(yīng)的build.gradle。

apply plugin: 'com.android.databinding'
注意

如果 Module 用到的 buildToolsVersion 高于 22.0.1昙篙,比如 23 rc1腊状,那 com.android.databinding:dataBinder 的版本要改為 1.3.0-beta1,否則會出現(xiàn)如下錯(cuò)誤:

基礎(chǔ)操作

工程創(chuàng)建完成后苔可,我們通過一個(gè)最簡單的例子來說明 DataBinding 的基本用法缴挖。

布局文件

使用 DataBinding 之后,xml的布局文件就不再單純地展示 UI 元素焚辅,還需要定義 UI 元素用到的變量映屋。所以苟鸯,它的根節(jié)點(diǎn)不再是一個(gè)ViewGroup,而是變成了layout棚点,并且新增了一個(gè)節(jié)點(diǎn)data早处。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
    </data>
    <!--原先的根節(jié)點(diǎn)(Root Element)-->
    <LinearLayout>
    ....
    </LinearLayout>
</layout>

要實(shí)現(xiàn) MVVM 的ViewModel 就需要把數(shù)據(jù)與UI進(jìn)行綁定,data節(jié)點(diǎn)就為此提供了一個(gè)橋梁瘫析,我們先在data 中聲明一個(gè)variable砌梆,這個(gè)變量會為UI 元素提供數(shù)據(jù)(例如 TextView 的 android:text),然后在Java代碼中把”后臺”數(shù)據(jù)與這個(gè)variable進(jìn)行綁定贬循。

如果要用一個(gè)表格來展示用戶的基本信息咸包,用 DataBinding 應(yīng)該怎么實(shí)現(xiàn)呢?

數(shù)據(jù)對象

添加一個(gè) POJO類 - User杖虾,非常簡單烂瘫,四個(gè)屬性以及他們的getter和setter。

public class User {
    private final String firstName;
    private final String lastName;
    private String displayName;
    private int age;
 
    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    public User(String firstName, String lastName, int age) {
        this(firstName, lastName);
        this.age = age;
    }
 
    public int getAge() {
        return age;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public String getDisplayName() {
        return firstName + " " + lastName;
    }
 
    public boolean isAdult() {
        return age >= 18;
    }
}

稍后亏掀,我們會新建一個(gè)User類型的變量忱反,然后把它跟布局文件中聲明的變量進(jìn)行綁定。

定義Variable

再回到布局文件滤愕,在data節(jié)點(diǎn)中聲明一個(gè)變量user温算。

<data>
    <variable name="user" type="com.cc.databinding.User" />
</data>

其中type屬性就是我們在Java文件中定義的User類。

當(dāng)然间影,data節(jié)點(diǎn)也支持import注竿,所以上面的代碼可以換一種形式來寫。

<data>
    <import type="com.cc.databinding.User" />
    <variable name="user" type="User" />
</data>

然后我們剛才在 build.gradle 中添加的那個(gè)插件 - com.android.databinding會根據(jù)xml文件的名稱Generate一個(gè)繼承自ViewDataBinding的類魂贬。

例如巩割,這里xml的文件名叫activity_basic.xml,那么生成的類就是ActivityBasicBinding付燥。

注意

java.lang.*包中的類會被自動導(dǎo)入宣谈,可以直接使用,例如要定義一個(gè)String類型的變量:

<variable name="firstName" type="String" />

綁定Variable

Activity

修改BasicActivity的onCreate方法键科,用DatabindingUtil.setContentView()來替換掉setContentView()闻丑,然后創(chuàng)建一個(gè)user對象,通過binding.setUser(user)與variable進(jìn)行綁定勋颖。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityBasicBinding binding = DataBindingUtil.setContentView(
            this, R.layout.activity_basic);
    User user = new User("guo", "cc");
    binding.setUser(user);
}
Fragment

所幸DataBinding庫還提供了另外一個(gè)初始化布局的方法:DataBindingUtil.inflate()嗦嗡。

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ViewDataBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_blank,container,false);
        return binding.getRoot();
    }
注意

ActivityBasicBinding類是自動生成的,所有的set方法也是根據(jù)variable名稱生成的饭玲。例如侥祭,我們定義了兩個(gè)變量。

<data>
    <variable name="firstName" type="String" />
    <variable name="lastName" type="String" />
</data>

那么就會生成對應(yīng)的兩個(gè) set 方法。

setFirstName(String firstName);
setLastName(String lastName);

使用 Variable

數(shù)據(jù)與 Variable 綁定之后矮冬,xml 的 UI 元素就可以直接使用了谈宛。

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.lastName}" />

至此,一個(gè)簡單的數(shù)據(jù)綁定就完成了胎署。

高級用法

綁定非Activity的onClick寫法

android:onClick="@{(view)->user.show(view)}"

使用類方法

首先為類添加一個(gè)靜態(tài)方法入挣。

public class MyStringUtils {
    public static String capitalize(final String word) {
        if (word.length() > 1) {
            return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1);
        }
        return word;
    }
}

然后在xml的data節(jié)點(diǎn)中導(dǎo)入:

<import type="com.cc.databinding.MyStringUtils" />

使用方法與 Java 語法一樣:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{MyStringUtils.capitalize(user.firstName)}" />

類型別名

如果我們在data節(jié)點(diǎn)了導(dǎo)入了兩個(gè)同名的類怎么辦?

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" />
<variable name="user" type="User" />

這樣一來出現(xiàn)了兩個(gè)User類硝拧,那user變量要用哪一個(gè)呢径筏?不用擔(dān)心,import還有一個(gè)alias屬性障陶。

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />

Null Coalescing 運(yùn)算符

android:text="@{user.displayName ?? user.lastName}"

就等價(jià)于

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

屬性值

通過@{}可以直接把Java中定義的屬性值賦值給xml屬性滋恬。

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

別忘了導(dǎo)包,否則View.VISIBLE和View.GONE不可以使用抱究。

<import type="android.view.View"/>

使用資源數(shù)據(jù)

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

完整版的布局文件如下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 記得在前面加個(gè)“.” -->
    <data class=".ResourceBinding">
        <variable name="large" type="boolean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
            android:background="@android:color/black"
            android:textColor="@android:color/white"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/hello_world" />
    </LinearLayout>
</layout>

largePadding和smallPadding都是定義在dimens.xml文件中的資源數(shù)據(jù)恢氯。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="largePadding">20dp</dimen>
    <dimen name="smallPadding">5dp</dimen>
</resources>

在Java代碼中與綁定large變量,并賦值為ture鼓寺。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // ResourceBinding不需要導(dǎo)包勋拟,導(dǎo)包就錯(cuò)了
    ResourceBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_resource);
    binding.setLarge(true);
} 

這樣做是沒有什么問題的,但是有老版本的可能會出問題妈候。
如果在Run工程的時(shí)候敢靡,出現(xiàn)錯(cuò)誤,報(bào)錯(cuò)信息如下:

cannot find the setter for attribute 'android:padding' on android.widget.TextView with parameter type float.

看來像是DataBinder把@dimen/largePadding解析成了float類型苦银,可以試一下類型轉(zhuǎn)換:

android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"

雙向綁定

在舊版本只支持單向綁定啸胧,建議升級為最新版。
在正向綁定中幔虏,我們在Layout里面的綁定表達(dá)式是這樣的:

<layout ...>
  <data>
    <variable type="com.example.myapp.User" name="user"/>
  </data>
  <RelativeLayout ...>
    <TextView android:text="@{user.name}" .../>
  </RelativeLayout>
</layout>

當(dāng)user.name的數(shù)據(jù)改動時(shí)纺念,我們的TextView都會同步改變文字。

現(xiàn)在假設(shè)一種情況想括,當(dāng)你更換成EditText時(shí)陷谱,如果你的用戶名User.name已經(jīng)綁定到EditText中,當(dāng)用戶輸入文字的時(shí)候瑟蜈,你原來的user.name數(shù)據(jù)并沒有同步改動烟逊,因此我們需要修改成:

<layout ...>
  <data>
    <variable type="com.example.myapp.User" name="user"/>
  </data>
  <RelativeLayout ...>
    <EditText android:text="@={user.name}" .../>
  </RelativeLayout>
</layout>

看出微小的差別了嗎?對踪栋,就是"@{}"改成了"@={}"焙格,是不是很簡單图毕?

開啟雙向綁定夷都,需要在項(xiàng)目的build.gradle中設(shè)置:

classpath 'com.android.tools.build:gradle:2.1.0-alpha3'

我們剛才的例子里面只顯示了系統(tǒng)自帶的應(yīng)用,那么如果是自定義控件,或者是我們更細(xì)顆粒度的Observable呢囤官?等下就揭曉如何自定義自己的雙向綁定冬阳,我們來看看目前Android支持的控件:

  • AbsListView android:selectedItemPosition
  • CalendarView android:date
  • CompoundButton android:checked
  • DatePicker android:year, android:month, android:day
  • NumberPicker android:value
  • RadioGroup android:checkedButton
  • RatingBar android:rating
  • SeekBar android:progress
  • TabHost android:currentTab (估計(jì)沒人用)
  • TextView android:text
  • TimePicker android:hour, android:minute

自定義雙向綁定

設(shè)想一下我們使用了下拉刷新SwipeRefreshLayout控件,這個(gè)時(shí)候我們希望在加載數(shù)據(jù)的時(shí)候能控制refreshing的狀態(tài)党饮,所以我們加入了ObservableBoolean的變量swipeRefreshViewRefreshing來正向綁定數(shù)據(jù)肝陪,并且能夠在用戶手動下拉刷新的時(shí)候同步更新swipeRefreshViewRefreshing數(shù)據(jù):

// SwipeRefreshLayout.java

public class SwipeRefreshLayout extends View {
    private boolean isRefreshing;
    public void setRefreshing() {/* ... */}
    public boolean isRefreshing() {/* ... */}
    public void setOnRefreshListener(OnRefreshListener listener) {
        /* ... */
    }
    public interface OnRefreshListener {
        void onRefresh();
    }
}

接下來我們需要告訴框架,我們需要將SwipeRefreshLayout的isRefreshing的值反向綁定到swipeRefreshViewRefreshing:

@InverseBindingMethods({
        @InverseBindingMethod(
                type = android.support.v4.widget.SwipeRefreshLayout.class,
                attribute = "refreshing",
                event = "refreshingAttrChanged",
                method = "isRefreshing")})

這是一種簡單的定義刑顺,其中event和method都不是必須的氯窍,因?yàn)橄到y(tǒng)會自動生成,寫出來是為了更好地了解如何綁定的蹲堂,可以參考官方文檔InverseBindingMethod狼讨。

當(dāng)然你也可以使用另外一種寫法,并且如果你的值并不是直接對應(yīng)Observable
的值的時(shí)候柒竞,就可以在這里進(jìn)行轉(zhuǎn)換:

@InverseBindingAdapter(attribute = "refreshing", event = "refreshingAttrChanged")
public static boolean isRefreshing(SwipeRefreshLayout view) {
    return view.isRefreshing();
}

上面的event同樣也不是必須的政供。以上的定義都是為了讓我們能夠在布局文件中使用"@={}"這個(gè)雙向綁定的特性。接下來你需要告訴框架如何處理refreshingAttrChanged事件朽基,就像處理一般的監(jiān)聽事件一樣:

@BindingAdapter("refreshingAttrChanged")
public static void setOnRefreshListener(final SwipeRefreshLayout view,
    final InverseBindingListener refreshingAttrChanged) {

    if (refreshingAttrChanged == null) {
        view.setOnRefreshListener(null);
    } else {
        view.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh() {
                colorChange.onChange();
            }
        });
    }
}

一般情況下布隔,我們都需要設(shè)置正常的OnRefreshListener,所以我們可以合并寫成:

@BindingAdapter(value = {"onRefreshListener", "refreshingAttrChanged"}, requireAll = false)
public static void setOnRefreshListener(final SwipeRefreshLayout view,
    final OnRefreshListener listener,
    final InverseBindingListener refreshingAttrChanged) {

    OnRefreshListener newValue = new OnRefreshListener() {
        @Override
        public void onRefresh() {
            if (listener != null) {
                listener.onRefresh();
            }
            if (refreshingAttrChanged != null) {
                refreshingAttrChanged.onChange();
            }
        }
    };

    OnRefreshListener oldValue = ListenerUtil.trackListener(view, newValue, R.id.onRefreshListener);
    if (oldValue != null) {
        view.setOnRefreshListener(null);
    }
    view.setOnRefreshListener(newValue);
}

現(xiàn)在我們終于可以使用雙向綁定的技術(shù)啦稼虎。但是要注意衅檀,需要設(shè)置requireAll = false,否則系統(tǒng)將識別不了refreshingAttrChanged屬性霎俩,前文提到的文章例子里并沒有設(shè)置這個(gè)术吝。

在ViewModel中,我們的數(shù)據(jù)是這樣的:

// MyViewModel.java

public final ObservableBoolean swipeRefreshViewRefreshing = new ObservableBoolean(false);

public void load() {
    swipeRefreshViewRefreshing.set(true);

    // 網(wǎng)絡(luò)請求
    ....

    swipeRefreshViewRefreshing.set(false);
}

public SwipeRefreshLayout.OnRefreshListener onRefreshListener() {
    return new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            // Do something you need
        }
    };
}

在布局文件中是這樣設(shè)置的:

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_refresh_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:onRefreshListener="@{viewModel.onRefreshListener}"
    app:refreshing="@={viewModel.swipeRefreshViewRefreshing}">

    ...
</android.support.v4.widget.SwipeRefreshLayout>

最后我們還有一個(gè)小問題茸苇,就是雙向綁定有可能會出現(xiàn)死循環(huán)排苍,因?yàn)楫?dāng)你通過Listener反向設(shè)置數(shù)據(jù)時(shí),數(shù)據(jù)也會再次發(fā)送事件給View学密。所以我們需要在設(shè)置一下避免死循環(huán):

@BindingAdapter("refreshing")
public static void setRefreshing(SwipeRefreshLayout view, boolean refreshing) {
    if (refreshing != view.isRefreshing()) {
        view.setRefreshing(refreshing);
    }
}

這樣就沒問題啦淘衙。

帶ID的View

DataBinding有效降低了代碼的冗余性,甚至完全沒有必要再去獲取一個(gè)View實(shí)例腻暮,但是情況不是絕對的彤守,萬一我們真的就需要了呢?不用擔(dān)心哭靖,只要給View定義一個(gè) ID具垫,DataBinding就會為我們生成一個(gè)對應(yīng)的final變量。

<TextView
    android:id="@+id/firstName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

上面代碼中定義了一個(gè)ID為firstName*的TextView试幽,那么它對應(yīng)的變量就是筝蚕。

public final TextView firstName;

使用的時(shí)候用。

binding.firstName.setText("cc");

ViewStubs

xml中的ViewStub經(jīng)過 binding 之后會轉(zhuǎn)換成 ViewStubProxy。

簡單用代碼說明一下起宽,xml文件與之前的代碼一樣洲胖,根節(jié)點(diǎn)改為layout,在LinearLayout中添加一個(gè)ViewStub坯沪,添加ID绿映。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        ...>
        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub"
            ... />
    </LinearLayout>
</layout>

在Java代碼中獲取binding實(shí)例,為ViewStubProy注冊ViewStub.OnInflateListener事件腐晾,搞定叉弦!

binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        ViewStubBinding binding = DataBindingUtil.bind(inflated);
        User user = new User("fee", "lang");
        binding.setUser(user);
    }
});

Dynamic Variables

以RecyclerView為例,Adapter的DataBinding需要動態(tài)生成藻糖,因此我們可以在onCreateViewHolder的時(shí)候創(chuàng)建這個(gè)DataBinding卸奉,然后在onBindViewHolder中獲取這個(gè)DataBinding。

public static class BindingHolder extends RecyclerView.ViewHolder {
    private ViewDataBinding binding;
    public BindingHolder(View itemView) {
        super(itemView);
    }
    public ViewDataBinding getBinding() {
        return binding;
    }
    public void setBinding(ViewDataBinding binding) {
        this.binding = binding;
    }
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    ViewDataBinding binding = DataBindingUtil.inflate(
            LayoutInflater.from(viewGroup.getContext()),
            R.layout.list_item,
            viewGroup,
            false);
    BindingHolder holder = new BindingHolder(binding.getRoot());
    holder.setBinding(binding);
    return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
    User user = users.get(position);
    holder.getBinding().setVariable(BR.user, user);
    holder.getBinding().executePendingBindings();
}

注意此處DataBindingUtil的用法:

ViewDataBinding binding = DataBindingUtil.inflate(
    LayoutInflater.from(viewGroup.getContext()),
    R.layout.list_item,
    viewGroup,
    false);

Attribute setters

當(dāng)一個(gè)被綁定的數(shù)據(jù)的值發(fā)生改變時(shí)颖御,Binding類會自動尋找該view上的綁定表達(dá)式上的方法去改變view榄棵,通過google數(shù)據(jù)綁定框架我們可以去自定義這些方法。

對于一個(gè)xml的attribute潘拱,DataBinding會去尋找setAttribute方法疹鳄,xml屬性的命名空間是沒有關(guān)系的。比如TextView上的一個(gè)屬性android:text芦岂,會去尋找setText(String)瘪弓。如果表達(dá)式返回的是int則會去尋找setText(int),所以必須確保xml中表達(dá)式返回正確的數(shù)據(jù)類型禽最,必要時(shí)需要數(shù)據(jù)轉(zhuǎn)換腺怯。

我們可以比較容易地為任何屬性創(chuàng)造出setter去使用dataBinding。比如support包下的DrawerLayout沒有任何屬性川无,但是確有很多setter呛占,下面利用這些已有的setter中的一個(gè):

<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
自定義setters

一些xml屬性需要自己去定義并實(shí)現(xiàn)邏輯,比如android:paddingLeft懦趋。但是setPadding(left,top,right,bottom)是存在的晾虑,那么我們可以同BindingAdapter注解去自定義個(gè)自己的setter:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

Note:開發(fā)者自定義的BindingAdapter和android自帶的發(fā)生沖突時(shí),data bingding會優(yōu)先采用開發(fā)者自定義的仅叫。

多參數(shù)的BindingAdapter

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}

BindingAdpater方法可以對屬性的舊值和新值進(jìn)行處理

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

事件處理的列子

@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);
        }
    }
}

轉(zhuǎn)換器 (Converters)

有時(shí)候我們想這樣寫xml屬性帜篇。

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

但是xml屬性的setter是一個(gè)drawable,我們可以定義一個(gè)標(biāo)記了@BindingConversion的靜態(tài)方法即可诫咱。

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

由于這個(gè)注解至是發(fā)生在setter層面上笙隙,所以并不支持下面的混合寫法。

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

關(guān)于DataBinding的一些個(gè)人看法

DataBinding使用心得

  • 使用xml進(jìn)行view布局
  • 采用符合Java Bean規(guī)范的數(shù)據(jù)原型
  • 規(guī)范的自定義View
  • 禁止在BindingAdapter中的setter方法中改變數(shù)據(jù)或者做數(shù)據(jù)處理
  • 不建議用BindingConversion處理數(shù)據(jù)轉(zhuǎn)換
  • 不建議在xml布局中處理view事件
  • 不建議在xml中使用復(fù)雜的表達(dá)式

DataBinding使用的一些思考

DataBinding的不足之處:

  • DataBinding在xml提供了豐富的操作符坎缭,但是由于Android studio天生的xml語法檢查的貧弱竟痰,xml布局中的表達(dá)式邏輯錯(cuò)誤签钩,不能準(zhǔn)確定位,導(dǎo)致debug難度增加凯亮,事實(shí)上一些BindingAdapter的錯(cuò)誤在build的時(shí)候也會被提示xml錯(cuò)誤。
  • 對自定義view的要求比較高哄尔,需要自定義綁定方法假消,如BindingAdapter等。
  • 可能由于java8移除apt岭接,采用了新的API的緣故富拗,所以即使Android Studio2.2已經(jīng)開始支持java8特性,但是需要開啟jack編譯鏈鸣戴,DataBinding與之沖突啃沪,導(dǎo)致在代碼中不能使用lambda表達(dá)式等java8特性。值得欣慰的是窄锅,這一問題將在Android Studio2.4中得到解決创千。

PS:數(shù)據(jù)綁定的應(yīng)用軟件開發(fā)的一種趨勢,使用DataBinding的優(yōu)點(diǎn)顯而易見入偷,但是使用的時(shí)候我們也需要小心追驴。

更多內(nèi)容戳這里(整理好的各種文集)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市疏之,隨后出現(xiàn)的幾起案子殿雪,更是在濱河造成了極大的恐慌,老刑警劉巖锋爪,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丙曙,死亡現(xiàn)場離奇詭異,居然都是意外死亡其骄,警方通過查閱死者的電腦和手機(jī)亏镰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拯爽,“玉大人拆挥,你說我怎么就攤上這事∧匙ィ” “怎么了纸兔?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長否副。 經(jīng)常有香客問我汉矿,道長,這世上最難降的妖魔是什么备禀? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任洲拇,我火速辦了婚禮奈揍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赋续。我一直安慰自己男翰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布纽乱。 她就那樣靜靜地躺著蛾绎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸦列。 梳的紋絲不亂的頭發(fā)上租冠,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音薯嗤,去河邊找鬼顽爹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骆姐,可吹牛的內(nèi)容都是我干的镜粤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼玻褪,長吁一口氣:“原來是場噩夢啊……” “哼繁仁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起归园,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤黄虱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后庸诱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捻浦,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年桥爽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朱灿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钠四,死狀恐怖盗扒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缀去,我是刑警寧澤侣灶,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站缕碎,受9級特大地震影響褥影,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咏雌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一凡怎、第九天 我趴在偏房一處隱蔽的房頂上張望校焦。 院中可真熱鬧,春花似錦统倒、人聲如沸寨典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耸成。三九已至,卻和暖如春坛缕,著一層夾襖步出監(jiān)牢的瞬間墓猎,已是汗流浹背捆昏。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工赚楚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骗卜。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓宠页,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寇仓。 傳聞我的和親對象是個(gè)殘疾皇子举户,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容