一西土、簡(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不需要)
-
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é)果
三、總結(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)及需求選擇缰儿。