讲道理,这次是真的笔者很久都没有更新 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 做了简单的概述,下面还是用一个简单的图表示一下。
如上图所示,在项目中 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 2<android.support.v4.widget.SwipeRefreshLayout
- 3xmlns:android="http://schemas.android.com/apk/res/android" 4xmlns:app="http://schemas.android.com/apk/res-auto" 5android:id="@+id/swipe_refresh_layout" 6android:layout_width="match_parent" 7android:layout_height="match_parent"> 8
- 9
- 10<com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.RecyclerViewWithFooter
- 11android:id="@+id/recyclerView"12android:layout_width="match_parent"13android:layout_height="match_parent"/>14
- 15
- 16
干货模块,也就是一个 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 interfaceICategoryViewextends 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 interfaceICategoryPresenterextends BasePresenter{
- 35
- 36 voidgetCategoryItems(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 classCategoryPresenterimplements ICategoryPresenter {
- 23
- 24 private ICategoryView mCategoryICategoryView;
- 25 private intmPage = 1;
- 26 private Subscription mSubscription;
- 27
- 28 public CategoryPresenter(ICategoryView androidICategoryView) {
- 29mCategoryICategoryView = androidICategoryView;
- 30 }
- 31
- 32 @Override
- 33 public void subscribe() {
- 34getCategoryItems(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 voidgetCategoryItems(final boolean isRefresh) {
- 46 if (isRefresh) {
- 47mPage = 1;
- 48 mCategoryICategoryView.showSwipeLoading();
- 49}else {
- 50mPage++;
- 51 }
- 52mSubscription = NetWork.getGankApi()
- 53 .getCategoryData(mCategoryICategoryView.getCategoryName(), GlobalConfig.CATEGORY_COUNT,mPage)
- 54 .subscribeOn(Schedulers.io())
- 55 .observeOn(AndroidSchedulers.mainThread())
- 56.subscribe(newObserver() {
- 57 @Override
- 58 public void onCompleted() {
- 59
- 60 }
- 61
- 62 @Override
- 63 public void onError(Throwable e) {
- 64 mCategoryICategoryView.hideSwipeLoading();
- 65mCategoryICategoryView.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 implements ListenerWithPosition.OnClickWithPositionListener {
- 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 classCategoryFragmentextendsBaseFragmentimplements ICategoryView, OnRefreshListener, OnLoadMoreListener {
- public static finalString 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 =newCategoryPresenter(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,随手点赞。
来源: http://www.cnblogs.com/liushilin/p/6760431.html