MVVM架構(gòu)—實(shí)現(xiàn)數(shù)據(jù)雙向綁定

1、MVVM

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


優(yōu)點(diǎn):
降低耦合:一個(gè)ViewModel層可以綁定不同的View層,當(dāng)Model變化時(shí)View可以不變。
可重用性:可以把一些視圖邏輯放在ViewModel層中,讓很多View重用這些視圖邏輯。

2、關(guān)系流程圖


半自動(dòng)生成,首先用戶自定義Model后,框架會(huì)為Model綁定ViewModel。

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

2、實(shí)踐

首先,介紹一下DataBinding 與 MVVM 之間的關(guān)系 。
MVVM是一種思想,一種架構(gòu)模式,而DataBinding是谷歌推出的方便實(shí)現(xiàn) MVVM 的工具。
在 DataBinding 庫(kù)之前,我們經(jīng)常會(huì)寫(xiě)一些重復(fù)性很高而且毫無(wú)營(yíng)養(yǎng)的代碼,比如:findViewById()、setText()、setOnClickListener() 等。直到2015谷歌 I/O大會(huì)推出了 DataBinding,一個(gè)實(shí)現(xiàn)視圖和數(shù)據(jù)雙向綁定的工具。使用 DataBinding 庫(kù)以后,可以使用聲明式布局文件來(lái)減少粘結(jié)業(yè)務(wù)邏輯和布局文件的膠水代碼,有利于開(kāi)發(fā)者更方便地實(shí)現(xiàn) MVVM 模式。

那么怎么使用呢?

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

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

第二步:創(chuàng)建一個(gè)Java bean

public class UserInfo {
    // 被觀察的屬性(切記:必須是public修飾符,因?yàn)槭荄ataBinding的規(guī)范)
    public ObservableField<String> name = new ObservableField<>();
    public ObservableField<String> pwd = new ObservableField<>();
}

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

使用DataBinding的布局文件和普通的布局文件有點(diǎn)不同,DataBinding 布局文件的根標(biāo)簽是layout標(biāo)簽,layout里面有一個(gè)data元素和View元素,這個(gè)View元素就是我們沒(méi)使用DataBinding時(shí)候的布局文件。data元素里面配置variable信息。
使用格式:@{標(biāo)識(shí).成員屬性}


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

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

根據(jù)上面的布局文件,在LoginViewModel中添加屬性和監(jiān)聽(tīng)方法,這個(gè)是個(gè)未完成的文件,還沒(méi)有具體的實(shí)現(xiàn),等在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中書(shū)寫(xiě)代碼綁定

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

接下來(lái)就是要完成LoginViewModel文件了,將ViewModel和View進(jìn)行綁定

public class LoginViewModel {
    public UserInfo userInfo;
    public LoginViewModel(ActivityMvvmLoginBinding binding) {
        userInfo = new UserInfo();
        // 將ViewModel和View進(jìn)行綁定,通過(guò)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) {
            // 模擬網(wǎng)絡(luò)請(qǐng)求
            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的思想是可以雙向綁定,我們看運(yùn)行結(jié)果

  • 打開(kāi)注釋 userInfo.name.set("yesterday");
    其實(shí)這就是個(gè)雙向綁定,View層接收到用戶的輸入,該變了model層的javabean屬性的值。model層javabean屬性的值的變更,改變了View層的顯示。在判斷用戶名和密碼的時(shí)候發(fā)現(xiàn)不一樣,打印登錄失敗。
  • 注釋//userInfo.name.set("yesterday");
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容