MVP介紹
- M:Model禁炒,需要顯示的數據,以及獲取和保存數據的相關邏輯
- V:View霍比,顯示數據的頁面或空間幕袱,并接受用戶的交互
- P:Presenter,處于M和V中間悠瞬,是對產品交互的抽象们豌。決定M由哪個V顯示,V的動作會引起哪些數據的變化浅妆。
如下為典型的MVP工作流程
需要注意的點:
- Presenter不應該直接處理View的事件
- View只應向Presenter傳遞消息望迎,并接受Presenter的命令
- Activity和Fragment是View的一部分,一般可用于處理用戶事件
- Presenter和Model應該是純Java代碼凌外,而且可以獨立的運行單元測試
Mosby介紹
gradle依賴
dependencies {
//...
compile 'com.hannesdorfmann.mosby:mvp:2.0.1'
compile 'com.hannesdorfmann.mosby:viewstate:2.0.1'
//...
}
MvpView和MvpPresenter
- MvpView是個空接口。在實際使用時摄欲,會擴展這個接口來定義一系列的View的方法
- MvpView會依附或脫離于MvpPresenter蒿涎。庫中定義好的一些MvpView使用代理模式實現了依附和脫離的邏輯惦辛。
- MvpPresenter通過軟引用訪問View,從而避免內存泄漏
public interface MvpView { }
public interface MvpPresenter<V extends MvpView> {
public void attachView(V view);
public void detachView(boolean retainInstance);
}
通過MvpLceFragment學習使用MVP
LCE就是Loading-Content-Error玻淑,代表了一個典型的移動互聯網應用的頁面补履。
- 顯示LoadingView箫锤,并在后臺獲取數據
- 如果獲取成功,顯示獲取的到數據
- 如果失敗谚攒,顯示一個錯誤的提示View
先看看MvpLceView
/**
* @param <M> The type of the data displayed in this view
*/
public interface MvpLceView<M> extends MvpView {
/**
* Display a loading view while loading data in background.
* <b>The loading view must have the id = R.id.loadingView</b>
*
* @param pullToRefresh true, if pull-to-refresh has been invoked loading.
*/
public void showLoading(boolean pullToRefresh);
/**
* Show the content view.
*
* <b>The content view must have the id = R.id.contentView</b>
*/
public void showContent();
/**
* Show the error view.
* <b>The error view must be a TextView with the id = R.id.errorView</b>
*
* @param e The Throwable that has caused this error
* @param pullToRefresh true, if the exception was thrown during pull-to-refresh, otherwise
* false.
*/
public void showError(Throwable e, boolean pullToRefresh);
/**
* The data that should be displayed with {@link #showContent()}
*/
public void setData(M data);
}
實現MvpLceView的控件或頁面一定要包含至少3個View馏臭,他們的id分別為R.id.loadingView
,R.id.contentView
和 R.id.errorView
,因此我們使用如下的xml為Fragment布局绕沈。
庫工程中的MvpLceFragment
和MvpLceActivity
已經實現了MvpLceView
的三個方法
showLoading
乍狐,showContent
和showError
固逗,
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- Loading View -->
<ProgressBar
android:id="@+id/loadingView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
/>
<!-- Content View -->
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SwipeRefreshLayout>
<!-- Error view -->
<TextView
android:id="@+id/errorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</FrameLayout>
這個頁面中將顯示一個從網絡獲取的國家列表掘鄙,先看看Presenter的代碼嗡髓。這里通過CountriesAsyncLoader獲取國家列表,并通過setData和showContent讓View顯示這些國家信息浊伙。當然還獲取前顯示Loading嚣鄙,獲取失敗后顯示Error串结。
public class CountriesPresenter extends MvpBasePresenter<CountriesView> {
@Override
public void loadCountries(final boolean pullToRefresh) {
getView().showLoading(pullToRefresh);
CountriesAsyncLoader countriesLoader = new CountriesAsyncLoader(
new CountriesAsyncLoader.CountriesLoaderListener() {
@Override public void onSuccess(List<Country> countries) {
if (isViewAttached()) {
getView().setData(countries);
getView().showContent();
}
}
@Override public void onError(Exception e) {
if (isViewAttached()) {
getView().showError(e, pullToRefresh);
}
}
});
countriesLoader.execute();
}
}
最后是MvpLceFragment肌割,注意其中的createPresenter
是所有的MvpView都需要實現的方法,用于創(chuàng)建和MvpView關聯的Presenter弥奸,另一個setData
兩個方法是MvpLceFragment中沒有實現的方法奋早,因為只有實現的時候才知道最終的Model,已經如何顯示這個Model愤炸。
另一個要注意的是MvpLceFragment的四個范型,依次是:顯示內容的AndroidView,需要顯示的內容Model挥萌,MvpView引瀑,MvpPresenter。
public class CountriesFragment
extends MvpLceFragment<SwipeRefreshLayout, List<Country>, CountriesView, CountriesPresenter>
implements CountriesView, SwipeRefreshLayout.OnRefreshListener {
@Bind(R.id.recyclerView) RecyclerView recyclerView;
CountriesAdapter adapter;
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.countries_list, container, false);
}
@Override public void onViewCreated(View view, @Nullable Bundle savedInstance) {
super.onViewCreated(view, savedInstance);
// Setup contentView == SwipeRefreshView
contentView.setOnRefreshListener(this);
// Setup recycler view
adapter = new CountriesAdapter(getActivity());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
loadData(false);
}
public void loadData(boolean pullToRefresh) {
presenter.loadCountries(pullToRefresh);
}
@Override protected CountriesPresenter createPresenter() {
return new SimpleCountriesPresenter();
}
@Override public void setData(List<Country> data) {
adapter.setCountries(data);
adapter.notifyDataSetChanged();
}
@Override public void onRefresh() {
loadData(true);
}
}
實戰(zhàn)——封裝一個LceListView
這部分代碼可參考代碼帜矾,只用關注mvplist包下的相關代碼即可屡萤。
這個頁面的View結構和之前的MvpLceFrgment類似死陆,并通過修改Adapter給RecycleView的末尾增加了一個LoadMoreView唧瘾。將這類業(yè)務的下拉刷新,上拉加載更多领虹,以及錯誤處理都抽象出來塌衰。實現列表時,剩下的邏輯主要包括
- Model:獲取的數據猾蒂,以及從數據中獲取每個列表項展示需要的數據列表
- Presenter:刷新和加載更多時肚菠,分別調用Model的獲取數據方法
- View:根據數據決定ViewHolder的類型罩缴,以及ViewHolder的實現
如何使用,可以參考mvplist.sample包的內容
基類LecListView的方案
LecListView包含了一個LoadMoreView烙荷,LoadMoreView也符合Mvp架構。
我們先看看LoadMoreView的實現戳表。
LoadMoreView
- 沒有Model:這里的數據只包括一個狀態(tài)匾旭,因此沒有對應的Model類
- LoadMoreView
- 提供一個
setState(int state)
方法圃郊,供Presenter更新狀態(tài) - 加載更多失敗的情況下,點擊View會請求Presenter更改狀態(tài)到Loading
- 提供一個
- LoadMorePresenter
- 通過
setLoadMoreState(int state)
改變View的狀況 - 通過接口
LoadMoreListener#onLoadMore
通知LceListViewPresenter加載更多數據
- 通過
LecListView
- IListModel:在
LceListView
中色瘩,Model應實現IListModel居兆,從而提供一個在RecycleView中顯示的數據列表史辙。
```Java
public interface IListModel<M> {
List<M> getData();
}
```
- LceListView
- 實現了
MvpLceView
的五個方法佩伤,和在列表底部添加數據的addData
方法 - 監(jiān)聽RecyclerView的滾動,通知Presenter改變加載更多的狀態(tài)
- 使用LceListAdapter為RecyclerView底部增加了LoadMoreView
- 實現了
- LceListPresenter實現ILceListPresenter
- 在refreshData時耙蔑,通知LceListView顯示Loading
- 包含一個LoadMorePresenter來實現ILoadMorePresenter的接口
- 在LoadMoreView附著在窗口時甸陌,調用
LceListPresenter#setLoadMorePresenter