極簡(jiǎn)的MVP設(shè)計(jì)模式配合UI框架
MVP可以實(shí)現(xiàn)視圖和數(shù)據(jù)的解耦
image
如圖所示 MVP使M與V分離 實(shí)現(xiàn)了解耦
定義BaseView 所有視圖的通用接口(activity fragment)
public interface BaseView {
/**
* 初始化數(shù)據(jù)
*/
void loadData();
/**
* 更新View的狀態(tài)
* @param state
*/
void updateState(int state);
}
updateState整個(gè)方法是由我們來(lái)根據(jù)網(wǎng)絡(luò)返回的數(shù)據(jù)或者來(lái)判斷整個(gè)View的狀態(tài),定義了三個(gè)狀態(tài)值,由此來(lái)展示loading頁(yè)面 或者 error的頁(yè)面
//加載狀態(tài)
public static final int STATUS_LOADING = 100;
//成功狀態(tài)
public static final int STATUS_SUCCESS = 101;
//失敗狀態(tài)
public static final int STATUS_ERROR = 102;
接著去定義通用的接口BaseViewImpl
public interface BaseViewImpl<D> extends BaseView{
void attachPre();
void showData(List<D> list);
}
attachPre 由View來(lái)關(guān)聯(lián)相應(yīng)的Presenter 可以在定義的BaseActivity里調(diào)用綁定Presenter
BasePresenter
public interface BasePresenter<V> {
/**
* 綁定View
* @param view
*/
void attachView(V view);
/**
* 解綁View
*/
void detachView();
}
BasePresenter的實(shí)現(xiàn)類 基類
public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter<V>{
public Context mContext;
public V mBaseView;
private CompositeSubscription mCompositeSubscription;
public BasePresenterImpl(Context context){
mContext = context;
}
@Override
public void attachView(V view) {
mBaseView = view;
}
@Override
public void detachView() {
this.mBaseView = null;
onUnsubscribe(); //解綁
}
/**
* 加載數(shù)據(jù)
*/
public abstract void loadData();
//RXjava取消注冊(cè)谐宙,以避免內(nèi)存泄露
public void onUnsubscribe() {
if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
mCompositeSubscription.unsubscribe();
}
}
public void addSubscription(Observable observable, Subscriber subscriber) {
if (mCompositeSubscription == null) {
mCompositeSubscription = new CompositeSubscription();
}
mCompositeSubscription.add(subscriber);
}
}
在BasePresenterImpl里綁定View ,由此子類可以根據(jù)View來(lái)更新?tīng)顟B(tài)
MVP就是基類的實(shí)現(xiàn)就是這么簡(jiǎn)單,現(xiàn)在來(lái)創(chuàng)建activity來(lái)試試手, 首先創(chuàng)建一個(gè)BaseActivity,抽取一些公共的代碼
public abstract class BaseActivity<T extends BasePresenter,D> extends AppCompatActivity implements BaseViewImpl<D>{
public T mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* 綁定Presenter
*/
attachPre();
View view = initView(LayoutInflater.from(this));
setContentView(view);
ButterKnife.bind(this);
findView(view);
loadData();
}
protected abstract View initView(LayoutInflater from);
public abstract void findView(View view);
@Override
protected void onDestroy() {
super.onDestroy();
if(mPresenter != null)
mPresenter.detachView();
ButterKnife.unbind(this);
}
/**
* error的統(tǒng)一處理
* @param error
*/
public void error(Throwable error){
if(error instanceof NetworkErrorException){
Toast.makeText(this,"網(wǎng)絡(luò)錯(cuò)誤", Toast.LENGTH_SHORT).show();
}
}
}
創(chuàng)建HomeActivity去訪問(wèn)網(wǎng)絡(luò)以及加載數(shù)據(jù)
public class HomeActivity extends BaseActivity<HomePresenter, NewsList.NewsEntity> {
@Bind(R.id.recy)
RecyclerView mRecy;
private NewsListAdapter mNewsListAdapter;
private LoadStuatus mLoadStuatus;
@Override
public View initView(LayoutInflater inflater) {
/**
* UI框架
*/
mLoadStuatus = new LoadStuatus(this);
/**
* 實(shí)現(xiàn)成功頁(yè)面的View
*/
mLoadStuatus.addSuccessView(inflater.inflate(R.layout.activity_home, null));
/**
* 綁定BaseView來(lái)實(shí)現(xiàn)頁(yè)面點(diǎn)擊加載
*/
mLoadStuatus.bindView(this);
return mLoadStuatus;
}
@Override
public void findView(View view) {
mRecy.setLayoutManager(new LinearLayoutManager(this));
mNewsListAdapter = new NewsListAdapter(this);
mRecy.setAdapter(mNewsListAdapter);
}
@Override
public void attachPre() {
mPresenter = new HomePresenter(this);
mPresenter.attachView(this);
}
@Override
public void showData(List<NewsList.NewsEntity> list) {
mNewsListAdapter.flush(list);
}
@Override
public void loadData() {
mPresenter.loadData();
}
/**
* 更新頁(yè)面
*/
@Override
public void updateState(int state) {
mLoadStuatus.updateView(state);
}
}
這里的loadStuatus是一個(gè)幀布局用來(lái)存放我們的三個(gè)頁(yè)面
errorPage,LoadPage,SuccessPage都是根據(jù)P層的回調(diào)來(lái)實(shí)現(xiàn)
在來(lái)看一下P層的實(shí)現(xiàn)
public class HomePresenter extends BasePresenterImpl<HomeActivity>{
public HomePresenter(Context context) {
super(context);
}
@Override
public void loadData() {
Subscription subscribe = RetrofitUtils.getinstance(mContext).buildNews().getNews()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bean -> mBaseView.showData(bean.getNews()), //onNext() 去更新頁(yè)面
error -> {
mBaseView.error(error); // 請(qǐng)求網(wǎng)絡(luò)失敗 在BaseActivity定義了公共的處理方法
mBaseView.updateState(LoadStuatus.STATUS_ERROR); //更新頁(yè)面 顯示失敗的頁(yè)面
},
() -> mBaseView.updateState(LoadStuatus.STATUS_SUCCESS));// onComplete 完成頁(yè)面更新,顯示成功的頁(yè)面
addSubscription(subscribe);
}
}
P層很簡(jiǎn)單 就是訪問(wèn)網(wǎng)絡(luò) 當(dāng)請(qǐng)求
接下來(lái)看一下我們的UI框架 定義了統(tǒng)一處理頁(yè)面狀態(tài)的狀態(tài)碼以及如何去正確的顯示頁(yè)面
public class LoadStuatus extends FrameLayout {
private static final String TAG = "TAG";
BaseView nView;
@Bind(R.id.error)
View mErrorView;
@Bind(R.id.loading)
View mLoadingView;
View successView;
//加載狀態(tài)
public static final int STATUS_LOADING = 100;
//成功狀態(tài)
public static final int STATUS_SUCCESS = 101;
//失敗狀態(tài)
public static final int STATUS_ERROR = 102;
public LoadStuatus(Context context) {
this(context, null);
}
public LoadStuatus(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadStuatus(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void addSuccessView(View view){
successView = view;
addView(successView);
successView.setVisibility(GONE);
}
public void bindView(BaseView view) {
this.nView = view;
mErrorView.setOnClickListener(v -> {
if(nView == null)
throw new NoBindViewException("沒(méi)有綁定View");
nView.loadData();
});
}
public void init() {
View.inflate(getContext(), R.layout.status, this);
ButterKnife.bind(this);
}
public void updateView(int currentStatus) {
switch (currentStatus) {
case STATUS_ERROR:
successView.setVisibility(GONE);
mErrorView.setVisibility(VISIBLE);
mLoadingView.setVisibility(GONE);
break;
case STATUS_LOADING:
successView.setVisibility(GONE);
mErrorView.setVisibility(GONE);
mLoadingView.setVisibility(VISIBLE);
break;
case STATUS_SUCCESS:
successView.setVisibility(VISIBLE);
mErrorView.setVisibility(GONE);
mLoadingView.setVisibility(GONE);
break;
}
}
public class NoBindViewException extends RuntimeException{
public NoBindViewException(String info){
super(info);
}
}
}