即學(xué)即用Android Jetpack - Data Binding

前言

即學(xué)即用Android Jetpack系列Blog的目的是通過學(xué)習(xí)Android Jetpack完成一個簡單的Demo沽瞭,本文是即學(xué)即用Android Jetpack系列Blog的第二篇坛梁。

Google在2018年推出Android Jetpack憾儒,本人最近在學(xué)習(xí)Android Jetpack闸餐,如果你有研究過Android Jetpack,你會發(fā)現(xiàn)Livedata幻妓,ViewModelLivecycles等一系列Android Jetpack組件非常適用于實現(xiàn)MVVM仔引,因此鄙皇,在進行Android Jetpack的下一步研究之前先巴,我們有必要學(xué)習(xí)一下MVVM設(shè)計模式以及Android中實現(xiàn)MVVM的Data Binding組件其爵。

語言:kotlin
我的Demo:https://github.com/mCyp/Hoo

目錄

目錄

一、介紹

1. MVVM介紹

MVVM(全稱Model-View-ViewModel)同MVCMVP一樣伸蚯,是邏輯分層解偶的模式(如果你還不了解MVCMVP摩渺,建議還是提前了解一下)。

1.1 結(jié)構(gòu)圖
MVVM結(jié)構(gòu)圖

從上圖我們可以了解到MVVM的三要素朝卒,他們分別是:

  • View層:xml证逻、Activity乐埠、Fragment抗斤、Adapter和View等
  • Model層:數(shù)據(jù)源(本地數(shù)據(jù)和網(wǎng)絡(luò)數(shù)據(jù)等)
  • ViewModel層:View層處理數(shù)據(jù)以及邏輯處理

2. Data Binding介紹

Data Binding不算特別新的東西,2015年Google就推出了丈咐,但即便是現(xiàn)在瑞眼,很多人都沒有學(xué)習(xí)過它,我就是這些工程師中的一位棵逊,因為我覺得MVP已經(jīng)足夠幫我處理日常的業(yè)務(wù)伤疙,Android Jetpack的出現(xiàn)辆影,是我研究Data Binding的一個契機徒像。

在進行下文之前蛙讥,我有必要聲明一下旁涤,MVVMData Binding是兩個不同的概念菌羽,MVVM是一種架構(gòu)模式,而Data Binding是一個實現(xiàn)數(shù)據(jù)和UI綁定的框架注祖,是構(gòu)建MVVM模式的一個工具份蝴。

2.1 學(xué)習(xí)姿勢

我依然認為官方文檔是最好的學(xué)習(xí)途徑:
官方文檔:Data Binding Library
谷歌實驗室:官方教程
官方Demo地址:https://github.com/googlecodelabs/android-databinding

二、實戰(zhàn)

在這里氓轰,我打算先在上一節(jié)即學(xué)即用Android Jetpack - Navigation的基礎(chǔ)代碼上進行拓展(如有涉及到Navigation的代碼婚夫,我會注明),本文會在登錄和注冊模塊的基礎(chǔ)上進行講解署鸡,后期如有需要案糙,會拓展到其他模塊。

效果圖靴庆,和之前的有點不一樣:


Data Binding

第一步 在app模塊下的build.gradle文件添加內(nèi)容

android {
...
    dataBinding {
       enabled true
    }
}

第二步 構(gòu)建LoginModel

創(chuàng)建登錄的LoginModel时捌,LoginModel主要負責登錄邏輯的處理以及兩個輸入框內(nèi)容改變的時候數(shù)據(jù)更新的處理:

class LoginModel constructor(name: String, pwd: String, context: Context) {
    val n = ObservableField<String>(name)
    val p = ObservableField<String>(pwd)
    var context: Context = context

    /**
     * 用戶名改變回調(diào)的函數(shù)
     */
    fun onNameChanged(s: CharSequence) {
        n.set(s.toString())
    }

    /**
     * 密碼改變的回調(diào)函數(shù)
     */
    fun onPwdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
        p.set(s.toString())
    }

    fun login() {
        if (n.get().equals(BaseConstant.USER_NAME)
            && p.get().equals(BaseConstant.USER_PWD)
        ) {
            Toast.makeText(context, "賬號密碼正確", Toast.LENGTH_SHORT).show()
            val intent = Intent(context, MainActivity::class.java)
            context.startActivity(intent)
        }
    }
}

我相信同學(xué)們可能會對ObservableField存在疑惑,那么ObservableField是什么呢炉抒?它其實是一個可觀察的域奢讨,通過泛型來使用,可以使用的方法也就三個:

方法 作用
ObservableField(T value) 構(gòu)造函數(shù)焰薄,設(shè)置可觀察的域
T get() 獲取可觀察的域的內(nèi)容拿诸,可以使用UI控件監(jiān)測它的值
set(T value) 設(shè)置可觀察的域,設(shè)置成功之后塞茅,會通知UI控件進行更新

不過亩码,除了使用ObservableField之外,Data Binding為我們提供了基本類型的ObservableXXX(如ObservableInt)和存放容器的ObservableXXX(如ObservableList<T>)等野瘦,同樣描沟,如果你想讓你自定義的類變成可觀察狀態(tài),需要實現(xiàn)Observable接口鞭光。

我們再回頭看看LoginModel這個類吏廉,它其實只有分別用來觀察namepwd的成員變量np,外加一個處理登錄邏輯的方法惰许,非常簡單席覆。

第三步 創(chuàng)建布局文件

引入Data Binding之后的布局文件的使用方式會和以前的布局使用方式有很大的不同,且聽我一一解釋:

標簽名 作用
layout 用作布局的根節(jié)點啡省,只能包裹一個View標簽娜睛,且不能包裹merge標簽髓霞。
data Data Binding的數(shù)據(jù),只能存在一個data標簽畦戒。
variable data中使用方库,數(shù)據(jù)的變量標簽,type屬性指明變量的類障斋,如com.joe.jetpackdemo.viewmodel.LoginModel纵潦。name屬性指明變量的名字,方便布局中使用垃环。
import data中使用邀层,需要使用靜態(tài)方法和靜態(tài)常量,如需要使用View.Visble屬性的時候遂庄,則需導(dǎo)入<import type="android.view.View"/>寥院。type屬性指明類的路徑,如果兩個import標簽導(dǎo)入的類名相同涛目,則可以使用alias屬性聲明別名秸谢,使用的時候直接使用別名即可。
include View標簽中使用霹肝,作用同普通布局中的include一樣估蹄,需要使用bind:<參數(shù)名>傳遞參數(shù)

我們再看一下LoginFragment下的fragment_login.xml布局文件:

<?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">

    <data>
        <!--需要的viewModel,通過mBinding.vm=mViewMode注入-->
        <variable
            name="model"
            type="com.joe.jetpackdemo.viewmodel.LoginModel"/>

        <variable
            name="activity"
            type="androidx.fragment.app.FragmentActivity"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/txt_cancel"
            android:onClick="@{()-> activity.onBackPressed()}"
            />

        <TextView
            android:id="@+id/txt_title"
            app:layout_constraintTop_toTopOf="parent"
            .../>

        <EditText
            android:id="@+id/et_account"
            android:text="@{model.n.get()}"
            android:onTextChanged="@{(text, start, before, count)->model.onNameChanged(text)}"
            ...
            />

        <EditText
            android:id="@+id/et_pwd"
            android:text="@{model.p.get()}"
            android:onTextChanged="@{model::onPwdChanged}"
            ...
            />

        <Button
            android:id="@+id/btn_login"
            android:text="Sign in"
            android:onClick="@{() -> model.login()}"
            android:enabled="@{(model.p.get().isEmpty()||model.n.get().isEmpty()) ? false : true}"
            .../>


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

variable有兩個:

  • model:類型為com.joe.jetpackdemo.viewmodel.LoginModel,綁定用戶名詳見et_accountEditText中的android:text="@{model.n.get()}"沫换,當EditText輸入框內(nèi)容變化的時候有如下處理android:onTextChanged="@{(text, start, before, count)->model.onNameChanged(text)}"臭蚁,以及登錄按鈕處理android:onClick="@{() -> model.login()}"
  • activity:類型為androidx.fragment.app.FragmentActivity讯赏,主要用來返回按鈕的事件處理垮兑,詳見txt_cancelTextView的android:onClick="@{()-> activity.onBackPressed()}"

對于以上的內(nèi)容待逞,我仍然有知識點需要講解:

1. 屬性的引用

如果想使用ViewModel中成員變量甥角,如直接使用model.p

2. 事件綁定

事件綁定包括方法引用監(jiān)聽綁定

  • 方法引用:參數(shù)類型和返回類型要一致识樱,參考et_pwdEditText的android:onTextChanged引用。
  • 監(jiān)聽綁定:相比較于方法引用震束,監(jiān)聽綁定的要求就沒那么高了怜庸,我們可以使用自行定義的函數(shù),參考et_accountEditText的android:onTextChanged引用垢村。
3. 表達式

如果你注意到了btn_loginButton在密碼沒有內(nèi)容的時候是灰色的:

LoginFragment

是因為它在android:enabled使用了表達式:@{(model.p.get().isEmpty()||model.n.get().isEmpty()) ? false : true}割疾,它的意思是用戶名和密碼為空的時候登錄的enable屬性為false,這是普通的三元表達式嘉栓,除了上述的||和三元表達式之外宏榕,Data Binding還支持:

  • 運算符 + - / * %
  • 字符串連接 +
  • 邏輯與或 && ||
  • 二進制 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比較 == > < >= <= (Note that < needs to be escaped as <)
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • 方法調(diào)用
  • 域訪問
  • 數(shù)組訪問
  • 三元操作符

除了上述之外拓诸,Data Binding新增了空合并操作符??,例如android:text="@{user.displayName ?? user.lastName}"麻昼,它等價于android:text="@{user.displayName != null ? user.displayName : user.lastName}"奠支。

第四步 生成綁定類

我們的布局文件創(chuàng)建完畢之后,點擊Build下面的Make Project抚芦,讓系統(tǒng)幫我生成綁定類倍谜,生成綁定的類如下:

生成的綁定類

下面我們只需在LoginFragment完成綁定即可,綁定操作既可以使用上述生成的FragmentLoginBinding也可以使用自帶的DataBindingUtil完成:

1. 使用DataBindingUtil

我們可以看一下DataBindingUtil的一些常用Api:

函數(shù)名 作用
setContentView 用來進行Activity下面的綁定
inflate 用來進行Fragment下面的綁定
bind 用來進行View的綁定

LoginFragment綁定代碼如下:

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentLoginBinding = DataBindingUtil.inflate(
            inflater
            , R.layout.fragment_login
            , container
            , false
        )
        loginModel = LoginModel("","",context!!)
        binding.model = loginModel
        binding.activity = activity
        return binding.root
    }
2. 使用生成的FragmentLoginBinding

使用方法與第一種類似叉抡,僅需將生成方式改成val binding = FragmentLoginBinding.inflate( inflater , container , false )即可

運行一下代碼尔崔,開始圖的效果就出現(xiàn)了。

三褥民、更多

Data Binding還有一些有趣的功能季春,為了讓同學(xué)們了解到更多的知識,我們在這里有必要探討一下:

1. 布局中屬性的設(shè)置

1.1 有屬性有setter的情況

如果XXXView類有成員變量borderColor消返,并且XXXView類有setBoderColor(int color)方法鹤盒,那么在布局中我們就可以借助Data Binding直接使用app:borderColor這個屬性,不太明白侦副?沒關(guān)系侦锯,以DrawerLayout為例,DrawerLayout沒有聲明app:scrimColor秦驯、app:drawerListener尺碰,但是DrawerLayoutmScrimColor:intmListener:DrawerListener這兩個成員變量并且具有這兩個屬性的setter的方法译隘,他就可以直接使用app:scrimColor亲桥、app:drawerListener這兩個屬性,代碼如下:

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">
1.2 沒有setter但是有相關(guān)方法

還用XXXView為例固耘,它有成員變量borderColor题篷,這次設(shè)置borderColor的方法是setBColor(總有程序員亂寫方法名~),強行用app:borderColor顯然是行不通的厅目,可以這樣用的前提是必須有setBoderColor(int color)方法番枚,顯然setBColor不匹配,但我們可以通過BindingMethods注解實現(xiàn)app:borderColor的使用损敷,代碼如下:

@BindingMethods(value = [
    BindingMethod(
        type = 包名.XXXView::class,
        attribute = "app:borderColor",
        method = "setBColor")])
1.3 自定義屬性

這次不僅沒setter方法葫笼,甚至連成員變量都需要自帶(條件越來越刻苦~),這次我們的目標就是給EditText添加文本監(jiān)聽器拗馒,先在LoginModel中自定義一個監(jiān)聽器并使用@BindingAdapter注解:

    // SimpleWatcher 是簡化了的TextWatcher
    val nameWatcher = object : SimpleWatcher() {
        override fun afterTextChanged(s: Editable) {
            super.afterTextChanged(s)

            n.set(s.toString())
        }
    }

    @BindingAdapter("addTextChangedListener")
    fun addTextChangedListener(editText: EditText, simpleWatcher: SimpleWatcher) {
        editText.addTextChangedListener(simpleWatcher)
    }

這樣我們就可以在布局文件中對EditText愉快的使用app:addTextChangedListener屬性了:

        <EditText
            android:id="@+id/et_account"
            android:text="@{model.n.get()}"
            app:addTextChangedListener="@{model.nameWatcher}"
            ...
            />

效果與我們之前使用的時候一樣

2. 雙向綁定

使用雙向綁定可以簡化我們的代碼路星,比如我們上面的EditText在實現(xiàn)雙向綁定之后既不需要添加SimpleWatcher也不需要用方法調(diào)用,怎么實現(xiàn)呢诱桂?代碼如下:

        <EditText
            android:id="@+id/et_account"
            android:text="@={model.n.get()}"
            ...
            />

僅僅在將@{model.n.get()}替換為@={model.n.get()}洋丐,多了一個=號而已呈昔,需要注意的是,屬性必須是可觀察的友绝,可以使用上面提到的ObservableField堤尾,也可以自定義實現(xiàn)BaseObservable接口,雙向綁定的時候需要注意無限循環(huán)九榔,更多關(guān)于雙向綁定還請查看官方文檔哀峻。

四、總結(jié)

Data Binding總結(jié)

Data Binding的介紹可能沒有那么全面哲泊,基本使用沒什么問題了剩蟀,想要了解更多可以查看官方文檔呦~,本人水平有限切威,難免理解有誤差育特,歡迎指正。
Over~

參考文章:

《DataBinding最全使用說明》
《官方文檔》

??如果覺得本文不錯先朦,可以查看Android Jetpack系列的其他文章:

第一篇:《即學(xué)即用Android Jetpack - Navigation》
第三篇:《即學(xué)即用Android Jetpack - ViewModel & LiveData》
第四篇:《即學(xué)即用Android Jetpack - Room》
第五篇:《即學(xué)即用Android Jetpack - Paging》
第六篇:《即學(xué)即用Android Jetpack - WorkManger》
第七篇:《即學(xué)即用Android Jetpack - Startup》
第八篇:《即學(xué)即用Android Jetpack - Paging 3》
項目總結(jié)篇:《學(xué)習(xí)Android Jetpack? 實戰(zhàn)和教程這里全都有缰冤!》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喳魏,隨后出現(xiàn)的幾起案子棉浸,更是在濱河造成了極大的恐慌,老刑警劉巖刺彩,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迷郑,死亡現(xiàn)場離奇詭異,居然都是意外死亡创倔,警方通過查閱死者的電腦和手機嗡害,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畦攘,“玉大人霸妹,你說我怎么就攤上這事≈海” “怎么了叹螟?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朗徊。 經(jīng)常有香客問我首妖,道長,這世上最難降的妖魔是什么爷恳? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮象踊,結(jié)果婚禮上温亲,老公的妹妹穿的比我還像新娘棚壁。我一直安慰自己,他們只是感情好栈虚,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布袖外。 她就那樣靜靜地躺著,像睡著了一般魂务。 火紅的嫁衣襯著肌膚如雪曼验。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天粘姜,我揣著相機與錄音鬓照,去河邊找鬼。 笑死孤紧,一個胖子當著我的面吹牛豺裆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播号显,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼臭猜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了押蚤?” 一聲冷哼從身側(cè)響起蔑歌,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揽碘,沒想到半個月后次屠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡钾菊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年帅矗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煞烫。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡浑此,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滞详,到底是詐尸還是另有隱情凛俱,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布料饥,位于F島的核電站蒲犬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏岸啡。R本人自食惡果不足惜原叮,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奋隶,春花似錦擂送、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至境氢,卻和暖如春蟀拷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萍聊。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工问芬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脐区。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓愈诚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親牛隅。 傳聞我的和親對象是個殘疾皇子炕柔,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

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