源碼請點擊:github.com/shuaijia/Js…android
您還能夠關注個人微信公衆號——安卓乾貨營,與我交流和獲取更多精彩內容。git
說到Android MVVM,就會聯想到DataBinding框架。然而二者的概念是不同的,不能混爲一談。MVVM是一種架構模式,而DataBinding是一個實現數據和UI綁定的框架,是構建MVVM模式的一個工具。github
網上關於MVVM框架的搭建和使用的文章不多,大多提到MVVM框架,就是在介紹DataBinding的使用。對於MVVM中各模塊之間如何劃分,如何定義,又是如何配合實現高度解耦的文章更是少之又少。你們看完後仍是一頭霧水,只是對MVVM有個大概的瞭解,並不很清楚如何上手。數據庫
接下來,咱們先認識什麼是MVVM,而後再一步一步來設計整個MVVM框架。json
MVC、MVP和MVVM都是在安卓開發中常用的模式,咱們在認識MVVM以前先回顧一下MVC和MVP。bash
Android開發自己仍是比較符合MVC架構的,可是Android中純粹做爲View的XML視圖功能太弱,咱們大量處理View的邏輯只能寫在Activity中,這樣Activity就充當了View和Controller兩個角色,直接致使Activity中的代碼臃腫、混亂,致使閱讀困難、重用困難和維護困難。相信大多數Android開發者都遇到過一個Acitivty數以千行的代碼狀況吧!因此,更貼切的說法是,這個MVC結構最終其實只是一個Model-View(Activity:View&Controller)的結構。服務器
前面咱們說,Activity充當了View和Controller兩個角色,MVP就能很好地解決這個問題,其核心理念是經過一個抽象的View接口(不是真正的View層)將Presenter與真正的View層進行解耦。Persenter持有該View接口,對該接口進行操做,而不是直接操做View層。這樣就能夠把視圖操做和業務邏輯解耦,從而讓Activity成爲真正的View層。微信
不足的是,MVP模式中定義了大量的接口,使得代碼結構變大和複雜;MVP是UI和事件驅動,須要手動調用大量的方法來進行實現,缺少自動性。網絡
因此咱們迎來了MVVM框架,固然得首先感謝google爸爸提供得DataBinding,真的是很強大!架構
在MVVM模式中,將程序結構分爲三層——View-ViewModel-Model,接下來咱們一塊兒來認識它們:
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層作的事情恰好和View層相反,ViewModel只負責業務邏輯,不作任何和UI相關的事情。
同時DataBinding框架已經支持雙向綁定,讓咱們能夠經過雙向綁定獲取View層反饋給ViewModel層的數據,並對這些數據上進行操做。
事件的處理,咱們也但願能把這些事件處理綁定到控件上,並把這些事件的處理統一化,爲此咱們經過使用BindingAdapter對一些經常使用的事件作封裝,把一個個事件封裝成一個個Command,對於每一個事件咱們用一個ReplyCommand去處理就好了,ReplyCommand會把你可能須要的數據帶給你,這使得咱們在ViewModel層處理事件的時候只須要關心處理數據就好了,具體見MVVM Light Toolkit 使用指南的Command部分。
Model層不只包括實體類的定義,還須要對數據進行處理和讀寫。例如:使用Retrofit或okHttp進行網絡請求,或着如數據庫操做等等。
咱們再來看下這張圖:
簡述下數據流走向:
View中使用DataBinding的Command來綁定事件和響應事件,觸發網絡請求;ViewModel進行分析處理,調用Model的數據請求方法;Model將收到的請求參數等信息封裝,調用網絡請求庫;網絡庫(Retrofit等)與服務器進行交互;
服務器將json數據返回Retrofit等網絡庫,再返回到Model層中,ViewModel在回調中收到返回的實體類對象;
由於xml與實體類對象實現了雙向綁定,實體類更新,使得UI更新!
/**
* 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,稍後咱們再說。
/**
* 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請求回來的實體類。
每一個項目的網絡請求庫和方法都會不一樣,符合本身的就是最好的!(●ˇ∀ˇ●)
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的知識整理成一篇博客,敬請期待!
由於本篇文章重點在於講述MVVM框架的使用,因此DataBinding只進行粗略簡介,若有錯誤,還望你們及時提出,咱們一塊兒進步!
Activity中
在Activity中設置佈局,咱們再也不使用Activity的setContentView方法,取而代之的是:DataBindingUtil.setContentView
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
複製代碼
所返回的變量類型怎麼來的呢?
將使用了DataBinding的佈局名字,去掉全部下劃線,將全部單詞首字母大寫,直接進行拼接,最後加上 Binding便可!
View層中這樣就能夠了!哈哈!
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);
}
});
}
}
複製代碼
看看裏邊有些啥:
將實體類對象經過setXXX方法,設置給Binding對象。
當事件觸發時,Model進行網絡請求,在回調中更新實體類,即可對應的更新UI界面。
實例中只是一個簡單的功能的展現,你們在熟悉了MVVM後可再深度封裝。
本文主要講解了一些本人再開發過程當中總結的Android MVVM構建思想,更可能是理論上各個模塊如何分工、代碼如何設計。雖然在現實生產中用Android MVVM模式開發還比較少,可是隨着DataBinding 1.0的發佈,相信在Android MVVM 這一領域會更多的人來嘗試。
因爲時間有限,能力有限,文中難免有錯誤或不足的地方,還請你們提出,咱們一同進步!
源碼請點擊:github.com/shuaijia/Js…
您還能夠關注個人微信公衆號——Android機動車,獲取更多精彩內容!