MVVM架構—實現數據雙向綁定

1、MVVM

MVVM 模式,即指 Model-View-ViewModel。它將 View 的狀態和行為完全抽象化,把邏輯與界面的控制完全交給 ViewModel 處理


優點:
降低耦合:一個ViewModel層可以綁定不同的View層,當Model變化時View可以不變。
可重用性:可以把一些視圖邏輯放在ViewModel層中,讓很多View重用這些視圖邏輯。

2、關系流程圖


半自動生成,首先用戶自定義Model后,框架會為Model綁定ViewModel。

  • View:指的是layout文件,主要進行視圖控件的一些初始設置,不應該有任何的數據邏輯操作。
  • ViewModel:作為連接 View 與 Model 的中間重要橋梁,ViewModel 與 Model 直接交互,處理完業務邏輯后,通過 DataBinding 將數據變化反應到用戶界面上。(DataBinding會根據layout中的布局文件來進行數據的綁定并自動生成class文件)
  • Model:定義實體類,以及獲取業務數據模型,比如通過數據庫或者網絡來操作數據等。javaBean里面定義了ObservableField的屬性,ObservableField中提供了get和set方法,還有刷新屬性方法。

2、實踐

首先,介紹一下DataBinding 與 MVVM 之間的關系 。
MVVM是一種思想,一種架構模式,而DataBinding是谷歌推出的方便實現 MVVM 的工具。
在 DataBinding 庫之前,我們經常會寫一些重復性很高而且毫無營養的代碼,比如:findViewById()、setText()、setOnClickListener() 等。直到2015谷歌 I/O大會推出了 DataBinding,一個實現視圖和數據雙向綁定的工具。使用 DataBinding 庫以后,可以使用聲明式布局文件來減少粘結業務邏輯和布局文件的膠水代碼,有利于開發者更方便地實現 MVVM 模式。

那么怎么使用呢?

第一步:在 Module:app的build.gradle文件添加如下代碼:

android {
...
    // 添加DataBinding依賴
    dataBinding{
        enabled = true
    }
}

第二步:創建一個Java bean

public class UserInfo {
    // 被觀察的屬性(切記:必須是public修飾符,因為是DataBinding的規范)
    public ObservableField<String> name = new ObservableField<>();
    public ObservableField<String> pwd = new ObservableField<>();
}

第三步:修改布局文件和添加ViewModel文件

使用DataBinding的布局文件和普通的布局文件有點不同,DataBinding 布局文件的根標簽是layout標簽,layout里面有一個data元素和View元素,這個View元素就是我們沒使用DataBinding時候的布局文件。data元素里面配置variable信息。
使用格式:@{標識.成員屬性}


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 定義該布局需要綁定的數據名稱和類型 name可以理解key type可以理解為value -->
    <data>
        <variable
            name="loginViewModel"
            type="com.migill.mvvm.vm.LoginViewModel" />
    </data>
    <!-- 下部分內容和平時布局文件一樣 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <!--在loginViewModel中實現賬號名改變的監聽-->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:addTextChangedListener="@{loginViewModel.nameInputListener}"
            android:hint="請輸入賬戶"
            android:text="@{loginViewModel.userInfo.name}" />
        <!--在loginViewModel中實現密碼改變的監聽-->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:addTextChangedListener="@{loginViewModel.pwdInputListener}"
            android:hint="請輸入密碼"
            android:text="@{loginViewModel.userInfo.pwd}" />
        <!--在loginViewModel中實現點擊登錄的監聽-->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:onClick="@{loginViewModel.loginClickListener}"
            android:text="登錄" />
    </LinearLayout>
</layout>

根據上面的布局文件,在LoginViewModel中添加屬性和監聽方法,這個是個未完成的文件,還沒有具體的實現,等在Activity完成綁定后在添加具體的功能代碼

public class LoginViewModel {
    public UserInfo userInfo;
    public TextWatcher nameInputListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }
        @Override
        public void afterTextChanged(Editable s) {
        }
    };
    public TextWatcher pwdInputListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void afterTextChanged(Editable s) {
        }
    };
    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
        }
    };
}

第四步:ReBuilder和Activity中書寫代碼綁定

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 1、必須先ReBuilder,2、書寫代碼綁定
        ActivityMvvmLoginBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm_login);
        new LoginViewModel(binding);
    }
}

接下來就是要完成LoginViewModel文件了,將ViewModel和View進行綁定

public class LoginViewModel {
    public UserInfo userInfo;
    public LoginViewModel(ActivityMvvmLoginBinding binding) {
        userInfo = new UserInfo();
        // 將ViewModel和View進行綁定,通過DataBinding工具
        binding.setLoginViewModel(this);
    }
    public TextWatcher nameInputListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // View層接收到用戶的輸入,改變Model層的javabean屬性
            userInfo.name.set(String.valueOf(s));
        }
        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    public TextWatcher pwdInputListener = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            // View層接收到用戶的輸入,改變Model層的javabean屬性
            userInfo.pwd.set(String.valueOf(s));
        }
        @Override
        public void afterTextChanged(Editable s) {
        }
    };

    public View.OnClickListener loginClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 模擬網絡請求
            new Thread(new Runnable() {
                @Override
                public void run() {
                   // Model層屬性的變更,改變View層的顯示
                   //userInfo.name.set("yesterday");
                    SystemClock.sleep(2000);
                    if ("migill".equals(userInfo.name.get()) && "123".equals(userInfo.pwd.get())) {
                        Log.e("TAG", "登錄成功!");
                    } else {
                        Log.e("TAG", "登錄失敗!");
                    }
                }
            }).start();
        }
    };
}

MVVM的思想是可以雙向綁定,我們看運行結果

  • 打開注釋 userInfo.name.set("yesterday");
    其實這就是個雙向綁定,View層接收到用戶的輸入,該變了model層的javabean屬性的值。model層javabean屬性的值的變更,改變了View層的顯示。在判斷用戶名和密碼的時候發現不一樣,打印登錄失敗。
  • 注釋//userInfo.name.set("yesterday");
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。