用代碼手把手教你使用MVVM

源碼請點擊:github.com/shuaijia/Js…android

您還能夠關注個人微信公衆號——安卓乾貨營,與我交流和獲取更多精彩內容。git

概述

說到Android MVVM,就會聯想到DataBinding框架。然而二者的概念是不同的,不能混爲一談。MVVM是一種架構模式,而DataBinding是一個實現數據和UI綁定的框架,是構建MVVM模式的一個工具。github

網上關於MVVM框架的搭建和使用的文章不多,大多提到MVVM框架,就是在介紹DataBinding的使用。對於MVVM中各模塊之間如何劃分,如何定義,又是如何配合實現高度解耦的文章更是少之又少。你們看完後仍是一頭霧水,只是對MVVM有個大概的瞭解,並不很清楚如何上手。數據庫

接下來,咱們先認識什麼是MVVM,而後再一步一步來設計整個MVVM框架。json

MVC、MVP簡介

MVC、MVP和MVVM都是在安卓開發中常用的模式,咱們在認識MVVM以前先回顧一下MVC和MVP。bash

MVC

  • View:xml佈局
  • Model:數據層,負責數據交互、存儲和實體類定義
  • Controller:業務處理層

Android開發自己仍是比較符合MVC架構的,可是Android中純粹做爲View的XML視圖功能太弱,咱們大量處理View的邏輯只能寫在Activity中,這樣Activity就充當了View和Controller兩個角色,直接致使Activity中的代碼臃腫、混亂,致使閱讀困難、重用困難和維護困難。相信大多數Android開發者都遇到過一個Acitivty數以千行的代碼狀況吧!因此,更貼切的說法是,這個MVC結構最終其實只是一個Model-View(Activity:View&Controller)的結構。服務器

MVP

  • View:xml文件及對應的Activity或Fragment,負責界面展現和交互
  • Model:數據層,負責數據交互、存儲和實體類定義
  • Presenter:負責View層和Model層之間的邏輯處理

前面咱們說,Activity充當了View和Controller兩個角色,MVP就能很好地解決這個問題,其核心理念是經過一個抽象的View接口(不是真正的View層)將Presenter與真正的View層進行解耦。Persenter持有該View接口,對該接口進行操做,而不是直接操做View層。這樣就能夠把視圖操做和業務邏輯解耦,從而讓Activity成爲真正的View層。微信

不足的是,MVP模式中定義了大量的接口,使得代碼結構變大和複雜;MVP是UI和事件驅動,須要手動調用大量的方法來進行實現,缺少自動性。網絡

因此咱們迎來了MVVM框架,固然得首先感謝google爸爸提供得DataBinding,真的是很強大!架構

MVVM簡介

這裏寫圖片描述

在MVVM模式中,將程序結構分爲三層——View-ViewModel-Model,接下來咱們一塊兒來認識它們:

View:

View層負責和UI相關的工做,咱們只在XML、Activity和Fragment寫View層的代碼,View層不進行業務處理,也就是咱們在Activity不寫業務邏輯和業務數據相關的代碼。

更新UI經過數據綁定實現,儘可能在ViewModel裏面作,Activity要作的事就是初始化一些控件(如RecyclerView設置LayoutManager或者控件的顯隱),View層能夠經過數據來驅動更改UI,UI事件經過Command來綁定。

簡而言之:View層不作任何業務邏輯、不涉及操做數據,UI和數據嚴格的分開。 **UI更新和事件相應所有使用數據綁定,也就是DataBinding來實現。**這就是MVVM和MVP、MVC很明顯的不一樣之處。

ViewModel

ViewModel層作的事情恰好和View層相反,ViewModel只負責業務邏輯,不作任何和UI相關的事情。

同時DataBinding框架已經支持雙向綁定,讓咱們能夠經過雙向綁定獲取View層反饋給ViewModel層的數據,並對這些數據上進行操做。

事件的處理,咱們也但願能把這些事件處理綁定到控件上,並把這些事件的處理統一化,爲此咱們經過使用BindingAdapter對一些經常使用的事件作封裝,把一個個事件封裝成一個個Command,對於每一個事件咱們用一個ReplyCommand去處理就好了,ReplyCommand會把你可能須要的數據帶給你,這使得咱們在ViewModel層處理事件的時候只須要關心處理數據就好了,具體見MVVM Light Toolkit 使用指南的Command部分。

Model

Model層不只包括實體類的定義,還須要對數據進行處理和讀寫。例如:使用Retrofit或okHttp進行網絡請求,或着如數據庫操做等等。

優勢:

  • 數據驅動
  • 低耦合
  • 主線程更新UI
  • 可複用性
  • 方便單元測試

咱們再來看下這張圖:

這裏寫圖片描述

簡述下數據流走向:

View中使用DataBinding的Command來綁定事件和響應事件,觸發網絡請求;ViewModel進行分析處理,調用Model的數據請求方法;Model將收到的請求參數等信息封裝,調用網絡請求庫;網絡庫(Retrofit等)與服務器進行交互;

服務器將json數據返回Retrofit等網絡庫,再返回到Model層中,ViewModel在回調中收到返回的實體類對象;

由於xml與實體類對象實現了雙向綁定,實體類更新,使得UI更新!

ok!接下來咱們就用活生生的例子來實現MVVM吧

A、實體類定義

/**
 * Description:
 * Created by jia on 2017/11/3.
 * 人之因此能,是相信能
 */
public class NewslistBean extends BaseObservable {
 
    private String ctime;
    private String title;
    private String description;
    private String picUrl;
    private String url;

    public NewslistBean(String ctime, String title, String description, String picUrl, String url) {
        this.ctime = ctime;
        this.title = title;
        this.description = description;
        this.picUrl = picUrl;
        this.url = url;
    }

    public String getCtime() {
        return ctime;
    }

    public void setCtime(String ctime) {
        this.ctime = ctime;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getPicUrl() {
        return picUrl;
    }

    public void setPicUrl(String picUrl) {
        this.picUrl = picUrl;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    @BindingAdapter("bind:imageUrl")
    public static void loadImage(ImageView imageView, String picUrl) {
        Glide.with(imageView.getContext())
                .load(picUrl)
                .into(imageView);
    }

    public void onItemClick(View pView) {
        Toast.makeText(pView.getContext(), title, Toast.LENGTH_SHORT).show();
    }

}
複製代碼

這和平時寫的實體類是否是沒啥區別!

是的,全部的屬性咱們依舊如原來原來同樣定義和設置get、set方法。可是,有一點不一樣的是實體類繼承了BaseObservable,稍後咱們再說。

B、Model類

/**
 * Description: 新聞網絡請求model
 * Created by jia on 2017/11/3.
 * 人之因此能,是相信能
 */
public class NewsModel {

    public void getNews(final OnCallBack onCallBack) {
        HttpMethod.getInstance().getNews(new Subscriber<News>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                onCallBack.onFaile(e.toString());
            }

            @Override
            public void onNext(News news) {
                onCallBack.onSuccess(news);
            }
        });
    }


    public interface OnCallBack {
        void onSuccess(News news);

        void onFaile(String errorInfo);
    }
}
複製代碼

這裏呢,我使用的是本身封裝過的Retrofit+RxJava的網絡請求庫,上面的Model用來進行新聞實體類News的網絡請求;

也定義了一個CallBack接口:此回調可讓接下的ViewModel得到Model請求回來的實體類。

每一個項目的網絡請求庫和方法都會不一樣,符合本身的就是最好的!(●ˇ∀ˇ●)

C、View

xml中

先看示例:

<?xml version="1.0" encoding="utf-8"?>
<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">

    <data>

        <variable
            name="news"
            type="com.jia.jsmvvm.home.viewmodel.NewslistBean" />

    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".home.view.MainActivity">

        <android.support.v7.widget.CardView
            android:id="@+id/cv_tuijian"
            android:layout_width="match_parent"
            android:layout_height="130dp"
            android:layout_margin="15dp"
            android:background="#ffffff"
            android:elevation="5dp"
            android:onClick="@{news.onItemClick}"
            app:cardElevation="5dp">

            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="5dp">

                <ImageView
                    android:id="@+id/iv_tuijian"
                    android:layout_width="120dp"
                    android:layout_height="match_parent"
                    android:layout_margin="15dp"
                    android:scaleType="fitXY"
                    app:imageUrl="@{news.picUrl}" />

                <TextView
                    android:id="@+id/tv_tuijian_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignTop="@id/iv_tuijian"
                    android:layout_toRightOf="@id/iv_tuijian"
                    android:ellipsize="end"
                    android:maxLines="2"
                    android:text="@{news.title}"
                    android:textColor="#111111"
                    android:textSize="18sp" />

                <TextView
                    android:id="@+id/tv_tuijian_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentBottom="true"
                    android:layout_alignParentRight="true"
                    android:ellipsize="end"
                    android:lines="3"
                    android:singleLine="true"
                    android:text="@{news.ctime}"
                    android:textColor="#777777"
                    android:textSize="14sp" />
            </RelativeLayout>
        </android.support.v7.widget.CardView>


    </RelativeLayout>
</layout>
複製代碼

你們可定已經發現了:佈局的編寫和往常比仍是又較大變化的。

熟悉DataBinding的朋友能夠直接跳過這趴。因爲本人對DataBinding也不是特別熟練,因此也只能和你們分享本身瞭解的一點使用方法。DataBinding擁有很是強大的功能,想深刻了解的能夠網上搜索,固然,本人不久也會把本身瞭解的DataBinding的知識整理成一篇博客,敬請期待!

  1. 咱們使用 layout 做爲佈局文件的跟節點
  2. layout中包含data節點和普通的佈局
  3. data節點中建立variable
  4. variable中有兩個「屬性」:name和type
  5. type聲明實體類,格式爲 包名.類名
  6. name爲type中的實體類定義「名字」,供如下佈局中使用
  7. 定義了data屬性後,就至關於xml佈局已和實體類綁定
  8. 在控件中引用實體類屬性的格式爲: @{實體類.屬性名}
  9. 在控件中引用實體類方法的格式爲: @{實體類.方法名}
  10. 涉及到圖片加載:在實體類中使用@BindingAdapter註解圖偏加載方法,在佈局中引用url便可

由於本篇文章重點在於講述MVVM框架的使用,因此DataBinding只進行粗略簡介,若有錯誤,還望你們及時提出,咱們一塊兒進步!

Activity中

在Activity中設置佈局,咱們再也不使用Activity的setContentView方法,取而代之的是:DataBindingUtil.setContentView

ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
複製代碼

所返回的變量類型怎麼來的呢?

將使用了DataBinding的佈局名字,去掉全部下劃線,將全部單詞首字母大寫,直接進行拼接,最後加上 Binding便可!

View層中這樣就能夠了!哈哈!

D、ViewModel

ViewModel層你們比較不熟悉,他和MVC的Controller、MVP的Presenter到底有什麼區別呢?

ViewModel類應該怎麼寫呢?

先看下代碼:

/**
 * Description: 新聞ViewModel類
 * Created by jia on 2017/11/3.
 * 人之因此能,是相信能
 */
public class NewsViewModel {

    public Activity activity;

    public ActivityMainBinding activityMainBinding;

    public NewslistBean news;

    public NewsModel model;

    private int num=1;

    public NewsViewModel(Activity activity, final ActivityMainBinding activityMainBinding) {
        this.activity = activity;
        this.activityMainBinding = activityMainBinding;

        model=new NewsModel();

        model.getNews(new NewsModel.OnCallBack() {
            @Override
            public void onSuccess(News news) {
                activityMainBinding.setNews(news.getNewslist().get(0));
            }

            @Override
            public void onFaile(String errorInfo) {
                news=new NewslistBean("error","error","error","error","error");
                activityMainBinding.setNews(news);
            }
        });
    }

}
複製代碼

看看裏邊有些啥:

  • Context或Activity對象(這個應該好理解把)
  • 在Activity中建立的Binding對象
  • 實體類對象
  • Model層對象
  • ChildViewModel(例如Activity中嵌套多個Fragment的狀況)

將實體類對象經過setXXX方法,設置給Binding對象。

當事件觸發時,Model進行網絡請求,在回調中更新實體類,即可對應的更新UI界面。

總結

實例中只是一個簡單的功能的展現,你們在熟悉了MVVM後可再深度封裝。

本文主要講解了一些本人再開發過程當中總結的Android MVVM構建思想,更可能是理論上各個模塊如何分工、代碼如何設計。雖然在現實生產中用Android MVVM模式開發還比較少,可是隨着DataBinding 1.0的發佈,相信在Android MVVM 這一領域會更多的人來嘗試。

因爲時間有限,能力有限,文中難免有錯誤或不足的地方,還請你們提出,咱們一同進步!

源碼請點擊:github.com/shuaijia/Js…

您還能夠關注個人微信公衆號——Android機動車,獲取更多精彩內容!

相關文章
相關標籤/搜索