浅淡MVP的实战演习,让代码结构更简单~

前言

讲道理,这次是真的笔者很久都没有更新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

当然不能跑题,前面对 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. <android.support.v4.widget.SwipeRefreshLayout 
  2.     xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     xmlns:app="http://schemas.android.com/apk/res-auto" 
  4.     android:id="@+id/swipe_refresh_layout" 
  5.     android:layout_width="match_parent" 
  6.     android:layout_height="match_parent"> 
  7.  
  8.     <com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.RecyclerViewWithFooter 
  9.         android:id="@+id/recyclerView" 
  10.         android:layout_width="match_parent" 
  11.         android:layout_height="match_parent"/> 
  12.  
  13. </android.support.v4.widget.SwipeRefreshLayout>  

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

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

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

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

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


  1. public interface CategoryContract {     
  2.     interface ICategoryView extends BaseView{  
  3.         void getCategoryItemsFail(String failMessage);         
  4.         void setCategoryItems(CategoryResult categoryResult);         
  5.         void addCategoryItems(CategoryResult categoryResult);         
  6.         void showSwipeLoading();         
  7.         void hideSwipeLoading();         
  8.         void setLoading();         
  9.         String getCategoryName();         
  10.         void noMore(); 
  11.     }    interface ICategoryPresenter extends BasePresenter{         
  12.         void getCategoryItems(boolean isRefresh); 
  13.     } 
  14. }  

编写 Presenter 实现类。


  1. public class CategoryPresenter implements ICategoryPresenter {     
  2. private ICategoryView mCategoryICategoryView;     
  3. private int mPage = 1;     
  4. private Subscription mSubscription;     
  5. public CategoryPresenter(ICategoryView androidICategoryView) { 
  6.         mCategoryICategoryView = androidICategoryView; 
  7.     }     
  8.     @Override 
  9.     public void subscribe() { 
  10.         getCategoryItems(true); 
  11.     }     
  12.     @Override 
  13.     public void unSubscribe() {         
  14.         if (mSubscription != null  && !mSubscription.isUnsubscribed()){ 
  15.             mSubscription.unsubscribe(); 
  16.         } 
  17.     }    @Override 
  18.     public void getCategoryItems(final boolean isRefresh) {         
  19.         if (isRefresh) { 
  20.             mPage = 1; 
  21.             mCategoryICategoryView.showSwipeLoading(); 
  22.         } else { 
  23.             mPage++; 
  24.         } 
  25.         mSubscription = NetWork.getGankApi() 
  26.                 .getCategoryData(mCategoryICategoryView.getCategoryName(), GlobalConfig.CATEGORY_COUNT,mPage) 
  27.                 .subscribeOn(Schedulers.io()) 
  28.                 .observeOn(AndroidSchedulers.mainThread()) 
  29.                 .subscribe(new Observer<CategoryResult>() {                     
  30.                     @Override 
  31.                     public void onCompleted() { 
  32.  
  33.                     }                     
  34.                     @Override 
  35.                     public void onError(Throwable e) { 
  36.                         mCategoryICategoryView.hideSwipeLoading(); 
  37.                         mCategoryICategoryView.getCategoryItemsFail(mCategoryICategoryView.getCategoryName()+" 列表数据获取失败!"); 
  38.                     }                     
  39.                     @Override 
  40.                     public void onNext(CategoryResult categoryResult) {                         
  41.                         if (isRefresh){ 
  42.                             mCategoryICategoryView.setCategoryItems(categoryResult); 
  43.                             mCategoryICategoryView.hideSwipeLoading(); 
  44.                             mCategoryICategoryView.setLoading(); 
  45.                         }else { 
  46.                             mCategoryICategoryView.addCategoryItems(categoryResult); 
  47.                         } 
  48.                     } 
  49.                 }); 
  50.     } 
  51. }  

编写Adapter,用于展示数据。


  1. class CategoryRecyclerAdapter extends CommonRecyclerAdapter<CategoryResult.ResultsBean> implements  
  2. ListenerWithPosition.OnClickWithPositionListener<CommonRecyclerHolder>{ 
  3.  
  4.     CategoryRecyclerAdapter(Context context) {         
  5.         super(context, null, R.layout.item_category); 
  6.     }    @Override 
  7.     public void convert(CommonRecyclerHolder holder, ResultsBean resultsBean) {         
  8.         if (resultsBean != null) {             
  9.         ImageView imageView = holder.getView(R.id.category_item_img);             
  10.         if (ConfigManage.INSTANCE.isListShowImg()) { // 列表显示图片 
  11.                 imageView.setVisibility(View.VISIBLE);                 
  12.             String quality = "";                 
  13.             if (resultsBean.images != null && resultsBean.images.size() > 0) { 
  14.                     switch (ConfigManage.INSTANCE.getThumbnailQuality()) {                         
  15.                         case 0: // 原图 
  16.                             quality = "";                             
  17.                             break;                         
  18.                         case 1: // 
  19.                             quality = "?imageView2/0/w/400";                             
  20.                             break;                         
  21.                         case 2: 
  22.                             quality = "?imageView2/0/w/190";                             
  23.                             break; 
  24.                     }                     
  25.                     Glide.with(mContext) 
  26.                             .load(resultsBean.images.get(0) + quality) 
  27.                             .placeholder(R.mipmap.image_default) 
  28.                             .error(R.mipmap.image_default) 
  29.                             .into(imageView); 
  30.                 } else { // 列表不显示图片 
  31.                     Glide.with(mContext).load(R.mipmap.image_default).into(imageView); 
  32.                 } 
  33.             } else { 
  34.                 imageView.setVisibility(View.GONE); 
  35.             } 
  36.  
  37.             holder.setTextViewText(R.id.category_item_desc, resultsBean.desc == null ? "unknown" : resultsBean.desc); 
  38.             holder.setTextViewText(R.id.category_item_author, resultsBean.who == null ? "unknown" : resultsBean.who); 
  39.             holder.setTextViewText(R.id.category_item_time, TimeUtil.dateFormat(resultsBean.publishedAt)); 
  40.             holder.setTextViewText(R.id.category_item_src, resultsBean.source == null ? "unknown" : resultsBean.source); 
  41.             holder.setOnClickListener(this, R.id.category_item_layout); 
  42.         } 
  43.     }    @Override 
  44.     public void onClick(View v, int position, CommonRecyclerHolder holder) { 
  45.         //Toasty.info(mContext,"跳转到相应网页!", Toast.LENGTH_SHORT,true).show(); 
  46.         Intent intent = new Intent(mContext, WebViewActivity.class); 
  47.         intent.putExtra(WebViewActivity.GANK_TITLE, mData.get(position).desc); 
  48.         intent.putExtra(WebViewActivity.GANK_URL, mData.get(position).url); 
  49.         mContext.startActivity(intent); 
  50.     } 
  51. }  

最后当然是 Fragment。


  1. public class CategoryFragment extends BaseFragment implements ICategoryView, OnRefreshListener, OnLoadMoreListener {     
  2. public static final String CATEGORY_NAME = "com.nanchen.aiyagirl.module.category.CategoryFragment.CATEGORY_NAME";     
  3. @BindView(R.id.recyclerView) 
  4.     RecyclerViewWithFooter mRecyclerView;     
  5.     @BindView(R.id.swipe_refresh_layout) 
  6.     SwipeRefreshLayout mSwipeRefreshLayout;     
  7.         private String categoryName;     
  8.         private CategoryRecyclerAdapter mAdapter;     
  9.         private ICategoryPresenter mICategoryPresenter;     
  10.         public static CategoryFragment newInstance(String mCategoryName) { 
  11.         CategoryFragment categoryFragment = new CategoryFragment(); 
  12.         Bundle bundle = new Bundle(); 
  13.         bundle.putString(CATEGORY_NAME, mCategoryName); 
  14.         categoryFragment.setArguments(bundle);         
  15.         return categoryFragment; 
  16.     }    @Override 
  17.     protected int getContentViewLayoutID() {         
  18.         return R.layout.fragment_category; 
  19.     }    @Override 
  20.     protected void init() { 
  21.         mICategoryPresenter = new CategoryPresenter(this); 
  22.         categoryName = getArguments().getString(CATEGORY_NAME); 
  23.         mSwipeRefreshLayout.setOnRefreshListener(this); 
  24.         mAdapter = new CategoryRecyclerAdapter(getActivity()); 
  25.         mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); 
  26.         mRecyclerView.addItemDecoration(new RecyclerViewDivider(getActivity(), LinearLayoutManager.HORIZONTAL)); 
  27.         mRecyclerView.setAdapter(mAdapter); 
  28.         mRecyclerView.setOnLoadMoreListener(this); 
  29.         mRecyclerView.setEmpty(); 
  30.         mICategoryPresenter.subscribe(); 
  31.     }    @Override 
  32.     public void onDestroy() {         
  33.         super.onDestroy();         
  34.         if (mICategoryPresenter != null) { 
  35.             mICategoryPresenter.unSubscribe(); 
  36.         } 
  37.     }    @Override 
  38.     public void onRefresh() { 
  39.         mICategoryPresenter.getCategoryItems(true); 
  40.     }    @Override 
  41.     public void onLoadMore() { 
  42.         mICategoryPresenter.getCategoryItems(false); 
  43.     }    @Override 
  44.     public void getCategoryItemsFail(String failMessage) {         
  45.         if (getUserVisibleHint()) { 
  46.             Toasty.error(this.getContext(), failMessage).show(); 
  47.         } 
  48.     }    @Override 
  49.     public void setCategoryItems(CategoryResult categoryResult) { 
  50.         mAdapter.setData(categoryResult.results); 
  51.     }    @Override 
  52.     public void addCategoryItems(CategoryResult categoryResult) { 
  53.         mAdapter.addData(categoryResult.results); 
  54.  
  55.     }    @Override 
  56.     public void showSwipeLoading() { 
  57.         mSwipeRefreshLayout.setRefreshing(true); 
  58.     }    @Override 
  59.     public void hideSwipeLoading() { 
  60.         mSwipeRefreshLayout.setRefreshing(false); 
  61.     }    @Override 
  62.     public void setLoading() { 
  63.         mRecyclerView.setLoading(); 
  64.     }    @Override 
  65.     public String getCategoryName() {         
  66.         return this.categoryName; 
  67.     }    @Override 
  68.     public void noMore() { 
  69.         mRecyclerView.setEnd("没有更多数据"); 
  70.     } 
  71. }  

项目截图

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

   

结束语

爱吖妹纸是运用 MVP,Retrofit,RxJava 等主流框架整合的干货 App,项目资源来源于代码家的干货集中营。代码量不多,但基本涉及了各个方面,界面采用design风格,所以也是学习design的良药。作者也是希望继续在开源路上越走越远,还请大家支持。

作者:南尘

来源:51CTO

时间: 2024-09-20 06:03:17

浅淡MVP的实战演习,让代码结构更简单~的相关文章

浅淡如何用ASP实现在线人数统计

统计|在线|在线人数                 浅淡如何用ASP实现在线人数统计                           (jaklin  2000.8.17)     我发现这几天问此问题的人挺多的.就此我想说说我个的用法, 请各位大虾指教. 在线人数是指一个时段内的访客人数统计,时间的长短是由设计者设定的. 在这个时段内,各个不同IP访问本站点的总数,就是当前的线上人数.在ASP中,一般是使用Session对象来实现统计,实现代码如下: 1. 在Golobal.asa文件中

浅淡对电子商务网站的实例策化与分析

我以前是推广和宣传别的网站,从来没有针对电子商务类型的网站进行策化和分析.而这次我选择了电子商务方面的网站,114电动车浅淡用了一个月多的时间对电子商务策化和分析的总结: 第一:选择作什么行业电子商务网站.这一般要根据你的爱好和利基市场,同时要有创新和创意. 第二:选择好的域名,域名这块也是非常重要.域名规则是:易记,易输,意义明确. 第三;选择网站具有独特的名字.互联网竞争非常大,我们在选择的名字比域名有时更重要.因为现在进行网站宣传时不能代域名一起发布,只有通过网站名字来活生生的体现. 第四

SEO实战演习:怎样通过综合需求的满足来达到前三排名位置

搜索引擎中排名前三位的网站会分走行业流量的75%,这就是众站长和SEOer不停利用技术刷新排名进击前三名的原因了.以现在搜索引擎的规则算法来说,要想获得前三名的排名位置,最为重要的就是满足用户体验,也只有这样才能够让网站排名尽量靠前.不过,这其中大家也有误解,所谓的用户体验并非是单一需求的满足,而是综合用户需求的满足,亦或者说是一个整和.因此,今天笔者小丹要和大家一起进行的SEO实战就是来研究怎样通过综合需求的满足来达到前三的排名位置. 讲到用户体验的满足,我们首当其冲想到的非网站内容莫属.不过

浅淡行业网站如何利用手机短信推广

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 浅淡一下行业站利用手机短信推广,有说的不好的地方欢迎拍砖,交流为主. 网站的推广是网站在运营过程中必不可少的,现在网站推广的方式多种多样,像什么软文推广.论坛推广.路边广告推广.口碑推广等等,笔者将其划分为两大类线上推广和线下推广,而个人站长大多数是以线上推广为主,主要是由于资金和精力原因所至,而线上推广的例子.教程也数不胜数,竞争也是非常的

CUDA开发实战:C#代码中使用DLL

使用.NET平台调用函数是一件容易的事情,但有一件事需要注意 访问的可变性,因为我们不能在它们上面使用DllImport属性,我们必须找到变量的地址,然后排列数据. using System.Runtime.InteropServices; #region hard way to import variable from unmanaged dll [DllImport(" kernel32.dll", SetLastError = true, CharSet = CharSet.An

从一个登录页面浅淡MVVM(三)

转自http://www.cnblogs.com/Sunpire/archive/2010/12/25/1916943.html 在 ViewModels 中增加一个 ViewModelCommand ,通过 Action<Object> 执行实际的方法.   ViewModelCommand.cs     PS:日前,在看有关 Object Value  的用法时,提到了 struct ,struct 是存储在 Heap 上,而引用类型是存储在 Stack 上的,在内存分配及垃圾回收等方面

代码结构优化工作中的细节

咱们说到程序优化,立马想到的是什么内容呢?一般情况下我们想到的可能是 程序执行速度 页面载入时间 内存占用 网络流量消耗 牺牲空间来换取执行速度的提升 执行重复操作来换取内存消耗的优化 以及用各种办法减少对数据库的查询 今天我们主要说的是另种优化,那就是代码结构的优化,而这一点往往更加重要. 优化可读性以及可扩展性 硬件足够,系统复杂的时代,让代码易于阅读和调试,易于维护和扩展更重要.才能更好的团队合作,才能适应需求变化. 咱们的代码,写1次,调试5次,修改10次,阅读50次. 连续不断的变化会

JavaScript实战(原生range和自定义特效)简单实例_javascript技巧

今天我又码了两个特效:一个是用原生input[type=range]的,另一个完全自定义的:下面是完整代码和演示: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <style> #tip{ position: absolute; top: 30px; left: 0; ri

树形菜单求助 ,不知道从何下手。求大咖们帮帮讲讲思路 要是有代码就更好了。

问题描述 树形菜单求助 ,不知道从何下手.求大咖们帮帮讲讲思路 要是有代码就更好了. 左边为国家的省份,子菜单为省份的市级单位,右边卫市级单位的区.数据是从数据库区的.括号内的数字为选中的个数.从数据库去出来的数据类似于 id:01, name:黑龙江省,市区的LIST. 在前台页面怎遍历这个树形菜单还可以联动 求助大咖们 图片地址:http://a.hiphotos.bdimg.com/album/s%3D1000%3Bq%3D90/sign=3cccf467caef7609380b9d9f1