Android MVVM框架

一首涼涼送給自己,心累。
首先借鑑一下別人寫的關於MVP的優缺點。。。

一、MVP模式優缺點

在說MVVM之前,簡單回顧一下MVP分層,MVP總共分成三層:

a 、View: 視圖層,對應xml文件與Activity/Fragment;
b 、Presenter: 邏輯控制層,同時持有View和Model對象;
c 、Model: 實體層,負責獲取實體數據。

MVP模式有其很大的優點

1.解耦合,業務邏輯和視圖分離;
2.項目代碼結構(文件夾)清晰,一看就知道什麼類幹什麼事情;
3.便於單元測試(其實還是第一點);
4.協同工作(例如在設計師沒出圖之前可以先寫一些業務邏輯代碼或者其他人接手代碼改起來比較容易);

但是也有美中不足的部分,MVP模式的缺點如下:

1.Presente層與View層是通過接口進行交互的,接口粒度不好控制。粒度太小,就會存在大量接口的情況,使代碼太過碎版化;粒度太大,解耦效果不好。因爲View定義的方法並不一定全部要用到,可能只是後面要用到先定義出來(後面要不要刪也未知),而且如果後面有些方法要刪改,Presenter和Activity都要刪改,比較麻煩;

2.V層與P層還是有一定的耦合度。一旦V層某個UI元素更改,那麼對應的接口就必須得改,數據如何映射到UI上、事件監聽接口這些都需要轉變,牽一髮而動全身。如果這一層也能解耦就更好了。

3.複雜的業務同時也可能會導致P層太大,代碼臃腫的問題依然不能解決,這已經不是接口粒度把控的問題了,一旦業務邏輯越來越多,View定義的方法越來越多,會造成Activity和Fragment實現的方法越來越多,依然臃腫。

目前MVP模式已經用的很多了,大部分接觸到的項目都是MVP,自己也寫了很多MVP模式下的項目。一直都知道有MVVM這個框架,只是一直沒有機會用,所以就自己學習一下。網上看了很多,都是簡單的例子,所以在github上找了一個項目直接拿來練手了。

二、MVVM模式

OK,現在開始介紹MVVM,MVVM模式不是四層,同MVP一樣也是三層,但是我不同意MVVM是MVP的升級版,二者有相同的地方,但是MVP的一些優點,MVVM也無法取代,MVVM的三層模型如下:

View
View層做的就是和UI相關的工作,我們只在XML和Activity或Fragment寫View層的代碼,View層不做和業務相關的事,也就是我們的Activity 不寫和業務邏輯相關代碼,也不寫需要根據業務邏輯來更新UI的代碼,因爲更新UI通過Binding實現,更新UI在ViewModel裏面做(更新綁定的數據源即可),Activity 要做的事就是初始化一些控件(如控件的顏色,添加 RecyclerView 的分割線),Activity可以更新UI,但是更新的UI必須和業務邏輯和數據是沒有關係的,只是單純的根據點擊或者滑動等事件更新UI(如 根據滑動顏色漸變、根據點擊隱藏等單純UI邏輯),Activity(View層)是可以處理UI事件,但是處理的只是處理UI自己的事情,View層只處理View層的事。簡單的說:View層不做任何業務邏輯、不涉及操作數據、不處理數據、UI和數據嚴格的分開。

ViewModel
ViewModel層做的事情剛好和View層相反,ViewModel 只做和業務邏輯和業務數據相關的事,不做任何和UI、控件相關的事,ViewModel 層不會持有任何控件的引用,更不會在ViewModel中通過UI控件的引用去做更新UI的事情。ViewModel就是專注於業務的邏輯處理,操作的也都是對數據進行操作,這些個數據源綁定在相應的控件上會自動去更改UI,開發者不需要關心更新UI的事情。DataBinding 框架已經支持雙向綁定,這使得我們在可以通過雙向綁定獲取View層反饋給ViewModel層的數據,並進行操作。關於對UI控件事件的處理,我們也希望能把這些事件處理綁定到控件上,並把這些事件統一化,方便ViewModel對事件的處理和代碼的美觀。爲此我們通過BindingAdapter 對一些常用的事件做了封裝,把一個個事件封裝成一個個Command,對於每個事件我們用一個ReplyCommand去處理就行了,ReplyCommand會把可能你需要的數據帶給你,這使得我們處理事件的時候也只關心處理數據就行了,具體見MVVM Light Toolkit 使用指南的 Command 部分。再強調一遍ViewModel 不做和UI相關的事。

Model
Model 的職責很簡單,基本就是實體模型(Bean)同時包括Retrofit 的Service ,ViewModel 可以根據Model 獲取一個Bean的Observable( RxJava ),然後做一些數據轉換操作和映射到ViewModel 中的一些字段,最後把這些字段綁定到View層上。

可以看到,MVVM模式的最大亮點是雙向綁定

單向綁定上,數據的流向是單方面的,只能從代碼流向UI;雙向綁定的數據流向是雙向的,當業務代碼中的數據改變時,UI上的數據能夠得到刷新;當用戶通過UI交互編輯了數據時,數據的變化也能自動的更新到業務代碼中的數據上。對於雙向綁定,剛好可以使用DataBinding,DataBinding是一個實現數據和UI綁定的框架,是構建MVVM模式的一個關鍵的工具。所以Android中實現MVVM就方便多了,IOS中還要使用block回調,或者使用reactiveCocoa庫。

三:MVVM體驗:

1.配置:
在項目app中的build.gradle的android{}中加入:

dataBinding {
        enabled = true
}

2.創建Model實體類:
實體類的創建和平時的沒什麼區別,這裏取了個用戶類做例子:

public class User implements Parcelable {
    public long id;
    public String name;
    public String url;
    public String email;
    public String login;
    public String location;
    @SerializedName("avatar_url")
    public String avatarUrl;

    public User() {
    }

    public boolean hasEmail() {
        return email != null && !email.isEmpty();
    }

    public boolean hasLocation() {
        return location != null && !location.isEmpty();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(this.id);
        dest.writeString(this.name);
        dest.writeString(this.url);
        dest.writeString(this.email);
        dest.writeString(this.login);
        dest.writeString(this.location);
        dest.writeString(this.avatarUrl);
    }

    protected User(Parcel in) {
        this.id = in.readLong();
        this.name = in.readString();
        this.url = in.readString();
        this.email = in.readString();
        this.login = in.readString();
        this.location = in.readString();
        this.avatarUrl = in.readString();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        public User createFromParcel(Parcel source) {
            return new User(source);
        }

        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        User user = (User) o;

        if (id != user.id) return false;
        if (name != null ? !name.equals(user.name) : user.name != null) return false;
        if (url != null ? !url.equals(user.url) : user.url != null) return false;
        if (email != null ? !email.equals(user.email) : user.email != null) return false;
        if (login != null ? !login.equals(user.login) : user.login != null) return false;
        if (location != null ? !location.equals(user.location) : user.location != null)
            return false;
        return !(avatarUrl != null ? !avatarUrl.equals(user.avatarUrl) : user.avatarUrl != null);

    }

    @Override
    public int hashCode() {
        int result = (int) (id ^ (id >>> 32));
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (url != null ? url.hashCode() : 0);
        result = 31 * result + (email != null ? email.hashCode() : 0);
        result = 31 * result + (login != null ? login.hashCode() : 0);
        result = 31 * result + (location != null ? location.hashCode() : 0);
        result = 31 * result + (avatarUrl != null ? avatarUrl.hashCode() : 0);
        return result;
    }

3.創建佈局:
和傳統佈局不同的是,這裏的根佈局是用layout來包裹:

<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">

    //佈局

</layout>

如果需要數據綁定,頭部是這樣的:

<data>

        <variable
            name="viewModel"
            type="com.ckw.zfsoft.mvvmdemo.viewmodel.MainViewModel"/>

    </data>

name可以隨便取,type是你要用到的viewModel

使用DataBinding後,佈局都是以標籤作爲根節點,這個佈局 最終會生成一個Binding類,命名規則是:單詞首字母大寫,移除下劃線,並在最後添加上Binding。我這裏是activity_main.xml,所以生成的是ActivityMainBinding。

4.viewModel:
例如在xml中有一個控件是這樣的:

<ProgressBar
            android:id="@+id/progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/layout_search"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="20dp"
            app:visibility="@{viewModel.progressVisibility}"
            />

關鍵代碼在這一行:

app:visibility="@{viewModel.progressVisibility}"

我們的viewModel中就可以定義一個參數,用於控制progress的可見性:

 private ObservableInt progressVisibility;

初始設置爲不可見:

progressVisibility = new ObservableInt(View.INVISIBLE);

設置可見:

 progressVisibility.set(View.VISIBLE);

又比如,有個EditTextView:

 <EditText
                android:id="@+id/edit_text_username"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_toLeftOf="@id/button_search"
                android:hint="@string/hit_username"
                android:imeOptions="actionSearch"
                android:inputType="text"
                android:textColor="@color/white"
                android:theme="@style/LightEditText"
                android:onEditorAction="@{viewModel.onSearchAction}"
                app:addTextChangedListener="@{viewModel.usernameEditTextWatcher}"
               />

設置監聽:

android:onEditorAction="@{viewModel.onSearchAction}"

同樣的,viewModel中需要寫一個public方法:

public boolean onSearchAction(TextView view, int actionId, KeyEvent event) {
        if (actionId == EditorInfo.IME_ACTION_SEARCH) {
            String username = view.getText().toString();
            if (username.length() > 0) loadGithubRepos(username);
            return true;
        }
        return false;
    }

這裏的參數爲什麼要這麼寫呢?
對比一下我們正常的監聽寫法就知道啦:

EditText editText = new EditText(getApplicationContext());
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        return false;
    }
});

5.綁定:
最後我們還需要在Activity中對其進行綁定:

private ActivityMainBinding binding;
private MainViewModel mainViewModel;

在oncreate方法中:

binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mainViewModel = new MainViewModel(this, this);
binding.setViewModel(mainViewModel);

大體上就是這麼使用的,科科

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