Associate what you’ re trying to learn with what you already know. —— Daily English
這篇文章旨在通過一個Demo讓我們對Android中的MVVM架構進行初步的認識。
MVVM與DataBinding的關係
很多同學會將這兩者混爲一談,所以開始介紹之前,我們需要先理清楚這兩者的關係。
MVVM和MVC、MVP一樣,是項目中的架構設計思想;DataBinding是一種工具,它可以用於MVVM,也同樣可以用於MVC和MVP。所以這兩者是兩回事,一個是架構設計思想,一個是工具。但是有一點,那就是Android中的MVVM一般都需要藉助DataBinding來實現,這也是很多人將這兩者混爲一談的原因。
MVVM簡介
MVVM是更節省的設計模式,能實現雙向的數據綁定。
須知
MVVM可以理解成M V VM。其中的M指的是Model層,也就是我們的JavaBean。V指的是VIew層,也就是我們具體的佈局,如EditText等。VM指的是ViewModel層,它是Model層和View層的一個橋樑,也用來處理視圖邏輯和業務邏輯。
簡而言之,M還是Model,V還是View,VM就是ViewModel層。三者的關係大致如下圖所示:
這個架構模式有如下兩個特點
- 降低耦合:一個ViewModel層可以綁定不同的View層,當Model變化時View可以不變。
- 可重用性:可以把一些視圖邏輯放在ViewModel層中,讓很多View重用這些視圖邏輯。
ViewModel相當於model層和View層的一個橋樑,當View層比如說一個EditText的值發生改變了,無需通過Activity,就直接可以改變JavaBean對應的屬性值。Model層set一個值,也無需通過Activity,就可以直接改變頁面上的值。
MVVM是有弊端的,一個是修改之後需要經常ReBuild,而且項目越大,ReBuild的時間也越長。另外也有三個原因會導致它的內存消耗比較大,這個會在介紹DataBinding的時候講到(點擊查看)。這也是有些公司不願意用MVVM架構的原因。但是,MVVM爲什麼還會這麼火呢,就是因爲這種View和Model的雙向綁定思想是值得我們學習的,也很可能是一種趨勢。
什麼是單向數據綁定,什麼是雙向數據綁定。
單向綁定是指View層(如EditText)上的數據改變會實時更新到Model層JavaBean中對應的屬性值(如username)上。或者,Model層的數據改變會實時更新到View層上的顯示,這樣我們稱之爲單向的數據綁定。而雙向綁定呢,是指Model層和View層,無論那層數據改變都會實時更新到對方,Model層數據改變會更新View,同樣,View層數據改變會更新Model,這樣就稱之爲雙向的數據綁定。
項目實踐
模擬一個登錄的功能。
第一步,引入
在module的build.gradle文件中引入DataBinding
android {
...
// 添加DataBinding依賴
dataBinding{
enabled = true
}
)
第二步,定義實體類
public class UserInfo {
// 被觀察的屬性(切記:必須是public修飾符,因爲是DataBinding的規範)
public ObservableField<String> name = new ObservableField<>();
public ObservableField<String> pwd = new ObservableField<>();
}
實體類也可以定義成原始的那種格式,添加get(),set()方法,也可以定義成被觀察者屬性的格式。只要注意兩點,一個是被觀察者屬性,一個是有刷新屬性的方法。這裏就不做解釋,對格式有疑問的同學請參考我另一篇講DataBinding的文章(點擊查看)。
第三步,定義佈局
佈局界面很簡單
對應xml文件代碼如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--定義該佈局需要綁定的數據名稱和類型-->
<data>
<variable
name="loginViewModel"
type="pers.owen.my_mvvm.vm.LoginViewModel" />
</data>
<!-- 下部分內容和平時佈局文件一樣 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:hint="請輸入賬戶"
android:text="@={loginViewModel.userInfo.name}" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:hint="請輸入密碼"
android:text="@={loginViewModel.userInfo.pwd}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="@{loginViewModel.loginClickListener}"
android:text="登錄" />
</LinearLayout>
</layout>
佈局格式參考上述XML即可。
這裏我們發現<data>
中的variable
是一個ViewModel的名稱和具體路徑,當然也可以直接是一個實體類。這也就是我們爲什麼說DataBinding其實也可以用在MVC或MVP架構當中,因爲這篇文章講的是MVVM的架構,所以這裏定義一個專門處理登錄邏輯的ViewModel,叫做LoginViewModel
類。
其中如android:onClick="@{loginViewModel.loginClickListener}"
中loginViewModel
就是<data>
標籤下LoginViewModel的name
。可以把這裏的loginViewModel
理解成一個對象,而loginViewModel.loginClickListener
就是相當於調用這個對象中一個叫loginClickListener
的public方法。
LoginViewModel的方法隨即附上。
第四步,定義ViewModel
public class LoginViewModel {
public UserInfo userInfo;
public View.OnClickListener loginClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 嘗試1:view --> model單向綁定測試,改變EditText的值,查看bean中的對應屬性值是否發生變化。
// Log.e("owen >>> ", userInfo.name.get() + "--" + userInfo.pwd.get());
// 嘗試2:model --> view單向綁定測試,Model層屬性的變更,也會改變View層的顯示
// userInfo.name.set("Owen");
// userInfo.pwd.set("0410");
// 嘗試3:模擬網絡請求
new Thread(new Runnable() {
@Override
public void run() {
if ("Owen".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
Log.e("Owen >>> ", "登錄成功!");
} else {
Log.e("Owen >>> ", "登錄失敗!");
}
}
}).start();
}
};
}
注意LoginViewModel
類中的屬性名userInfo
和loginClickListener
是要和xml佈局中如android:onClick="@{loginViewModel.loginClickListener}"
的名稱對應上。可以通過ctrl+左鍵點擊跳轉驗證。
第五步,Rebuild Project
Rebuild完成後,會在data_binding_base_class_source_out
目錄下生成以[佈局名]Binding.java
文件,這裏我們的佈局叫activity_main
,所以生成了一個名爲ActivityMainBinding
的Java類文件。目錄的具體位置如下圖所示:
有了它,我們就可以做綁定操作了。
最後一步,書寫代碼綁定
在Activity中創建ActivityMainBinding對象
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1、必須先ReBuilder,2、書寫代碼綁定
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
new LoginViewModel(binding);
}
}
通過構造方法傳入LoginViewModel進行綁定
public LoginViewModel(ActivityMainBinding binding) {
userInfo = new UserInfo();
// 將ViewModel和View進行綁定,通過DataBinding工具。
binding.setLoginViewModel(this);
}
最後LoginViewModel的完整代碼如下:
public class LoginViewModel {
public UserInfo userInfo;
public LoginViewModel(ActivityMainBinding binding) {
userInfo = new UserInfo();
// 將ViewModel和View進行綁定,通過DataBinding工具。
binding.setLoginViewModel(this);
}
public View.OnClickListener loginClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// 嘗試1:view --> model單向綁定測試,改變EditText的值,查看bean中的對應屬性值是否發生變化。
// Log.e("owen >>> ", userInfo.name.get() + "--" + userInfo.pwd.get());
// 嘗試2:model --> view單向綁定測試,Model層屬性的變更,也會改變View層的顯示
// userInfo.name.set("Owen");
// userInfo.pwd.set("0410");
// 嘗試3:模擬網絡請求
new Thread(new Runnable() {
@Override
public void run() {
if ("Owen".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
Log.e("Owen >>> ", "登錄成功!");
} else {
Log.e("Owen >>> ", "登錄失敗!");
}
}
}).start();
}
};
}
嘗試代碼依次開啓,做測試
嘗試1:在界面上輸入用戶名,密碼,點擊登錄,查看log發現UserInfo中的兩個屬性值已經被改變。證明View --> Mode單向綁定是Ok的。
嘗試2:程序運行後,直接點擊登錄,發現界面上的EditText上的值已經更改。證明Model --> View的單向綁定也是成功的。
嘗試3:模擬網絡登錄。
這就是我們在業務中用到的MVVM,我們發現在Activity中什麼事情都不用幹,但是我們必須有一個VM來作爲V和M的橋樑來溝通。我們現在不需要在Activity中做很多複雜的東西,這就是架構MVVM的思想。你學到了嗎?
文中Demo下載地址。