当你的才华还撑不起你的梦想时,你只能一直前进!

【知识必备】浅淡MVP在Android项目中的实战演习,让代码结构更简单~

一、写在前面

     讲道理,这次是真的笔者很久都没有更新blog了,主要最近维护的框架问题也是层出不穷,而且对技术交流群的解答也让我身心疲惫,所以在这里跟关注我的人说声抱歉,没有定期给你们带来福利,那么这里就给大家带来一个重磅福利:爱吖妹纸——Retrofit & RxJava & MVP & Butterknife 的完整App.

     讲到最近让我身心疲惫的问题解答,无疑是让我在开源的路上越走越远,虽然我不是技术大牛,却依然被一些很简单的问题轮番轰炸,其实笔者的内心真的是拒绝的。不得不说,写给技术群内的你和群主,为什么你提问,而总没人回你!写的挺好。

二、概述

     废话也不多说,对于MVP(Model View Presenter),我相信大多数人都能说出一些的,“MVC的演化版本”,“让Model和View完全解耦”等等,但用过MVP的人一定会觉得,在Android中,代码很清晰,不过多了很多类。对于大多数人而言,在看MVP的Demo的时候,一眼便是慢慢的nice,然而让自己来写个例子,却很头疼写不出来。但的确MVC模式写起来更加像是顺水推舟。只需要把自己的业务逻辑一股脑的放进Activity就成功完事儿。

     不得不说,之前我们项目中的确也是用的MVC在编写的。很简单的会发现随便一个Activity代码都是几百上千行,甚至还有一万行以上的。看起来的确那么一回事儿,但是细想这个View对于布局文件,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的操作都在Activity中,造成了Activity既想View又像Controller,鄙弃代码上的不美观来说,对于后面的阅读代码真的是吃力。

     不信?你瞧瞧。

     也许业务逻辑比较简单的功能用MVC没什么,但是想没想过,如果你产品后面改需求怎么办?是的,你接受产品需求的强奸,但还是只有忍辱偷生。在日渐复杂的业务逻辑上,你的Activity和Fragment代码越来越多,最终导致代码爆炸,难以维护。

     网上浏览一圈,发现讲MVP的文章比比皆是,可见MVP的欢迎度,但大多数文章都只是讲理论,稍微好点的会附带一个简单的登录的Demo。然而,一个简单的demo很难让初次接触MVP模式的人掌握它的使用。所以爱吖妹纸应运而生。

三、爱吖妹纸的优势?

     爱吖妹纸是运用 MVP,Retrofit,RxJava 等主流框架整合的干货 App,项目资源来源于代码家的干货集中营。代码量不多,但基本涉及了各个方面,界面采用design风格,所以也是学习design的良药。你还在等什么,猛戳链接吧!

     https://github.com/nanchen2251/AiYaGirl

四、什么是 MVP

     当然不能跑题,前面对 MVP 做了简单的概述,下面还是用一个简单的图表示一下。

  如上图所示,在项目中 View 和 Model 并不直接交互,而是使用 Presenter 作为 View 和 Model 之间的桥梁。其中 Presenter 中同时持有 View 层以及 Model 层的 Interface 的引用,而 View 层持有 Presenter 层 Interface 的引用,当 View 层某个页面需要展示某些数据的时候,首先会调用Presenter 层的某个接口,然后 Presenter 层会调用 Model 层请求数据,当 Model 层数据加载成功之后会调用 Presenter 层的回调方法通知 Presenter 层数据加载完毕,最后 Presenter 层再调用 View 层的接口将加载后的数据展示给用户。这就是 MVP 模式的核心过程。

     这样分层的好处就是大大减少了Model与View层之间的耦合度。一方面可以使得View层和Model层单独开发与测试,互不依赖。另一方面Model层可以封装复用,可以极大的减少代码量。当然,MVP还有其他的一些优点,这里不再赘述。

 

五、随便看一个功能?

      这里就给大家随便看看干货板块的功能吧。

      布局相当简单。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <android.support.v4.widget.SwipeRefreshLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     xmlns:app="http://schemas.android.com/apk/res-auto"
 5     android:id="@+id/swipe_refresh_layout"
 6     android:layout_width="match_parent"
 7     android:layout_height="match_parent">
 8 
 9 
10     <com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.RecyclerViewWithFooter
11         android:id="@+id/recyclerView"
12         android:layout_width="match_parent"
13         android:layout_height="match_parent"/>
14 
15 
16 </android.support.v4.widget.SwipeRefreshLayout>

   干货模块,也就是一个Fragment,里面有一个RecyclerView,支持下拉刷新和上拉加载数据。所以我们的 Presenter 和 View 只需要定义一下简单的方法。 

         1)加载数据的过程中显示加载的进度条;

         2)加载数据成功提醒 Adapter 刷新数据;

         3)加载失败谈窗提醒用户相关信息;

         4)加载结束隐藏进度条; 

 1 package com.nanchen.aiyagirl.module.category;
 2 
 3 import com.nanchen.aiyagirl.base.BasePresenter;
 4 import com.nanchen.aiyagirl.base.BaseView;
 5 import com.nanchen.aiyagirl.model.CategoryResult;
 6 
 7 /**
 8  * Author: nanchen
 9  * Email: liushilin520@foxmail.com
10  * Date: 2017-04-14  10:14
11  */
12 
13 public interface CategoryContract {
14 
15     interface ICategoryView extends BaseView{
16         
17         void getCategoryItemsFail(String failMessage);
18 
19         void setCategoryItems(CategoryResult categoryResult);
20 
21         void addCategoryItems(CategoryResult categoryResult);
22 
23         void showSwipeLoading();
24 
25         void hideSwipeLoading();
26 
27         void setLoading();
28 
29         String getCategoryName();
30 
31         void noMore();
32     }
33     
34     interface ICategoryPresenter extends BasePresenter{
35         
36         void getCategoryItems(boolean isRefresh);
37     }
38 }

       编写 Presenter 实现类。

       

 1 package com.nanchen.aiyagirl.module.category;
 2 
 3 import com.nanchen.aiyagirl.config.GlobalConfig;
 4 import com.nanchen.aiyagirl.model.CategoryResult;
 5 import com.nanchen.aiyagirl.module.category.CategoryContract.ICategoryView;
 6 import com.nanchen.aiyagirl.module.category.CategoryContract.ICategoryPresenter;
 7 import com.nanchen.aiyagirl.net.NetWork;
 8 
 9 import rx.Observer;
10 import rx.Subscription;
11 import rx.android.schedulers.AndroidSchedulers;
12 import rx.schedulers.Schedulers;
13 
14 /**
15  * ICategoryPresenter
16  * <p>
17  * Author: nanchen
18  * Email: liushilin520@foxmail.com
19  * Date: 2017-04-14  11:16
20  */
21 
22 public class CategoryPresenter implements ICategoryPresenter {
23 
24     private ICategoryView mCategoryICategoryView;
25     private int mPage = 1;
26     private Subscription mSubscription;
27 
28     public CategoryPresenter(ICategoryView androidICategoryView) {
29         mCategoryICategoryView = androidICategoryView;
30     }
31 
32     @Override
33     public void subscribe() {
34         getCategoryItems(true);
35     }
36 
37     @Override
38     public void unSubscribe() {
39         if (mSubscription != null  && !mSubscription.isUnsubscribed()){
40             mSubscription.unsubscribe();
41         }
42     }
43 
44     @Override
45     public void getCategoryItems(final boolean isRefresh) {
46         if (isRefresh) {
47             mPage = 1;
48             mCategoryICategoryView.showSwipeLoading();
49         } else {
50             mPage++;
51         }
52         mSubscription = NetWork.getGankApi()
53                 .getCategoryData(mCategoryICategoryView.getCategoryName(), GlobalConfig.CATEGORY_COUNT,mPage)
54                 .subscribeOn(Schedulers.io())
55                 .observeOn(AndroidSchedulers.mainThread())
56                 .subscribe(new Observer<CategoryResult>() {
57                     @Override
58                     public void onCompleted() {
59 
60                     }
61 
62                     @Override
63                     public void onError(Throwable e) {
64                         mCategoryICategoryView.hideSwipeLoading();
65                         mCategoryICategoryView.getCategoryItemsFail(mCategoryICategoryView.getCategoryName()+" 列表数据获取失败!");
66                     }
67 
68                     @Override
69                     public void onNext(CategoryResult categoryResult) {
70                         if (isRefresh){
71                             mCategoryICategoryView.setCategoryItems(categoryResult);
72                             mCategoryICategoryView.hideSwipeLoading();
73                             mCategoryICategoryView.setLoading();
74                         }else {
75                             mCategoryICategoryView.addCategoryItems(categoryResult);
76                         }
77                     }
78                 });
79 
80     }
81 }

       编写Adapter,用于展示数据。

     

 1 package com.nanchen.aiyagirl.module.category;
 2 
 3 import android.content.Context;
 4 import android.content.Intent;
 5 import android.view.View;
 6 import android.widget.ImageView;
 7 
 8 import com.bumptech.glide.Glide;
 9 import com.nanchen.aiyagirl.ConfigManage;
10 import com.nanchen.aiyagirl.R;
11 import com.nanchen.aiyagirl.base.adapter.CommonRecyclerAdapter;
12 import com.nanchen.aiyagirl.base.adapter.CommonRecyclerHolder;
13 import com.nanchen.aiyagirl.base.adapter.ListenerWithPosition;
14 import com.nanchen.aiyagirl.model.CategoryResult;
15 import com.nanchen.aiyagirl.model.CategoryResult.ResultsBean;
16 import com.nanchen.aiyagirl.module.web.WebViewActivity;
17 import com.nanchen.aiyagirl.utils.TimeUtil;
18 
19 /**
20  * Author: nanchen
21  * Email: liushilin520@foxmail.com
22  * Date: 2017-04-14  10:21
23  */
24 
25 class CategoryRecyclerAdapter extends CommonRecyclerAdapter<CategoryResult.ResultsBean> implements ListenerWithPosition.OnClickWithPositionListener<CommonRecyclerHolder>{
26 
27     CategoryRecyclerAdapter(Context context) {
28         super(context, null, R.layout.item_category);
29     }
30 
31     @Override
32     public void convert(CommonRecyclerHolder holder, ResultsBean resultsBean) {
33         if (resultsBean != null) {
34             ImageView imageView = holder.getView(R.id.category_item_img);
35             if (ConfigManage.INSTANCE.isListShowImg()) { // 列表显示图片
36                 imageView.setVisibility(View.VISIBLE);
37                 String quality = "";
38                 if (resultsBean.images != null && resultsBean.images.size() > 0) {
39                     switch (ConfigManage.INSTANCE.getThumbnailQuality()) {
40                         case 0: // 原图
41                             quality = "";
42                             break;
43                         case 1: //
44                             quality = "?imageView2/0/w/400";
45                             break;
46                         case 2:
47                             quality = "?imageView2/0/w/190";
48                             break;
49                     }
50                     Glide.with(mContext)
51                             .load(resultsBean.images.get(0) + quality)
52                             .placeholder(R.mipmap.image_default)
53                             .error(R.mipmap.image_default)
54                             .into(imageView);
55                 } else { // 列表不显示图片
56                     Glide.with(mContext).load(R.mipmap.image_default).into(imageView);
57                 }
58             } else {
59                 imageView.setVisibility(View.GONE);
60             }
61 
62             holder.setTextViewText(R.id.category_item_desc, resultsBean.desc == null ? "unknown" : resultsBean.desc);
63             holder.setTextViewText(R.id.category_item_author, resultsBean.who == null ? "unknown" : resultsBean.who);
64             holder.setTextViewText(R.id.category_item_time, TimeUtil.dateFormat(resultsBean.publishedAt));
65             holder.setTextViewText(R.id.category_item_src, resultsBean.source == null ? "unknown" : resultsBean.source);
66             holder.setOnClickListener(this, R.id.category_item_layout);
67         }
68     }
69 
70     @Override
71     public void onClick(View v, int position, CommonRecyclerHolder holder) {
72 //        Toasty.info(mContext,"跳转到相应网页!", Toast.LENGTH_SHORT,true).show();
73         Intent intent = new Intent(mContext, WebViewActivity.class);
74         intent.putExtra(WebViewActivity.GANK_TITLE, mData.get(position).desc);
75         intent.putExtra(WebViewActivity.GANK_URL, mData.get(position).url);
76         mContext.startActivity(intent);
77     }
78 }

      最后当然是 Fragment。

   

package com.nanchen.aiyagirl.module.category;

import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.support.v7.widget.LinearLayoutManager;

import com.nanchen.aiyagirl.R;
import com.nanchen.aiyagirl.base.BaseFragment;
import com.nanchen.aiyagirl.model.CategoryResult;
import com.nanchen.aiyagirl.module.category.CategoryContract.ICategoryPresenter;
import com.nanchen.aiyagirl.module.category.CategoryContract.ICategoryView;
import com.nanchen.aiyagirl.widget.RecyclerViewDivider;
import com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.OnLoadMoreListener;
import com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.RecyclerViewWithFooter;

import butterknife.BindView;
import es.dmoral.toasty.Toasty;

/**
 * 主页轮播下面的Fragment
 * <p>
 * Author: nanchen
 * Email: liushilin520@foxmail.com
 * Date: 2017-04-14  9:46
 */

public class CategoryFragment extends BaseFragment implements ICategoryView, OnRefreshListener, OnLoadMoreListener {

    public static final String CATEGORY_NAME = "com.nanchen.aiyagirl.module.category.CategoryFragment.CATEGORY_NAME";
    @BindView(R.id.recyclerView)
    RecyclerViewWithFooter mRecyclerView;
    @BindView(R.id.swipe_refresh_layout)
    SwipeRefreshLayout mSwipeRefreshLayout;

    private String categoryName;
    private CategoryRecyclerAdapter mAdapter;
    private ICategoryPresenter mICategoryPresenter;

    public static CategoryFragment newInstance(String mCategoryName) {
        CategoryFragment categoryFragment = new CategoryFragment();
        Bundle bundle = new Bundle();
        bundle.putString(CATEGORY_NAME, mCategoryName);
        categoryFragment.setArguments(bundle);
        return categoryFragment;
    }


    @Override
    protected int getContentViewLayoutID() {
        return R.layout.fragment_category;
    }

    @Override
    protected void init() {
        mICategoryPresenter = new CategoryPresenter(this);

        categoryName = getArguments().getString(CATEGORY_NAME);

        mSwipeRefreshLayout.setOnRefreshListener(this);

        mAdapter = new CategoryRecyclerAdapter(getActivity());

        mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
        mRecyclerView.addItemDecoration(new RecyclerViewDivider(getActivity(), LinearLayoutManager.HORIZONTAL));
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setOnLoadMoreListener(this);
        mRecyclerView.setEmpty();

        mICategoryPresenter.subscribe();

    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mICategoryPresenter != null) {
            mICategoryPresenter.unSubscribe();
        }
    }

    @Override
    public void onRefresh() {
        mICategoryPresenter.getCategoryItems(true);
    }

    @Override
    public void onLoadMore() {
        mICategoryPresenter.getCategoryItems(false);
    }

    @Override
    public void getCategoryItemsFail(String failMessage) {
        if (getUserVisibleHint()) {
            Toasty.error(this.getContext(), failMessage).show();
        }
    }

    @Override
    public void setCategoryItems(CategoryResult categoryResult) {
        mAdapter.setData(categoryResult.results);
    }

    @Override
    public void addCategoryItems(CategoryResult categoryResult) {
        mAdapter.addData(categoryResult.results);

    }

    @Override
    public void showSwipeLoading() {
        mSwipeRefreshLayout.setRefreshing(true);
    }

    @Override
    public void hideSwipeLoading() {
        mSwipeRefreshLayout.setRefreshing(false);
    }

    @Override
    public void setLoading() {
        mRecyclerView.setLoading();
    }

    @Override
    public String getCategoryName() {
        return this.categoryName;
    }

    @Override
    public void noMore() {
        mRecyclerView.setEnd("没有更多数据");
    }

}

 

六、看看项目截图?

      还是给大家看看项目截图,以免大家心慌。

      

 

  

七、写在最后

     笔者也是希望继续在开源路上越走越远,还请大家支持,点击评论666!别忘了关注我的 github,随手点赞。

posted @ 2017-04-25 09:56  南尘  阅读(3548)  评论(10编辑  收藏  举报

写不完的矫情,做不完的开源

点击进入我的GitHub页
南 尘
主 页
优美钢琴曲合集-南尘.mp3                    感谢您阅读我的博客,如果您现在工作、学习累了或者疲惫了,不妨聆听一下音乐,它能够减轻你的疲劳,还能够带给您一种舒适愉悦的心情。(样式取自博客园-欲泪成雪)