作者
本文由 南尘 投稿,github地址:https://github.com/nanchen2251
前言
讲道理,这次是真的笔者很久都没有更新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还有其他的一些优点,这里不再赘述。
功能展示
这里就给大家随便看看干货板块的功能吧。
布局相当简单。
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.RecyclerViewWithFooter
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
干货模块,也就是一个Fragment,里面有一个RecyclerView,支持下拉刷新和上拉加载数据。所以我们的 Presenter 和 View 只需要定义一下简单的方法。
1)加载数据的过程中显示加载的进度条;
2)加载数据成功提醒 Adapter 刷新数据;
3)加载失败谈窗提醒用户相关信息;
4)加载结束隐藏进度条;
public interface CategoryContract {
interface ICategoryView extends BaseView{
void getCategoryItemsFail(String failMessage);
void setCategoryItems(CategoryResult categoryResult);
void addCategoryItems(CategoryResult categoryResult);
void showSwipeLoading();
void hideSwipeLoading();
void setLoading();
String getCategoryName();
void noMore();
} interface ICategoryPresenter extends BasePresenter{
void getCategoryItems(boolean isRefresh);
}
}
编写 Presenter 实现类。
public class CategoryPresenter implements ICategoryPresenter {
private ICategoryView mCategoryICategoryView;
private int mPage = 1;
private Subscription mSubscription;
public CategoryPresenter(ICategoryView androidICategoryView) {
mCategoryICategoryView = androidICategoryView;
}
@Override
public void subscribe() {
getCategoryItems(true);
}
@Override
public void unSubscribe() {
if (mSubscription != null && !mSubscription.isUnsubscribed()){
mSubscription.unsubscribe();
}
} @Override
public void getCategoryItems(final boolean isRefresh) {
if (isRefresh) {
mPage = 1;
mCategoryICategoryView.showSwipeLoading();
} else {
mPage++;
}
mSubscription = NetWork.getGankApi()
.getCategoryData(mCategoryICategoryView.getCategoryName(), GlobalConfig.CATEGORY_COUNT,mPage)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<CategoryResult>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
mCategoryICategoryView.hideSwipeLoading();
mCategoryICategoryView.getCategoryItemsFail(mCategoryICategoryView.getCategoryName()+" 列表数据获取失败!");
}
@Override
public void onNext(CategoryResult categoryResult) {
if (isRefresh){
mCategoryICategoryView.setCategoryItems(categoryResult);
mCategoryICategoryView.hideSwipeLoading();
mCategoryICategoryView.setLoading();
}else {
mCategoryICategoryView.addCategoryItems(categoryResult);
}
}
});
}
}
编写Adapter,用于展示数据。
class CategoryRecyclerAdapter extends CommonRecyclerAdapter<CategoryResult.ResultsBean> implements
ListenerWithPosition.OnClickWithPositionListener<CommonRecyclerHolder>{
CategoryRecyclerAdapter(Context context) {
super(context, null, R.layout.item_category);
} @Override
public void convert(CommonRecyclerHolder holder, ResultsBean resultsBean) {
if (resultsBean != null) {
ImageView imageView = holder.getView(R.id.category_item_img);
if (ConfigManage.INSTANCE.isListShowImg()) { // 列表显示图片
imageView.setVisibility(View.VISIBLE);
String quality = "";
if (resultsBean.images != null && resultsBean.images.size() > 0) {
switch (ConfigManage.INSTANCE.getThumbnailQuality()) {
case 0: // 原图
quality = "";
break;
case 1: //
quality = "?imageView2/0/w/400";
break;
case 2:
quality = "?imageView2/0/w/190";
break;
}
Glide.with(mContext)
.load(resultsBean.images.get(0) + quality)
.placeholder(R.mipmap.image_default)
.error(R.mipmap.image_default)
.into(imageView);
} else { // 列表不显示图片
Glide.with(mContext).load(R.mipmap.image_default).into(imageView);
}
} else {
imageView.setVisibility(View.GONE);
}
holder.setTextViewText(R.id.category_item_desc, resultsBean.desc == null ? "unknown" : resultsBean.desc);
holder.setTextViewText(R.id.category_item_author, resultsBean.who == null ? "unknown" : resultsBean.who);
holder.setTextViewText(R.id.category_item_time, TimeUtil.dateFormat(resultsBean.publishedAt));
holder.setTextViewText(R.id.category_item_src, resultsBean.source == null ? "unknown" : resultsBean.source);
holder.setOnClickListener(this, R.id.category_item_layout);
}
} @Override
public void onClick(View v, int position, CommonRecyclerHolder holder) {
//Toasty.info(mContext,"跳转到相应网页!", Toast.LENGTH_SHORT,true).show();
Intent intent = new Intent(mContext, WebViewActivity.class);
intent.putExtra(WebViewActivity.GANK_TITLE, mData.get(position).desc);
intent.putExtra(WebViewActivity.GANK_URL, mData.get(position).url);
mContext.startActivity(intent);
}
}
最后当然是 Fragment。
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("没有更多数据");
}
}
项目截图
还是给大家看看项目截图,以免大家心慌。
结束语
爱吖妹纸是运用 MVP,Retrofit,RxJava 等主流框架整合的干货 App,项目资源来源于代码家的干货集中营。代码量不多,但基本涉及了各个方面,界面采用design风格,所以也是学习design的良药。作者也是希望继续在开源路上越走越远,还请大家支持。查看详细源码请点击“阅读原文”,可以和作者交流关注他的github,欢迎大家点赞和评论。
“碎片化”无法终结,安卓终将走向封闭?【Bus Weekly】48期