MVVM實現數據雙向綁定

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層。三者的關係大致如下圖所示:
在這裏插入圖片描述
這個架構模式有如下兩個特點

  1. 降低耦合:一個ViewModel層可以綁定不同的View層,當Model變化時View可以不變。
  2. 可重用性:可以把一些視圖邏輯放在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類中的屬性名userInfologinClickListener是要和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下載地址

Android 架構設計模式系列文章索引

DataBinding的使用與原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章