淺淡MVP的實戰(zhàn)演習,讓代碼結(jié)構(gòu)更簡單~
前言
講道理,這次是真的筆者很久都沒有更新blog了,主要最近維護的框架問題也是層出不窮,而且對技術(shù)交流群的解答也讓我身心疲憊,所以在這里跟關(guān)注我的人說聲抱歉,沒有定期給你們帶來福利,那么這里就給大家?guī)硪粋€重磅福利:愛吖妹紙——Retrofit & RxJava & MVP & Butterknife 的完整App.
講到最近讓我身心疲憊的問題解答,無疑是讓我在開源的路上越走越遠,雖然我不是技術(shù)大牛,卻依然被一些很簡單的問題輪番轟炸,其實筆者的內(nèi)心真的是拒絕的。不得不說,寫給技術(shù)群內(nèi)的你和群主,為什么你提問,而總沒人回你!寫的挺好。
概述
廢話也不多說,對于MVP(Model View Presenter),我相信大多數(shù)人都能說出一些的,“MVC的演化版本”,“讓Model和View完全解耦”等等,但用過MVP的人一定會覺得,在Android中,代碼很清晰,不過多了很多類。對于大多數(shù)人而言,在看MVP的Demo的時候,一眼便是慢慢的nice,然而讓自己來寫個例子,卻很頭疼寫不出來。但的確MVC模式寫起來更加像是順水推舟。只需要把自己的業(yè)務(wù)邏輯一股腦的放進Activity就成功完事兒。
不得不說,之前我們項目中的確也是用的MVC在編寫的。很簡單的會發(fā)現(xiàn)隨便一個Activity代碼都是幾百上千行,甚至還有一萬行以上的??雌饋淼拇_那么一回事兒,但是細想這個View對于布局文件,其實能做的事情特別少,實際上關(guān)于該布局文件中的數(shù)據(jù)綁定的操作,事件處理的操作都在Activity中,造成了Activity既想View又像Controller,鄙棄代碼上的不美觀來說,對于后面的閱讀代碼真的是吃力。
不信?你瞧瞧。
也許業(yè)務(wù)邏輯比較簡單的功能用MVC沒什么,但是想沒想過,如果你產(chǎn)品后面改需求怎么辦?是的,你接受產(chǎn)品需求的強奸,但還是只有忍辱偷生。在日漸復雜的業(yè)務(wù)邏輯上,你的Activity和Fragment代碼越來越多,最終導致代碼爆炸,難以維護。
網(wǎng)上瀏覽一圈,發(fā)現(xiàn)講MVP的文章比比皆是,可見MVP的歡迎度,但大多數(shù)文章都只是講理論,稍微好點的會附帶一個簡單的登錄的Demo。然而,一個簡單的demo很難讓初次接觸MVP模式的人掌握它的使用。所以愛吖妹紙應運而生。
什么是MVP
當然不能跑題,前面對 MVP 做了簡單的概述,下面還是用一個簡單的圖表示一下。
如上圖所示,在項目中 View 和 Model 并不直接交互,而是使用 Presenter 作為 View 和 Model 之間的橋梁。其中 Presenter 中同時持有 View 層以及 Model 層的 Interface 的引用,而 View 層持有 Presenter 層 Interface 的引用,當 View 層某個頁面需要展示某些數(shù)據(jù)的時候,首先會調(diào)用Presenter 層的某個接口,然后 Presenter 層會調(diào)用 Model 層請求數(shù)據(jù),當 Model 層數(shù)據(jù)加載成功之后會調(diào)用 Presenter 層的回調(diào)方法通知 Presenter 層數(shù)據(jù)加載完畢,*** Presenter 層再調(diào)用 View 層的接口將加載后的數(shù)據(jù)展示給用戶。這就是 MVP 模式的核心過程。
這樣分層的好處就是大大減少了Model與View層之間的耦合度。一方面可以使得View層和Model層單獨開發(fā)與測試,互不依賴。另一方面Model層可以封裝復用,可以極大的減少代碼量。當然,MVP還有其他的一些優(yōu)點,這里不再贅述。
功能展示
這里就給大家隨便看看干貨板塊的功能吧。
布局相當簡單。
- <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,支持下拉刷新和上拉加載數(shù)據(jù)。所以我們的 Presenter 和 View 只需要定義一下簡單的方法。
1)加載數(shù)據(jù)的過程中顯示加載的進度條;
2)加載數(shù)據(jù)成功提醒 Adapter 刷新數(shù)據(jù);
3)加載失敗談窗提醒用戶相關(guān)信息;
4)加載結(jié)束隱藏進度條;
- 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 實現(xiàn)類。
- 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()+" 列表數(shù)據(jù)獲取失??!");
- }
- @Override
- public void onNext(CategoryResult categoryResult) {
- if (isRefresh){
- mCategoryICategoryView.setCategoryItems(categoryResult);
- mCategoryICategoryView.hideSwipeLoading();
- mCategoryICategoryView.setLoading();
- }else {
- mCategoryICategoryView.addCategoryItems(categoryResult);
- }
- }
- });
- }
- }
編寫Adapter,用于展示數(shù)據(jù)。
- 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,"跳轉(zhuǎn)到相應網(wǎng)頁!", 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("沒有更多數(shù)據(jù)");
- }
- }
項目截圖
還是給大家看看項目截圖,以免大家心慌。
結(jié)束語
愛吖妹紙是運用 MVP,Retrofit,RxJava 等主流框架整合的干貨 App,項目資源來源于代碼家的干貨集中營。代碼量不多,但基本涉及了各個方面,界面采用design風格,所以也是學習design的良藥。作者也是希望繼續(xù)在開源路上越走越遠,還請大家支持。