Android MVVM 系列之 Databinding(一)

Android MVVM 系列之 Databinding(一)

寫(xiě)在最前,先借用前人的話講一下MVVM的概念:

Databinding 是一種框架,MVVM是一種架構(gòu),一種模式。DataBinding是一個(gè)實(shí)現(xiàn)數(shù)據(jù)和UI綁定的框架,是實(shí)現(xiàn)MVVM模式的工具,而MVVM中的VM(ViewModel)和View可以通過(guò)DataBinding來(lái)實(shí)現(xiàn)數(shù)據(jù)綁定(目前已支持雙向綁定)

關(guān)于MVVM的更詳細(xì)的介紹請(qǐng)看:

MVVM 是一種架構(gòu),DataBinDing 只是一個(gè)易于實(shí)現(xiàn)這種架構(gòu)的一個(gè)工具,網(wǎng)上 MVVM 的教程很多,但是成套的很少,大多講的都是 DataBinding 庫(kù)的使用方式,我這里會(huì)講從使用 DataBinding 庫(kù)到開(kāi)發(fā)一個(gè) MVVM 模式的程序。

[TOC]

一、在項(xiàng)目中添加 DataBinding 庫(kù)

1.開(kāi)發(fā)環(huán)境

Android studio 版本 1.3 以上,Gradle 插件 1.5 以上。

截至文章撰寫(xiě)之前,還在使用低于 2.3.3 版本的 studio 和 Gradle 就是耍流氓!

2.添加依賴

打開(kāi) model 的 build.gradle 文件,添加以下代碼

android {
    ...
    dataBinding {
        enabled = true
    }
}

這樣就可以在項(xiàng)目中使用 DataBinding 這個(gè)框架了。然后呢,這里還有一個(gè) Data Binding Compiler V2 這是 Google 在 Android Gradle Plugin 3.1.0 Canary 6 以后推出的一個(gè)新的編譯器,詳細(xì)請(qǐng)看 Google 文檔,開(kāi)啟只需要在 gradle.properties 文件中添加

android.databinding.enableV2=true

或者還可以通過(guò)添加以下參數(shù)在gradle命令中啟用新編譯器:

-Pandroid.databinding.enableV2=true

注意: V1 與 V2 不兼容,添加以后可能需要 clean 下項(xiàng)目

二、在項(xiàng)目中使用

1.替換舊的布局文件

第一步,我們需要把布局文件的根節(jié)點(diǎn)變?yōu)?layout 節(jié)點(diǎn)

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.xxj.mvvm.demo.android_mvvm_demo.MainActivity">

        <TextView...>

    </android.support.constraint.ConstraintLayout>
</layout>

我去,難道每個(gè)布局我都得這么寫(xiě)么?當(dāng)然,一開(kāi)始我確實(shí)是這樣來(lái)的,但我發(fā)現(xiàn)其實(shí) Android studio 有自帶的快捷轉(zhuǎn)換功能,只要在根布局 Alt+Enter 就可以一鍵轉(zhuǎn)換

image

額,看了我的文章自然不能讓你們空手而歸,繼續(xù)安利一發(fā)插件 DataBinding Support 咳咳,插件怎么裝不用我說(shuō)了吧。。。

第二步,修改 Activity 中的代碼

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        /*
         使用 DataBinding 布局后這里需要 DataBindingUtil 來(lái)完成 setContentView
         返回值是布局的 ViewBinding 對(duì)象,
         這個(gè)類是 DataBinding 框架自動(dòng)生成,類文件在 app/build/intermediates/classes/yourpackage/databinding
         修改布局后如果發(fā)現(xiàn) ViewBinding 類沒(méi)有及時(shí)生成,工具欄找小錘子 Make Project 一下就好
        */
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    }
}

在 Fragment 中

public class MainFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //DataBinding 布局是可以使用下面兩種方式,inflate 出來(lái)的。
//        FragmentMainBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);
        FragmentMainBinding binding = FragmentMainBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }
}

然后試著運(yùn)行一下吧,Hello World 應(yīng)該成功跑起來(lái)了

2.簡(jiǎn)單數(shù)據(jù)綁定

布局是修改完成了,接下來(lái)我們就要綁定(Binding)數(shù)據(jù)(Data)了。

第一步,我們先搞個(gè)實(shí)體類出來(lái)

public class User {
    private String name;
    private int age;

    ...getter and setters...
}

第二步,我們需要吧這個(gè)類放到我們的布局內(nèi),控件使用綁定的數(shù)據(jù)很簡(jiǎn)單,使用 @{} 就可以了(雙向綁定使用 @={} 后面講)

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

    <!-- data 標(biāo)簽里面的就是要 Binding 的數(shù)據(jù)
         如果你覺(jué)得自動(dòng)生成的類名不爽的話,可以在 data 標(biāo)簽內(nèi)加上 class 屬性,如:
         <data class=".MyBinding">
         這樣的話DataBinding生成的類名就是你想要的了
    -->
    <data>
        <!-- 數(shù)據(jù)對(duì)象,name 是變量名,type 是類的全路徑 -->
        <variable
            name="user"
            type="com.xxj.mvvm.demo.android_mvvm_demo.User" />
    </data>
    
    <!-- 為了方便展示,我把根布局換了 -->
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 這里的 user.name 調(diào)用的實(shí)際上是 User 類中的 getName() 方法,如果沒(méi)有對(duì)應(yīng)的 get 方法,就會(huì)報(bào)錯(cuò) -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <!-- 我們知道 TextView 的內(nèi)容必須是 String 類型的,這里傳入 int 會(huì)報(bào)錯(cuò)
             java.lang 包下的類不需要導(dǎo)入 -->
        <TextView
            android:text="@{String.valueOf(user.age)}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

注意:注意看代碼中的注釋!?。∥也冗^(guò)的坑你們就別再掉下去了QAQ

第三步,在 Activity 中給實(shí)體類賦值

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User();
        user.setName("張三");
        user.setAge(88);
        //這里可能會(huì)有好事之徒說(shuō),我不傳 user 呢?我不給他某個(gè)參數(shù)賦值呢?
        //答:DataBinding不懼怕空指針異常,若表達(dá)式結(jié)果為null,則根據(jù)其結(jié)果的值類型顯示不同,比如引用類型顯示null,int類型顯示0,string類型顯示空
        binding.setUser(user);
    }

結(jié)果肯定是成功的,我就不給大家展示了,下面開(kāi)始講事件綁定,嗯,比較多,但是得看全了。

3.表達(dá)式使用

關(guān)鍵字

類似于 @{String.valueOf(user.age)} 您也可以在表達(dá)式中以下的運(yùn)算符和關(guān)鍵字:

  • 運(yùn)算類 + - / * % && || & | ^ ! ~ == > < >= <= () >> >>> <<
  • 字符串連接 + (注意字符串要用``括起來(lái))
  • instanceof
  • 文字 - 字符,字符串,數(shù)字, null
  • 強(qiáng)轉(zhuǎn) cast
  • 方法調(diào)用
  • res 資源訪問(wèn)
  • 數(shù)組訪問(wèn) []
  • 三目運(yùn)算 ?:
  • 合并運(yùn)算 ??

例子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{user.age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

不支持的操作:

  • this
  • super
  • new
  • 顯式泛型調(diào)用

合并運(yùn)算:
null 合并運(yùn)算符(??)選擇左邊的是 null 嗎?,不是選左邊,如果是選右邊。

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

在功能上等同于:

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

集合

DataBinding 中可以使用操作符訪問(wèn)常見(jiàn)集合,例如 []、List、Map等

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}" //注意:這里也可以換成 @{map.key}

字符串

您可以使用單引號(hào)將屬性值包圍起來(lái),這樣可以在表達(dá)式中使用雙引號(hào),例:

android:text='@{map["firstName"]}'

也可以使用雙引號(hào)來(lái)包圍屬性值,字符串用后引號(hào)括起來(lái):

android:text="@{map[`firstName`]}"

注意: 拼接字符串的時(shí)候需要單引號(hào)包圍屬性值,拼接字符串用雙引號(hào)包圍,拼接的字符串不要使用中文 ?。?!

Resources

您可以使用以下語(yǔ)法訪問(wèn)表達(dá)式中的資源:

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

格式字符串和復(fù)數(shù)可以通過(guò)提供參數(shù)來(lái)使用:

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

當(dāng)一個(gè)復(fù)數(shù)需要多個(gè)參數(shù)時(shí),應(yīng)該傳遞所有參數(shù):

  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

某些資源需要使用顯式類型,如下表所示:

類型 正常引用 表達(dá)式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

4.事件綁定(Event Handling)

看了這么久,終于來(lái)到了重頭戲?。](méi)有事件綁定的 DataBinding 是沒(méi)有靈魂的。

事件處理 Google 給出了以下兩種方式:

方法引用(Method references)

事件可以直接綁定到處理程序方法,類似于android:onClick 可以分配給 Activity 中的方法的方式(android:onClick="onClick",這種的)。與View onClick屬性相比,一個(gè)主要優(yōu)點(diǎn) 是表達(dá)式在編譯時(shí)處理,所以如果該方法不存在或其簽名不正確,則會(huì)收到編譯時(shí)錯(cuò)誤。

方法引用和偵聽(tīng)器綁定之間的主要區(qū)別在于實(shí)際的偵聽(tīng)器實(shí)現(xiàn)是在數(shù)據(jù)綁定時(shí)創(chuàng)建的,而不是在事件觸發(fā)時(shí)創(chuàng)建的。如果您希望在事件發(fā)生時(shí)檢查表達(dá)式,則應(yīng)使用偵聽(tīng)器綁定。

話不多說(shuō),例子如下:

寫(xiě)個(gè)事件出來(lái)先

public class EventHandler {
    public void onUserClick(View view) {
        Toast.makeText(Utils.getContext(), "User is click !", Toast.LENGTH_LONG).show();
    }
}
<data>
    ...
    <variable
        name="event"
        type="com.xxj.mvvm.demo.android_mvvm_demo.EventHandler" />
</data>
    ...
    <TextView
        ...
        android:onClick="@{event::onUserClick}"
        android:text='@{user.name+"abc"}' />

最后別忘了把事件綁到布局中,好多人忘了這一步

binding.setEvent(new EventHandler());

點(diǎn)擊名字效果如圖
[圖片上傳失敗...(image-18b3e1-1527072014457)]

監(jiān)聽(tīng)器綁定(Listener bindings)

監(jiān)聽(tīng)器綁定是發(fā)生事件時(shí)運(yùn)行的綁定表達(dá)式。它們與方法引用類似,但它們?cè)试S您運(yùn)行任意數(shù)據(jù)綁定表達(dá)式。此功能適用于Gradle 2.0版及更高版本的Android Gradle插件。

在方法引用中,方法的參數(shù)必須與事件偵聽(tīng)器的參數(shù)匹配。在偵聽(tīng)器綁定中,只有您的返回值必須與偵聽(tīng)器的期望返回值相匹配(除非它為void)。例:

咱在剛剛的 EventHandler 類中加一個(gè)方法

public void onUserClick1(User user){
    Toast.makeText(Utils.getContext(), "User is click !"+user.getName(), Toast.LENGTH_LONG).show();
}

我們把這個(gè)事件加到 demo 中用戶年齡上

<TextView
    ...
    android:onClick="@{() -> event.onUserClick1(user)}"
    android:text="@{String.valueOf(user.age)}" />

在上面的例子中,我們沒(méi)有定義view傳遞給的參數(shù)onClick(View)。監(jiān)聽(tīng)器綁定為監(jiān)聽(tīng)器參數(shù)提供了兩種選擇:您可以忽略該方法的所有參數(shù)或命名所有參數(shù)。如果您更喜歡命名參數(shù),則可以在表達(dá)式中使用它們。例如,上面的表達(dá)式可以寫(xiě)成如下形式:

android:onClick="@{(view) -> event.onUserClick1(user)"

或者如果你想在表達(dá)式中使用參數(shù),可以用以下方法:

public void onUserClick2(View view, User user){
    view.setBackgroundColor(Color.BLUE);
    Toast.makeText(Utils.getContext(), "User is click !"+user.getName(), Toast.LENGTH_LONG).show();
}
<TextView
    android:layout_width="200dp"
    android:layout_height="50dp"
    android:gravity="center"
    android:onClick="@{(v) -> event.onUserClick2(v, user)}"
    android:text="@{user.sex}" />

效果如圖:

[圖片上傳失敗...(image-585c4b-1527072014457)]

你也可以使用帶有多個(gè)參數(shù)的lambda表達(dá)式:

public void onCompletedChanged(Task task, boolean completed){ ... }
<CheckBox
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

如果您正在偵聽(tīng)的事件返回其類型不是 Void,則您的表達(dá)式也必須返回相同類型的值。例如,如果您想要監(jiān)聽(tīng)長(zhǎng)按事件,則應(yīng)該返回表達(dá)式boolean。

public boolean onLongClick(View view, Task task){ ... }
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

如果您需要使用帶判斷的表達(dá)式(例如三元),可以將 void 用作符號(hào)。

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

注意: 要避免使用復(fù)雜的表達(dá)式,應(yīng)該把邏輯盡量的寫(xiě)到 Java 代碼中

5.標(biāo)簽的使用(include,merge)

額,import、variables、data 都在最上面講過(guò)了,剩下的

include

使用 include 標(biāo)簽時(shí),外層布局時(shí)可以對(duì)內(nèi)層布局傳遞變量的

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

merge 標(biāo)簽,不支持,會(huì) Boom !

demo傳送門(mén)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一個(gè)剛?cè)胄邪肽甑牟锁B(niǎo)安卓開(kāi)發(fā)人員,始終有一顆不安分的心。mvvm框架是我在學(xué)習(xí)vue的時(shí)候才知道的一種新型架構(gòu)。公...
    sakasa閱讀 4,358評(píng)論 5 22
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 先吐槽下,不說(shuō)不爽,不說(shuō)不通達(dá) 不吐不快,集合我這幾天學(xué)習(xí) DataBinding 的經(jīng)歷說(shuō)幾句。DataBind...
    前行的烏龜閱讀 21,398評(píng)論 13 46
  • DataBinding 庫(kù)是 Google 公司 Android Framework UI 工具團(tuán)隊(duì)開(kāi)發(fā)出來(lái)的一款...
    bravian閱讀 5,516評(píng)論 2 16
  • 小時(shí)候和母親一起回娘家。那時(shí)我初中畢業(yè),12歲。路上母親遇到一位故人,不知談了些什么,我不明白,但我記住了一個(gè)女人...
    疏桐流響閱讀 242評(píng)論 0 0

友情鏈接更多精彩內(nèi)容