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