Android MVVM實戰(zhàn)Demo完全解析

前言

在之前的文章中介紹了部分mvvm模式的理論,那今天就通過一個Demo來講解一下mvvm在實戰(zhàn)中的結(jié)構(gòu)是怎么樣的,以及它的具體使用,下面一起來看,關(guān)于mvvm,還是先貼一下學(xué)習(xí)地址。

Android 對比MVC、MVP來聊聊MVVM模式的理解

在之前DataBinding的學(xué)習(xí)中,當(dāng)然也包括網(wǎng)上大部分關(guān)于mvvm和databinding的教程中,都是在xml中引入很多變量,然后把這些變量的數(shù)據(jù)和控件綁定在一起,這樣xml的可讀性非常差。實際上正確的做法,是只需要把ViewModel變量引入即可。而且很多也沒有講解如何使用ViewModel。

效果圖


目錄結(jié)構(gòu)

整體架構(gòu)MVVM,網(wǎng)絡(luò)請求用的是retrofit2+rxjava2,圖片加載用的Glide,列表用的xRecyclerView庫

在這里我假設(shè)讀者已經(jīng)掌握了DataBinding的用法,還不會的趕緊點擊上面的鏈接學(xué)起來,DataBinding是實現(xiàn)mvvm的一種工具,在mvvm項目中的重要性不言而喻,這里我還是再次說明一下各層的作用

1.View層就是展示數(shù)據(jù)的,以及接收到用戶的操作傳遞給viewModel層,通過dataBinding實現(xiàn)數(shù)據(jù)與view的單向綁定或雙向綁定

2.Model層最重要的作用就是獲取數(shù)據(jù)了,當(dāng)然不止于此,model層將結(jié)果通過接口的形式傳遞給viewModel層

3.ViewModel 層通過調(diào)用model層獲取數(shù)據(jù),以及業(yè)務(wù)邏輯的處理。

4.mvvm中 viewModel 和MVP中的presenter 的作用類似 ,只不過是通過 databinding 將數(shù)據(jù)與ui進行了綁定。

代碼

1.MainActivity的布局


2.MainActivity

publicclassMainActivityextendsAppCompatActivityimplementsXRecyclerView.LoadingListener{

privateActivityMainBinding binding;

privateNewsAdapter newsAdapter;//新聞列表的適配器privateNewsVM newsVM;@OverrideprotectedvoidonCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState); ? ? ? ?binding = DataBindingUtil.setContentView(this, R.layout.activity_main); ? ? ? ?initRecyclerView(); ? ? ? ?newsVM =newNewsVM(this, binding, newsAdapter); ? ?}

/**

* 初始化RecyclerView

*/privatevoidinitRecyclerView(){ ? ? ? ?binding.newsRv.setRefreshProgressStyle(ProgressStyle.BallClipRotate);//設(shè)置下拉刷新的樣式binding.newsRv.setLoadingMoreProgressStyle(ProgressStyle.BallClipRotate);//設(shè)置上拉加載更多的樣式binding.newsRv.setArrowImageView(R.mipmap.pull_down_arrow); ? ? ? ?binding.newsRv.setLoadingListener(this); ? ? ? ?LinearLayoutManager layoutManager =newLinearLayoutManager(this); ? ? ? ?binding.newsRv.setLayoutManager(layoutManager); ? ? ? ?newsAdapter =newNewsAdapter(this); ? ? ? ?binding.newsRv.setAdapter(newsAdapter); ? ?}@OverridepublicvoidonRefresh(){

//下拉刷新newsVM.loadRefreshData(); ? ?}@OverridepublicvoidonLoadMore(){

//上拉加載更多newsVM.loadMoreData(); ? ?}}

這里也就是做了RecyclerView的初始化,以及設(shè)置XRecyclerView的刷新和加載的樣式以及回調(diào),還有就是創(chuàng)建了對應(yīng)的ViewModel對象,可以通過這個對象來完成一些操作。這里的Activity基本上可以稱之為比較純粹的View了,因為確實只做了和UI相關(guān)的工作。

3.Model層去獲取解析網(wǎng)絡(luò)數(shù)據(jù),并通過接口回調(diào)給ViewModel

publicclassNewsModelImplimplementsINewsModel{

privatestaticfinalString TAG ="NewsModelImpl";

@OverridepublicvoidloadNewsData(finalintpage,finalBaseLoadListener loadListener){ ? ? ? ?HttpUtils.getNewsData() ? ? ? ? ? ? ? ?.subscribeOn(Schedulers.io()) ? ? ? ? ? ? ? ?.observeOn(AndroidSchedulers.mainThread()) ? ? ? ? ? ? ? ?.subscribe(newDisposableObserver() {

@OverridepublicvoidonNext(@NonNull NewsBean newsBean){ ? ? ? ? ? ? ? ? ? ? ? ?Log.i(TAG,"onNext: "); ? ? ? ? ? ? ? ? ? ? ? ?loadListener.loadSuccess(simpleNewsBeanList); ? ? ? ? ? ? ? ? ? ? ? ? ? ?} ? ? ? ? ? ? ? ? ? ? ? ?} ? ? ? ? ? ? ? ? ? ?}

@OverridepublicvoidonError(@NonNull Throwable throwable){ ? ? ? ? ? ? ? ? ? ? ? ?Log.i(TAG,"onError: "+ throwable.getMessage()); ? ? ? ? ? ? ? ? ? ? ? ?loadListener.loadFailure(throwable.getMessage()); ? ? ? ? ? ? ? ? ? ?}

@OverridepublicvoidonComplete(){ ? ? ? ? ? ? ? ? ? ? ? ?Log.i(TAG,"onComplete: "); ? ? ? ? ? ? ? ? ? ? ?loadListener.loadComplete(); ? ?}}

4.ViewModel

publicclassNewsVMimplementsBaseLoadListener{

privatestaticfinalString TAG ="NewsVM";

privateINewsModel mNewsModel;

privateINewsView mNewsView;

privateNewsAdapter mAdapter;

privateintcurrPage =1;//當(dāng)前頁數(shù)privateintloadType;//加載數(shù)據(jù)的類型publicNewsVM(INewsView mNewsView, NewsAdapter mAdapter){

this.mNewsView = mNewsView;

this.mAdapter = mAdapter; ? ? ? ?mNewsModel =newNewsModelImpl(); ? ? ? ?getNewsData(); ? ?}/**

* 第一次獲取新聞數(shù)據(jù)

*/privatevoidgetNewsData(){ ? ? ? ?loadType = MainConstant.LoadData.FIRST_LOAD; ? ? ? ?mNewsModel.loadNewsData(currPage,this); ? ?}/**

* 獲取下拉刷新的數(shù)據(jù)

*/publicvoidloadRefreshData(){ ? ? ? ?loadType = MainConstant.LoadData.REFRESH; ? ? ? ?currPage =1; ? ? ? ?mNewsModel.loadNewsData(currPage,this); ? ?}/**

* 獲取上拉加載更多的數(shù)據(jù)

*/publicvoidloadMoreData(){ ? ? ? ?loadType = MainConstant.LoadData.LOAD_MORE; ? ? ? ?currPage++; ? ? ? ?mNewsModel.loadNewsData(currPage,this); ? ?}@OverridepublicvoidloadSuccess(List list){

if(currPage >1) {

//上拉加載的數(shù)據(jù)mAdapter.loadMoreData(list); ? ? ? ?}else{

//第一次加載或者下拉刷新的數(shù)據(jù)mAdapter.refreshData(list); ? ? ? ?} ? ?}@OverridepublicvoidloadFailure(String message){

// 加載失敗后的提示if(currPage >1) {

//加載失敗需要回到加載之前的頁數(shù)currPage--; ? ? ? ?} ? ? ? ?mNewsView.loadFailure(message); ? ?}@OverridepublicvoidloadStart(){ ? ? ? ?mNewsView.loadStart(loadType); ? ?}@OverridepublicvoidloadComplete(){ ? ? ? ?mNewsView.loadComplete(); ? ?}}

這里,大家應(yīng)該看到了我只是做了數(shù)據(jù)和業(yè)務(wù)邏輯的處理,并沒有任何更新UI的操作,也沒有通過binding對象去操作UI,所有的UI都是通過view接口回調(diào)到activity去處理。再次強調(diào)一下ViewModel中持有的對象是view和mode這兩個接口,處理的是業(yè)務(wù)邏輯,而不應(yīng)該是databing對象,對ui的具體操作還是應(yīng)該放在view層。

5.Adapter

publicclassNewsAdapterextendsBaseAdapter{

publicNewsAdapter(Context context){

super(context); ? ?}

@OverridepublicBaseViewHolderonCreateVH(ViewGroup parent,intviewType){ ? ? ? ?ViewDataBinding dataBinding = DataBindingUtil.inflate(inflater, R.layout.item_news, parent,false);

returnnewBaseViewHolder(dataBinding); ? ?}

@OverridepublicvoidonBindVH(BaseViewHolder baseViewHolder,intposition){ ? ? ? ?ViewDataBinding binding = baseViewHolder.getBinding(); ? ? ? ?binding.setVariable(BR.simpleNewsBean, mList.get(position)); ? ? ? ?binding.setVariable(BR.position,position); ? ? ? ?binding.setVariable(BR.adapter,this); ? ? ? ?binding.executePendingBindings();//防止閃爍}

/** ? ? * 點贊 ? ? * ? ? *@paramsimpleNewsBean ? ? *@paramposition ? ? */publicvoidclickDianZan(SimpleNewsBean simpleNewsBean,intposition){

if(simpleNewsBean.isGood.get()) { ? ? ? ? ? ?simpleNewsBean.isGood.set(false); ? ? ? ? ? ?ToastUtils.show(mContext,"取消點贊 position="+ position); ? ? ? ?}else{ ? ? ? ? ? ?simpleNewsBean.isGood.set(true); ? ? ? ? ? ?ToastUtils.show(mContext,"點贊成功 position="+ position); ? ? ? ?} ? ?}}

看到這里應(yīng)該比較驚訝吧,我們的onBindViewHolder()里面沒有任何的更新UI的操作,沒有一對的setXX(),只是設(shè)置了幾個變量,以及一個點擊方法而已。

6.item_news,列表的item的布局


adapter.clickDianZan(simpleNewsBean,position)}" app:resid="@{simpleNewsBean.isGood ? R.mipmap.dianzan_press : R.mipmap.dianzan_normal }">

注意:

1.這個ImageView的onclick方法是通過lambda表達式來實現(xiàn)的,它的點擊事件事件就是adapter的clickDianZan()方法來完成的,里面引入的幾個變量都是在adapter中設(shè)置的。

2.因為我們沒有 獲取具體的binding類型,所以我們通過調(diào)用setVariable(a,b)來設(shè)置。 a代表:通過BR類來查找xml中variable標(biāo)簽中屬性name定義的名字 ,b代表:事件或數(shù)據(jù)。當(dāng)然,你也可以根據(jù)item布局對應(yīng)的具體的Binding來實現(xiàn),比如這里就是ItemNewsBinding

3.自定義屬性通過BindingAdapter來實現(xiàn)

adapter.clickDianZan(simpleNewsBean,position)}" app:resid="@{simpleNewsBean.isGood ? R.mipmap.dianzan_press : R.mipmap.dianzan_normal }">

publicclassImageHelper{

/** ? ? * mv_vm xml 傳入url 加載圖片 ? ? * imageUrl 為xml中 的命名 ? ? * ? ? *@paramiv ? imageView ? ? *@paramurl 圖片路徑 ? ? */@BindingAdapter({"imageUrl"})

publicstaticvoidloadImage(ImageView iv, String url){ ? ? ? ?Glide.with(iv.getContext()).load(url).into(iv); ? ?}

/** ? ? * mv_vm xml 設(shè)置 mipmap Resource ? ? * ? ? *@paramiv ? ?imageView ? ? *@paramresId resource id ? ? */@BindingAdapter({"resId"})

publicstaticvoidloadMipmapResource(ImageView iv,intresId){ ? ? ? ?iv.setImageResource(resId); ? ?}}

大家可以下載源碼查看,有什么問題可以給我留言。

Demo地址:https://github.com/zhouxu88/MVVMDemo


該文章摘自:mp.weixin.qq.com/s/haPOAnWGnDBznvA0wYKjBg

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內(nèi)容