Android当中的MVP模式(六)View 层 Activity 的基类--- BaseMvpActivity 的封装
个人博客:CODE FRAMER BIGZ
MVP系列文章配套DEMO
Android 当中的 MVP 模式(一)基本概念
Android 当中的 MVP 模式(二)封装
Android 当中的 MVP 模式(三)基于分页列表的封装
Android 当中的 MVP 模式(四)插曲-封装 OkHttp
Android 当中的 MVP 模式(五)封装之后的 OkHttp 工具在 Model 层的使用
Android 当中的 MVP 模式(六)View 层 Activity 的基类— BaseMvpActivity 的封装
Android 当中的 MVP 模式(七)终篇—关于对 MVP 模式中代码臃肿问题的思考
摘要:使用封装之后的 MVP
模式实现一个新的界面,也就是 View
层,那么就需要去实现 IBaseView
接口,可能还需要针对当前要实现的界面情况,在 IBaseView
的基础之上派生出一个新的接口 IXxxView
,之前的 SohuAlbumInfoActivity
用于展示搜狐电视剧主要信息的 View
就是这种情况,由 IBaseView
派生了一个 ISohuSerials
,再由 SohuAlbumInfoActivity
去实现,那么随着需要展示的界面越来越多,它们坐着大量重复的工作,我们就要像个方法来简化这个过程了。
回顾前几篇中 View 层的写法
根据MVP系列第二篇当中的分析, View
层的职责如下:
- Loading 状态的展示隐藏
- 接收 Presenter 层处理后的数据
- 接收 Presenter 层处理后的错误信息
- 接收 Presenter 层处理后的服务器拒绝信息
所以当就将着一些职责抽象成方法,放在 IBaseView
接口中,看看之前的的 IBaseView
:
/** * Created by fanyuzeng on 2017/10/20. * @author : ZengFanyu */ public interface IBaseView { /** * 进行耗时操作时的用户友好交互接口,比如显示ProgressBar * * @param isShow * @author zfy * @created at 2017/10/21/021 14:12 */ void showProgress(boolean isShow); /** * 显示网络请求错的的接口 * * @param errorCode * @param errorDesc * @param errorUrl * @author zfy * @created at 2017/10/21/021 14:14 */ void showOkHttpError(int errorCode, String errorDesc, String errorUrl); /** * 现实服务器端请求错误的接口 * * @param errorCode * @param errorDesc * @author zfy * @created at 2017/10/21/021 14:14 */ void showServerError(int errorCode, String errorDesc); /** * 请求成功或者失败之后,对应UI做出改变的接口 * * @param isSuccess * @author zfy * @created at 2017/10/21/021 14:15 */ void showSuccess(boolean isSuccess);
View
层每需要添加一个类, View
层的对象都需要在它的基础上去实现,比如说,在 MVP系列第三篇中,需要对搜狐视频电视剧频道的主要信息做分页展示,当时是在 IBaseView
的基础上派生出了一个 ISohuSerials
:
/** * 展示搜狐电视剧频道具体信息的接口 * * @author:ZengFanyu */ public interface ISohuSerials extends IBaseView { /** * 展示搜狐视频API电视剧主要信息的方法 * * @param videoList 处理好的VideoInfo集合 */ void showAlbumMainInfo(List videoList); }
然后再使用 SohuAlbumInfoActivity
去实现这个接口,对 IBaseView
和 ISohuSerials
中的方法又做了一遍实现,但是这次的实现过程,跟MVP系列第二篇中的 LatestNewsTitleActivity
实现的功能几乎一致,并且这个时候,我就意识到 IBaseView
接口设计的缺陷,我们在 IBaseView
的基础上派生出 ISohuSerials
接口 ILatestNewsView
接口,无非就是要展示不同类型的数据,那这个功能完全可以整合进 IBaseView
接口中,至于不同页面的数据类型不同,我们完全可以使用泛型来解决。
下面就来解决这两个问题:
- 将展示
Presenter
层实例好的数据的方法,由派生接口整合至基类接口中,使用泛型解决数据类型不同的问题。 - 封装
BaseMvpActivity
,实现共有逻辑,子类不重复处理View
层基类接口(IBaseView
)中的方法。
IBaseView 的重构
再回顾一下,之前要展示知乎日报的最新消息的标题内容,我写了一个 ILatestNewsView
接口,它长这样:
public inerface ILatestNewsView extends IBaseView { void showLatestNewsTitle(List titles) }
后来又需要展示搜狐电视剧主要信息,于是写了一个 ISohuSerials
,:
public interface ISohuSerials extends IBaseView { void showAlbumMainInfo(List videoList); }
当时怎么想的,要整个这接口出来 - - !
现在把他们都整合进 IBaseView
:
public interface IBaseView<Data> { //省略代码 /** * 显示presenter层处理好之后的数据 * @param data data */ void showDataFromPresenter(Data data);
此处添加了一个泛型 Data
,它就可以用于指代上面两个接口中的 List
和 List
,或者是其他的数据了类型,然后在实现接口的类中去指明参数的类型就可以动态的更改它的类型了。
BaseMvpActivity 的封装
上述是对之前遗留问题的一个解决,从这儿开始才正式对基类 BaseMvpActivity
进行封装。
ToolBar 的统一处理
首先, Demo
是在 API 25
,所以对 ToolBar
也要有良好的支持,所以首先是对 ToolBar
的封装,将 ToolBar
写到一个单独的 Layout
文件之中,方便其他文件引用。top_action_bar
:
"http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"> "@+id/id_tool_bar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:titleTextColor="#ffffff" >
在 BaseMvpActivity
中的统一处理如下:
protected void setSupportActionBar() { if (mToolbar != null) { setSupportActionBar(mToolbar); } } protected void setActionBarIcon(int resId) { if (mToolbar != null) { mToolbar.setNavigationIcon(resId); } } protected void setSupportArrowActionBar(boolean isSupport) { getSupportActionBar().setDisplayHomeAsUpEnabled(isSupport); }
这样处理了之后,在子类当中,就可以直接调用上述方法,就可以使用 ToolBar
了, 当然,对 ToolBar
的自定义需要另外去处理。
BaseMvpActivity 的布局文件的处理
由于 BaseMvpActivity
是要作为 MVP
模式下,所有 View
层的基类,所以它自己需要有布局文件,将 IBaseView
中的接口实现,
activity_base_mvp
:
"http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> "@layout/top_action_bar"/> "@+id/id_tip_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:textSize="16sp" android:text="tip"/> "match_parent" android:layout_height="match_parent"> "@+id/id_content_container" android:layout_width="match_parent" android:layout_height="match_parent"/> "@+id/id_progress_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone"/>
- 第
7
行的include
文件,就是对上面top_action_bar
的引用。 - 第
10
行的TextView
这里用来实现IBaseView
中showOkHttpError
和showServerError
接口的。 - 第
22
行的FrameLayout
很重要,看id
就知道了,它是用于展示子类页面的方法的,直接将子类的布局文件给add
进来。类似于:
View contentView = LayoutInflater.from(this).inflate(R.layout.activity_album_view, null); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); mContentContainer.addView(contentView, lp);
- 第
27
行的ProgressBar
就是用于实现IBaseView
中showProgress
的。
BaseMvpActivity 对 IBaseView 的实现
/** * @author:fanyuzeng * @date: 2017/10/30 13:50 * @desc: */ public abstract class BaseMvpActivity<Data> extends AppCompatActivity implements IBaseView<Data> { private static final String TAG = "BaseMvpActivity"; protected Toolbar mToolbar; protected ProgressBar mProgressBar; protected TextView mTipView; protected FrameLayout mContentContainer; protected Handler mHandler = new Handler(Looper.getMainLooper()); protected Context mContext; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_base_mvp); mContext = this; mToolbar = bindViewId(R.id.id_tool_bar); mProgressBar = bindViewId(R.id.id_progress_bar); mTipView = bindViewId(R.id.id_tip_content); mContentContainer = bindViewId(R.id.id_content_container); beforeInitViews(); initViews(); afterInitViews(); } protected T bindViewId(int resId) { return (T) findViewById(resId); } //统一处理ToolBar @Override public void showProgress(final boolean isShow) { mHandler.post(new Runnable() { @Override public void run() { if (isShow) { mProgressBar.setVisibility(View.VISIBLE); } else { mProgressBar.setVisibility(View.GONE); } } }); } @Override public void showOkHttpError(final int errorCode, final String errorDesc, final String errorUrl) { mHandler.post(new Runnable() { @Override public void run() { mTipView.setText("errorCode:" + errorCode + ",errorDesc:" + errorDesc + ",errorUrl:" + errorUrl); } }); } @Override public void showServerError(final int errorCode, final String errorDesc) { mHandler.post(new Runnable() { @Override public void run() { mTipView.setText("errorCode:" + errorCode + ",errorDesc:" + errorDesc); } }); } @Override public void showSuccess(final boolean isSuccess) { mHandler.post(new Runnable() { @Override public void run() { if (isSuccess) { mContentContainer.setBackgroundResource(android.R.color.white); } else { mContentContainer.setBackgroundResource(R.color.colorAccent); } } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: finish(); break; default: break; } return super.onOptionsItemSelected(item); } /** * 子类实现,用于初始化控件 */ protected abstract void initViews(); /** * 子类实现 在初始化控件之后进行的操作 */ protected abstract void afterInitViews(); /** * 子类实现, 在初始化控件之前的操作 */ protected abstract void beforeInitViews(); }
做了几点事情
- 实现了
IBaseView
中的接口 - 对
ToolBar
做统一处理 findViewById
方法处理Menu Item
中返回按键的处理
还有一个问题,似乎少了一个方法?就是在上一小节中,整合进 IBaseView
接口中的 void showDataFromPresenter(Data data)
,还没有实现。
由于这里的 BaseMvpView
是 abstract
的,所以它可以不实现,也实现不了,因为实现这方法需要知道泛型参数 Data
的具体类型,所以这个函数是留给子类去实现的。
上面三个抽象方法也很好理解,就是用于子类初始化操作的,并且都在基类初始化之后才执行,这一点很重要,因为子类中是需要将布局文件给 add
到基类布局当中的,所以基类的组件也必须提前初始化好。
下面就看看子类中是如何处理的。
SohuAlbumInfoActivity 的重构
/** * @author:ZengFanyu * Function: */ public class SohuAlbumInfoActivity extends BaseMvpActivity<List<VideoInfo>> { private static final String TAG = "SohuAlbumInfoActivity"; private PullLoadRecyclerView mRecyclerView; private AlbumPresenter mAlbumPresenter; private BasePaginationParam mParam = new BasePaginationParam(1, 10); private VideoInfoAdapter mAdapter; private boolean mIsFromRefresh = false; // private View mContentView; @Override protected void beforeInitViews() { mRecyclerView = new PullLoadRecyclerView(this); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); mContentContainer.addView(mRecyclerView, lp); // View contentView = LayoutInflater.from(this).inflate(R.layout.activity_album_view, null); // FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); // mContentContainer.addView(contentView, lp); } @Override protected void initViews() { setSupportActionBar(); //表示当前页面支持ActionBar setTitle(TAG); setSupportArrowActionBar(true); mAlbumPresenter = new AlbumPresenter(this, Album.class); mTipView.setText(TAG); // mRecyclerView = (PullLoadRecyclerView)mContentView.findViewById(R.id.id_recycler_view); mRecyclerView.setLinearLayout(); mAdapter = new VideoInfoAdapter(mContext); mAlbumPresenter.requestServer(mParam); mRecyclerView.setAdapter(mAdapter); mRecyclerView.setOnPullLoadMoreListener(new PullLoadRecyclerView.OnPullLoadMoreListener() { @Override public void onRefresh() { mIsFromRefresh = true; mParam.setPageIndex(1); mAlbumPresenter.refresh(mParam); mRecyclerView.setRefreshCompleted(); } @Override public void onLoadMore() { mAlbumPresenter.loadingNext(); mRecyclerView.setLoadMoreCompleted(); } }); } @Override protected void afterInitViews() { } @Override public void showDataFromPresenter(List albumList) { if (mIsFromRefresh) { mAdapter.cleanData(); mIsFromRefresh = false; } if (albumList != null && albumList.size() > 0) { for (VideoInfo videoInfo : albumList) { mAdapter.addData(videoInfo); } mHandler.post(new Runnable() { @Override public void run() { mAdapter.notifyDataSetChanged(); mTipView.setText(TAG); } }); } } }
15
行的beforeInitViews
方法,就是用于初始化子类的布局的,由于这个子类布局比较简单, 就是一个RecyclerView
,所以可以直接用代码实现,然后给add
进父类的mContentContainer
,或者用下面注释掉的,常规尝试来实现。- 第
5
行,泛型参数为List
,这个参数就是用于上面提到的,未实现的方法当中的,指定了泛型参数的类型。 - 第
26
行的initViews
方法就用户初始化子类的View
showDataFromPresenter
的写法和未封装之前是一样的。
LatestNewsTitleActivity 的重构
/** * @author ZengFanyu */ public class LatestNewsTitleActivity extends BaseMvpActivity<List<String>> { private ListView mListView; private LatestNewsPresenter mBasePresenter; LatestNewsAdapter mAdapter; private View mContentView; @Override protected void beforeInitViews() { mContentView = LayoutInflater.from(this).inflate(R.layout.activity_latest_news, null); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); mContentContainer.addView(mContentView, lp); } @Override protected void initViews() { mBasePresenter = new LatestNewsPresenter(this, LatestNews.class); mTipView.setText(LatestNews.class.getSimpleName()); mListView = (ListView) mContentView.findViewById(R.id.id_list_view); Button btnLatestNews = (Button) mContentView.findViewById(R.id.id_btn_latest_news); btnLatestNews.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mBasePresenter.requestServer(null); } }); } @Override protected void afterInitViews() { } @Override public void showDataFromPresenter(List titles) { if (mAdapter != null) { mAdapter.clear(); mAdapter = null; } mAdapter = new LatestNewsAdapter(titles, mContext); mListView.setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); mTipView.setText(LatestNews.class.getSimpleName()); } }
写法和上面一样,但是比起之前的代码量来说,已经少了很多了,并且对比这两个子类,都没有重复的实现方法,只专注于自己需要实现的逻辑。
还有其他的 View
层类和上述的实现过程类似,此处不再赘述。
下一篇这个系列的最后一篇准备些关于
MVP
模式在开发中使用,随着项目的复杂程度的提高,Presenter
会越来越臃肿的问题的解决思路。
个人博客地址 :CODER FRAMER BIGZ
更多相关文章
- 用fastboot大刷Android ~换个方法刷android手机
- [Android]ScrollView和ListView套用冲突的解决方法
- Android Camera的接口与架构介绍
- Android页面去掉标题全屏的方法-第一次用安卓试试看
- android 中使文本(TextView 、button等可参考)在屏幕正中心显示的
- android屏幕自适应4方法案例整合
- 打开SDK Manager检查Android SDK下载和更新失败的解决方法
- android studio 3.6.0 绑定视图新特性的方法
- androidSDK无法更新的解决方法之一