Android框架設(shè)計(jì)之MVVM

一西土、簡(jiǎn)介

1陆错、定義

MVVM 模式(Model--View--ViewModel 模式)

  • Model:模型層,負(fù)責(zé)處理數(shù)據(jù)的加載或存儲(chǔ)剪侮。與MVP中的M一樣拭宁。
  • View:視圖層,對(duì)應(yīng)于Activity票彪,XML,View,負(fù)責(zé)界面數(shù)據(jù)的展示不狮,與用戶進(jìn)行交互降铸。與MVP中的V一樣。
  • ViewModel:視圖模型摇零,負(fù)責(zé)完成View于Model間的綁定和交互,Model或者View更改時(shí)推掸,實(shí)時(shí)刷新對(duì)方,負(fù)責(zé)業(yè)務(wù)邏輯驻仅。

2谅畅、詳情

和 MVP 模式相比,MVVM 模式用ViewModel 替換了 Presenter 噪服,其他層基本上與 MVP 模式一致毡泻,ViewModel 可以理解成是 View 的數(shù)據(jù)模型和 Presenter 的合體。

MVVM 采用雙向綁定(data-binding):View 的變動(dòng)粘优,自動(dòng)反映在 ViewModel仇味,反之亦然,
這種模式實(shí)際上是框架替應(yīng)用開發(fā)者做了一些工作(相當(dāng)于 ViewModel 類是由庫(kù)幫我們生
成的)雹顺,開發(fā)者只需要較少的代碼就能實(shí)現(xiàn)比較復(fù)雜的交互丹墨。

3、各模塊關(guān)系

MVVM 的調(diào)用關(guān)系和 MVP 一樣嬉愧。但是贩挣,在 ViewModel 當(dāng)中會(huì)有一個(gè)叫 Binder,或者是Data-binding engine 的東西。以前全部由 Presenter 負(fù)責(zé)的 View 和 Model 之間數(shù)據(jù)同步操作交由給 Binder 處理王财。你只需要在 View 的模版語(yǔ)法當(dāng)中卵迂,指令式地聲明 View 上的顯示的內(nèi)容是和 Model 的哪一塊數(shù)據(jù)綁定的。當(dāng) ViewModel 對(duì)進(jìn)行 Model 更新的時(shí)候搪搏,Binder會(huì)自動(dòng)把數(shù)據(jù)更新到 View 上去狭握,當(dāng)用戶對(duì) View 進(jìn)行操作(例如表單輸入),Binder 也會(huì)自動(dòng)把數(shù)據(jù)更新到 Model 上去疯溺。這種方式稱為:Two-way data-binding论颅,雙向數(shù)據(jù)綁定〈涯郏可以簡(jiǎn)單而不恰當(dāng)?shù)乩斫鉃橐粋€(gè)模版引擎恃疯,但是會(huì)根據(jù)數(shù)據(jù)變更實(shí)時(shí)渲染。

二墨闲、MVVM案例

此案例基于MVP的案例,改為MVVM模式今妄。其中ViewModel會(huì)跟View進(jìn)行綁定,這里會(huì)用到Android的 Data Binding鸳碧,關(guān)于DataBinding可以參考此文

首先在需要用到DataBinding模塊的build.gradle中添加以下兩句配置(kotlin需要加第一句盾鳞,Java不需要)


image.png
  • Model層

data class UserModel(var userName:String){

    fun login(result: Int, listener: ModelCallBack){
        if (result == 1){
            listener.onSuccess("${userName}登錄成功")
        }else{
            listener.onFailed("${userName}登錄失敗")
        }
    }

    //回調(diào)接口
    interface ModelCallBack{
        fun onSuccess(msg:String)
        fun onFailed(msg:String)
    }
}
  • View層

將根布局修改為layout,加入 Data Binding瞻离。
注意:class名為xml名去掉_后的駝峰寫法腾仅,即此xml為activity_main.xml,則其類名則需要為ActivityMainBinding套利。兩者名字必須是這個(gè)規(guī)則的寫法推励,否則在View層進(jìn)行xml綁定時(shí)會(huì)顯示找不到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">
    <!--class名為xml名去掉_后的駝峰寫法-->
    <data class="ActivityMainBinding">
        <variable
            name="userViewModel"
            type="yzl.swu.mvvm.viewModel.UserViewModel" />
    </data>


    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={userViewModel.userName}"
            android:textSize="50sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.35" />

        <Button
            android:id="@+id/loginBtn"
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_marginTop="30dp"
            android:text="登錄"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView"
            android:onClick="@{userViewModel.loginUser}"/>
    </androidx.constraintlayout.widget.ConstraintLayout>


</layout>

build一下生成需要的類


再到MainActivity中進(jìn)行View和ViewModel的綁定

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //DataBindingUtil.setContentView()方法返回一個(gè)數(shù)據(jù)綁定對(duì)象,其命名規(guī)則由系統(tǒng)自動(dòng)生成
        DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main).apply {
            //將View和Model進(jìn)行綁定
            val userModel = UserViewModel()
            userViewModel = userModel
        }

    }
}
  • ViewModel層

ViewModel負(fù)責(zé)業(yè)務(wù)邏輯處理肉迫,并且數(shù)據(jù)有更新直接通知View去更改验辞。

class UserViewModel: BaseObservable() {
    lateinit var userModel:UserModel
    var userName:String? = null
    //該注解用于雙向綁定,需要與 notifyPropertyChanged()方法結(jié)合使用
    //該注解用于標(biāo)記實(shí)體類中的get方法或“is”開頭的方法,且實(shí)體類必須繼承BaseObservable
    //使用@Bindable注解標(biāo)記的get方法喊衫,在編譯時(shí)跌造,會(huì)在BR類中生成對(duì)應(yīng)的字段,然后與notifyPropertyChanged()方法配合使用族购,當(dāng)該字段中的數(shù)據(jù)被修改時(shí)鼻听,dataBinding會(huì)自動(dòng)刷新對(duì)應(yīng)view的數(shù)據(jù)
        @Bindable
        get() {
            if (field != null) return field
            return null
        }
        set(value) {
            field = value
            //更新UI
            notifyPropertyChanged(BR.userName)
        }



    fun loginUser(view:View){
        userModel = UserModel("用戶${(0..9).random()}")
        //點(diǎn)擊事件的處理 直接交給Model層
        userModel.login((0..1).random(),object:UserModel.ModelCallBack{
            override fun onSuccess(msg: String) {
                userName = msg
            }

            override fun onFailed(msg: String) {
                userName = msg
            }

        })
    }
}

運(yùn)行結(jié)果

mvvm-gif.gif

三、總結(jié)

MVP和MVVM的區(qū)別

ViewModel與View綁定后联四,ViewModel與View其中一方的數(shù)據(jù)更新都能立即通知到對(duì)方撑碴,Presenter需要通過接口去通知View進(jìn)行更新。

關(guān)鍵點(diǎn)

MVVM 把 View 和 Model 的同步邏輯自動(dòng)化了朝墩。以前 Presenter 負(fù)責(zé)的 View 和 Model 同步不再手動(dòng)地進(jìn)行操作醉拓,而是交由框架所提供的 Binder 進(jìn)行負(fù)責(zé)伟姐。
只需要告訴 Binder,View 顯示的數(shù)據(jù)對(duì)應(yīng)的是 Model 哪一部分即可亿卤。

MVVM的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 提高可維護(hù)性愤兵。解決了 MVP 大量的手動(dòng) View 和 Model同步的問題,提供雙向綁定機(jī)制排吴。提高了代碼的可維護(hù)性秆乳。
  • 簡(jiǎn)化測(cè)試。因?yàn)橥竭壿嬍墙挥?Binder 做的钻哩,View 跟著 Model 同時(shí)變更屹堰,所以只需要保證 Model 的正確性,View就正確街氢。大大減少了對(duì) View 同步更新的測(cè)試扯键。

缺點(diǎn)

  • 過于簡(jiǎn)單的圖形界面不適用,或說牛刀殺雞珊肃。
  • 對(duì)于大型的圖形應(yīng)用程序荣刑,視圖狀態(tài)較多,ViewModel的構(gòu)建和維護(hù)的成本都會(huì)比較高伦乔。
  • 數(shù)據(jù)綁定的聲明是指令式地寫在 View 的模版當(dāng)中的厉亏,這些內(nèi)容是沒辦法去打斷點(diǎn) debug 的。

結(jié)語(yǔ)

可以看到烈和,從 MVC->MVP->MVVM爱只,就像一個(gè)打怪升級(jí)的過程。后者解決了前者遺留的問題斥杜,把前者的缺點(diǎn)優(yōu)化成了優(yōu)點(diǎn)虱颗。同樣的 Demo 功能沥匈,代碼從最開始的一堆文件蔗喂,優(yōu)化成了最后只需要 20 幾行代碼就完成。
但也不是說MVVM一定比其他兩種模式要好高帖,在實(shí)際中因根據(jù)各模式的優(yōu)缺點(diǎn)及需求選擇缰儿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市散址,隨后出現(xiàn)的幾起案子乖阵,更是在濱河造成了極大的恐慌,老刑警劉巖预麸,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞪浸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吏祸,警方通過查閱死者的電腦和手機(jī)对蒲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹈矮,你說我怎么就攤上這事砰逻。” “怎么了泛鸟?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵蝠咆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我北滥,道長(zhǎng)刚操,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任碑韵,我火速辦了婚禮赡茸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祝闻。我一直安慰自己占卧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布联喘。 她就那樣靜靜地躺著华蜒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豁遭。 梳的紋絲不亂的頭發(fā)上叭喜,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音蓖谢,去河邊找鬼捂蕴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛闪幽,可吹牛的內(nèi)容都是我干的啥辨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盯腌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼溉知!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腕够,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤级乍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后帚湘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體玫荣,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年大诸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捅厂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片材诽。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恒傻,靈堂內(nèi)的尸體忽然破棺而出脸侥,到底是詐尸還是另有隱情,我是刑警寧澤盈厘,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布睁枕,位于F島的核電站,受9級(jí)特大地震影響沸手,放射性物質(zhì)發(fā)生泄漏外遇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一契吉、第九天 我趴在偏房一處隱蔽的房頂上張望跳仿。 院中可真熱鬧,春花似錦捐晶、人聲如沸菲语。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)山上。三九已至,卻和暖如春英支,著一層夾襖步出監(jiān)牢的瞬間佩憾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工干花, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妄帘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓池凄,卻偏偏與公主長(zhǎng)得像抡驼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子修赞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354