MVVM架構(gòu)模式已經(jīng)非常火了,我還是只知其一不知其二,搜了很多帖子學(xué)習(xí),這里整理一下學(xué)習(xí)筆記。
a.便于理解,先把一些字面意思放在這里:
Model 模型(數(shù)據(jù)層)
是應(yīng)用程序中處理程序數(shù)據(jù)邏輯的部分,通常負(fù)責(zé)從本地?cái)?shù)據(jù)庫存取,遠(yuǎn)程存取數(shù)據(jù)。
View 視圖(UI顯示)
是應(yīng)用程序中處理數(shù)據(jù)顯示的部分,通常視圖都是依據(jù)模型來創(chuàng)建的。
Controller 控制者,管理者,調(diào)度員
在應(yīng)用程序中處理用戶交互的部分,通??刂破髫?fù)責(zé)從視圖讀取數(shù)據(jù),控制用戶輸入,并向模型發(fā)送數(shù)據(jù)。
Presenter 主持人
ViewModel 視圖模型
Repository 倉庫,存放處
1.MVC

MVC的模式優(yōu)點(diǎn)在于分離UI與業(yè)務(wù)職責(zé),增加可測(cè)試性與可擴(kuò)展性,但缺點(diǎn)是,View即依賴于Controller又依賴于Model,在修改UI內(nèi)容時(shí),也需要修改對(duì)應(yīng)的Model,降低架構(gòu)的靈活性,View和Model的職責(zé)存在部分重疊,耦合度高,實(shí)際操作中很難按照MVC模式嚴(yán)格去分離。
2.MVP

Model-View-Presenter ;MVP 是從經(jīng)典的模式MVC演變而來,它們的基本思想有相通的地方Controller/Presenter負(fù)責(zé)邏輯的處理,Model提供數(shù)據(jù),View負(fù)責(zé)顯示。
MVP模式的優(yōu)點(diǎn)是模型與視圖完全分離,我們可以修改視圖而不影響模型,因?yàn)樵谶@個(gè)模式下,所有的交互都發(fā)生在Presenter內(nèi)部,也利于脫離用戶接口來測(cè)試業(yè)務(wù)邏輯。缺點(diǎn)是會(huì)導(dǎo)致視圖和Presenter的交互過于頻分,Presenter與具體的View沒有直接關(guān)聯(lián),而是通過定義好的接口進(jìn)行交互,這會(huì)導(dǎo)致有大量的接口生成,造成代碼繁具,很難維護(hù),這也是很多像我一樣從入坑到放棄這種模式的原因。
3.MVVM

MVP的特質(zhì)是“依賴倒置”,MVVM的特質(zhì)是“數(shù)據(jù)驅(qū)動(dòng)”。
學(xué)這個(gè)的目的,是因?yàn)檫@句話,顯然說明二者并沒有誰演化自誰的關(guān)系了,JetPack MVVM是MVVM模式在Android開發(fā)中的具體落實(shí),“數(shù)據(jù)驅(qū)動(dòng)”這個(gè)理解有點(diǎn)難,看帖子不理解就找個(gè)教程寫個(gè)Demo理解一下,B站是個(gè)學(xué)習(xí)網(wǎng)站,很多優(yōu)質(zhì)UP主提供了幫助,這里貼一下一個(gè)小Demo:

一個(gè)計(jì)分器,點(diǎn)擊加分向?qū)?yīng)的隊(duì)加相應(yīng)的分?jǐn)?shù),點(diǎn)擊回退取消上一步的操作,點(diǎn)擊重置將兩隊(duì)的比分清零。
首先定義MyViewModel,繼承自ViewModel,在這里我們定義數(shù)據(jù),并處理邏輯。
public class MyViewModel extends ViewModel {
// 定義兩隊(duì)的分?jǐn)?shù)
// MutableLiveData可修改的LiveData,因?yàn)榉謹(jǐn)?shù)需要變化,LiveData
private MutableLiveData<Integer> aTeamScore;
private MutableLiveData<Integer> bTeamScore;
//緩存兩隊(duì)當(dāng)前分?jǐn)?shù),用于回退一步
private int aBack, bBack;
public MutableLiveData<Integer> getATeamScore() {
if (aTeamScore == null) {
aTeamScore = new MutableLiveData<>();
aTeamScore.setValue(0);
}
return aTeamScore;
}
public MutableLiveData<Integer> getBTeamScore() {
if (bTeamScore == null) {
bTeamScore = new MutableLiveData<>();
bTeamScore.setValue(0);
}
return bTeamScore;
}
/**
* 給A隊(duì)加分,在添加之前保留A、B兩隊(duì)的分?jǐn)?shù),用于在執(zhí)行undo操作時(shí)使用
* @param p 要加的分?jǐn)?shù)
*/
public void aTeamAdd(int p) {
aBack = aTeamScore.getValue();
bBack = bTeamScore.getValue();
aTeamScore.setValue(aTeamScore.getValue() + p);
}
public void bTeamAdd(int p) {
aBack = aTeamScore.getValue();
bBack = bTeamScore.getValue();
bTeamScore.setValue(bTeamScore.getValue() + p);
}
/**
* 清零
*/
public void reset() {
aBack = aTeamScore.getValue();
bBack = bTeamScore.getValue();
aTeamScore.setValue(0);
bTeamScore.setValue(0);
}
public void undo() {
aTeamScore.setValue(aBack);
bTeamScore.setValue(bBack);
}
}
布局文件的內(nèi)容除了常規(guī)控件的內(nèi)容,也就是使用了JetPack提供的DataBinding,篇幅有限這里僅僅對(duì)新生事物粘貼一下:
<?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>
<variable
name="data"
type="com.nxhope.community.state.MyViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.BasketballScoring">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="@{String.valueOf(data.getATeamScore())}"
android:textColor="#E91E63"
android:textSize="80sp"
app:layout_constraintBottom_toTopOf="@+id/guideline10"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline9"
tools:text="120" />
<Button
style="@style/button_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:onClick="@{()->data.aTeamAdd(1)}"
android:text="+1"
app:layout_constraintBottom_toTopOf="@+id/guideline11"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline10" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="60dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:onClick="@{()->data.undo()}"
app:layout_constraintBottom_toTopOf="@+id/guideline14"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline13"
app:srcCompat="@drawable/ic_baseline_undo_24" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
布局文件轉(zhuǎn)換為data-bingding layout這步可以不用手寫,windows用戶選中布局的根節(jié)點(diǎn)alt+enter就會(huì)提示出一個(gè)選項(xiàng)“convert to data binding layout" 的內(nèi)容, 轉(zhuǎn)換后會(huì)多出來<data>節(jié)點(diǎn)中的內(nèi)容,我們只需要name中命名,在type中導(dǎo)入我們定義好的ViewModel即可。
剩下的事情就是為控件綁定ViewModel提供的數(shù)據(jù)或者操作了,TextView中
android:text="@{String.valueOf(data.getATeamScore())}"為A隊(duì)分?jǐn)?shù)顯示區(qū)域綁定數(shù)據(jù),Button中
android:onClick="@{()->data.aTeamAdd(1)}"綁定了為A隊(duì)加1的操作,當(dāng)然加2加3就類似了,ImageButton綁定了我們?cè)赩iewModel中定義的undo()操作,也就是回退一步??梢钥吹?,到目前為止,我們好像只關(guān)心了布局文件和ViewModel的內(nèi)容,對(duì)于頁面Activity反而還沒下手,不像常規(guī)操作,之所以這樣,是因?yàn)锳ctiviy中真的沒啥可處理了,看代碼:
public class BasketballScoring extends AppCompatActivity {
private MyViewModel viewModel;
private ActivityBasketballScoringBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_basketball_scoring);
//ViewModel
viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class);
binding.setData(viewModel);
binding.setLifecycleOwner(this);
}
}
Activity中我們只是做了將布局和ViewModel綁定,當(dāng)然這里還寫了一些LiveData和Lifecycle的內(nèi)容(我還沒研究透徹,這里按下不表,聽下回分說),不是重點(diǎn),重點(diǎn)是,Activity中太簡(jiǎn)潔了,常見的FindViewById( ),setOnclickListener( ),setText( )統(tǒng)統(tǒng)不見蹤影,以及我們可能要定義的常量,Bean什么的也沒了,這就很香了。
玩玩這個(gè)Demo,比如刪除布局文件中的一些內(nèi)容,常規(guī)方式可能就會(huì)出現(xiàn)空指針,控件找不到造成的Crash,但現(xiàn)在不會(huì)了,MVVM模式用于解決“視圖調(diào)用一致性問題”這句話也誠不欺我,這時(shí)候再拿出KunMinX大佬的圖:

再品品,想總結(jié)些內(nèi)容,品出來的是:“我是小白,特別的白”、
帖子前面提到了Repository ,這里面并沒有說,本著的是不懂就不裝懂的原則,況且程序員有的東西懂了怎么用,不一定會(huì)表達(dá),繼續(xù)學(xué)習(xí),Android推出的JetPack內(nèi)容是時(shí)候?qū)W學(xué)了:
