MVVM模式將應(yīng)用分為三層:
Model層:主要負(fù)責(zé)數(shù)據(jù)的提供靡狞。Model層提供業(yè)務(wù)邏輯的數(shù)據(jù)結(jié)構(gòu)(比如续搀,實(shí)體類),提供數(shù)據(jù)的獲取(比如募判,從本地?cái)?shù)據(jù)庫或者遠(yuǎn)程網(wǎng)絡(luò)獲取數(shù)據(jù)),提供數(shù)據(jù)的存儲(chǔ)俊庇。
View層:主要負(fù)責(zé)界面的顯示吧享。View層不涉及任何的業(yè)務(wù)邏輯處理,它持有ViewModel層的引用庭呜,當(dāng)需要進(jìn)行業(yè)務(wù)邏輯處理時(shí)通知ViewModel層滑进。
ViewModel層:主要負(fù)責(zé)業(yè)務(wù)邏輯的處理。ViewModel層不涉及任何的視圖操作募谎。通過官方提供的Data Binding庫扶关,View層和ViewModel層中的數(shù)據(jù)可以實(shí)現(xiàn)綁定,ViewModel層中數(shù)據(jù)的變化可以自動(dòng)通知View層進(jìn)行更新近哟,因此ViewModel層不需要持有View層的引用驮审。ViewModel層可以看作是View層的數(shù)據(jù)模型和Presenter層的結(jié)合。
MVVM模式最大的優(yōu)勢在于:ViewModel層不持有View層的引用吉执。這樣進(jìn)一步降低了耦合疯淫,View層代碼的改變不會(huì)影響到ViewModel層。當(dāng)View層發(fā)生改變時(shí)戳玫,只要View層綁定的數(shù)據(jù)不變熙掺,那么ViewModel層就不需要改變。不用再編寫很多樣板代碼咕宿。通過官方的Data Binding庫币绩,UI和數(shù)據(jù)之間可以實(shí)現(xiàn)綁定蜡秽,不用再編寫大量的findViewById()和操作視圖的代碼了。極大的提高了開發(fā)過程中在UI界面數(shù)據(jù)綁定上的開發(fā)效率缆镣!
首先我們從最基礎(chǔ)的MVVM構(gòu)架開始入手芽突,暫且稱為MVVM入門級版本,主要是圍繞Databinding和ViewMode來封裝實(shí)現(xiàn)董瞻,拋棄掉煩不勝煩的findViewById以及通過ButterKnife方式獲取view操作的方式寞蚌。
第一步,創(chuàng)建一個(gè)空白的工程钠糊,入門級的我們先把工程內(nèi)分好包結(jié)構(gòu):
- activity層或者fragment層用于存放Activity或者Fragment挟秤;
- vm層用于存放ViewModel;
- presenter層用于存放數(shù)據(jù)操作和網(wǎng)絡(luò)交互邏輯抄伍;
不要忘了開啟databinding功能艘刚,這樣我們就能通過關(guān)聯(lián)databinding少些一大片的view代碼,在工程Module:app的gradle文件下:
android {
//... 省略一大堆默認(rèn)配置
dataBinding {
enabled = true
}
}
開啟databinding功能以后截珍,回到我們創(chuàng)建的layout文件攀甚,默認(rèn)的是這樣的:
直接選中根目錄,alt+emter或者直接鼠標(biāo)點(diǎn)擊提示燈泡笛臣,選擇第一個(gè)Convert to data binding layout云稚,AS就會(huì)自動(dòng)將布局文件轉(zhuǎn)換成我們需要的databinding結(jié)構(gòu):
<?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:我們這里直接和剛創(chuàng)建的MainVM綁定起來-->
<variable
name="viewModel"
type="com.jf.mvvm.vm.MainVM" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<TextView
android:id="@+id/txv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.title}"
tools:text="txv_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txv_title2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.title2}"
tools:text="txv_title2"
app:layout_constraintTop_toBottomOf="@+id/txv_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="20dp"/>
<Button
android:id="@+id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/txv_title2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="20dp"
android:onClick="@{viewModel::onTestClick}"
android:text="測試一下"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
附上我們初級的VM文件,就是一個(gè)普通的java類沈堡,里面提供了一個(gè)getter方法静陈,默認(rèn)情況下databinding是可以直接獲取VM文件中的getter以及public的屬性值的:
public class MainVM{
//通過MainPresenter可以跟數(shù)據(jù)庫以及后臺做通訊操作,用于更新UI數(shù)據(jù)
private MainPresenter mainPresenter = new MainPresenter();
// 布局文件可以直接讀取該屬性值
public String title2 = "這里的值也可以顯示";
public String getTitle(){
return "這是textView顯示的內(nèi)容";
}
public void onTestClick(View view){
Log.d("mvvm","onTestClick!!!!");
}
}
為了關(guān)聯(lián)VM和layout文件诞丽,我們需要在MainActivity中調(diào)用DataBindingUtil.setContentView()方法鲸拥,并綁定MainVM到布局文件中,F(xiàn)ragment可以通過DataBindingUtil.inflate(inflater, layoutRes, container,false);來進(jìn)行頁面布局綁定:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.setViewModel(new MainVM());
}
運(yùn)行APP工程僧免,我們就能看到我們初步完成的基于Databinding的MVVM工程結(jié)果了:
點(diǎn)擊按鈕我們后臺也看到了日志:com.jf.mvvm D/mvvm: onTestClick!!!!
我們再更新一下代碼刑赶,將VM中的點(diǎn)擊操作和數(shù)據(jù)聯(lián)動(dòng)起來:
public class MainVM{
public String title2 = "這里的值也可以顯示";
private MainPresenter mainPresenter = new MainPresenter();
public String getTitle(){
if(!"這里的值也可以顯示".equals(title2)){
return "我的值也一起變吧!";
}
return "這是textView顯示的內(nèi)容";
}
public void onTestClick(View view){
title2 = "我的值改變了懂衩!";
Log.d("mvvm","onTestClick >>> "+title2 + " >>> "+getTitle());
}
}
點(diǎn)擊以后撞叨,頁面沒有發(fā)生任何事情,但是我們的日志里記錄的值是成功更新了的:
onTestClick >>> 我的值改變了浊洞! >>> 我的值也一起變吧牵敷!
了解過Databinding使用以后,我們知道法希,如果要更新值并同步到UI里枷餐,我們需要這樣做,將VM文件繼承androidx.databinding.BaseObservable苫亦,并且在需要通知的綁定數(shù)據(jù)上加上@Bindable毛肋,這樣我們就可以通過調(diào)用notifyPropertyChanged(BR.title);來通知UI刷新對應(yīng)的屬性值(title為目標(biāo)屬性值):
public class MainVM extends BaseObservable {
//通過MainPresenter可以跟數(shù)據(jù)庫以及后臺做通訊操作怨咪,用于更新UI數(shù)據(jù)
private MainPresenter mainPresenter = new MainPresenter();
// 布局文件可以直接讀取該屬性值
@Bindable
public String title2 = "這里的值也可以顯示";
@Bindable
public String getTitle(){
if(!"這里的值也可以顯示".equals(title2)){
return "我的值也一起變吧!";
}
return "這是textView顯示的內(nèi)容";
}
public void onTestClick(View view){
title2 = "我的值改變了润匙!";
Log.d("mvvm","onTestClick >>> "+title2 + " >>> "+getTitle());
notifyPropertyChanged(BR.title);
notifyPropertyChanged(BR.title2);
}
}
完成上述改進(jìn)以后诗眨,我們再次運(yùn)行APP,點(diǎn)擊測試一下按鈕趁桃,會(huì)發(fā)現(xiàn)頁面展示的值已經(jīng)成功按我們預(yù)想的值改變了辽话!
到目前為止,我們最初級最簡單的MVVM模式就搭建完畢了卫病!
操作過程中關(guān)于Databinding的使用可以參考別人已經(jīng)寫得很詳細(xì)得文章:DataBinding 的簡單使用