之前在做項(xiàng)目時(shí)用到了mvp模式腕够,由于時(shí)間緊,沒(méi)來(lái)得急進(jìn)行抽基礎(chǔ)類舌劳,趁現(xiàn)在有時(shí)間進(jìn)行整理一下帚湘,并對(duì)應(yīng)做下筆記,供以后方便使用甚淡。先扯一下M大诸、V、P作用吧:
- m (Model ) : Model 層包含著具體的數(shù)據(jù)請(qǐng)求贯卦,數(shù)據(jù)源资柔。
- V (View ) : Activity 和Fragment 視為View層,負(fù)責(zé)處理 UI撵割。
- P (Presenter ) : Presenter 為業(yè)務(wù)處理層贿堰,既能調(diào)用UI邏輯,又能請(qǐng)求數(shù)據(jù)啡彬,該層為純Java類羹与,不涉及任何Android API。
M層是處理數(shù)據(jù)請(qǐng)求的外遇,放到后期再說(shuō)注簿,今天先整理一下V契吉、P兩層跳仿。
如下圖:
根據(jù)UI層常用的方法抽出對(duì)應(yīng)的接口,封裝成BaseView捐晶,代碼如下:
/**
* 類描述:給頁(yè)面基類提供的共用接口
*/
public interface BaseView {
/**
* 獲取上下文
*
* @return 上下文
*/
Context getContext();
/**
* Show loading
*/
void showLoading();
/**
* hide loading
*/
void hideLoading();
/**
* 顯示提示
*
* @param msg
*/
void showToast(String msg);
/**
* 輸出打印
*
* @param msg 打印內(nèi)容
*/
void log(String msg);
}
Presenter業(yè)務(wù)層要與view層進(jìn)行交互菲语,所以需要持有view層的對(duì)象,持有view對(duì)象就有可能出現(xiàn)內(nèi)存泄漏或者空指針問(wèn)題惑灵,所以在抽BasePresenter時(shí)山上,進(jìn)行對(duì)view對(duì)象做對(duì)應(yīng)的處理,因?yàn)関iew層可能是activity英支,也可能是fragment佩憾,所以此處用到了類的泛型,代碼如下:
public class BasePresenter<T extends BaseView> {
/**
* 綁定的view
*/
protected T mView;
/**
* 綁定view,一般在初始化中調(diào)用該方法
*/
public void attachView(T view) {
this.mView = view;
}
/**
* 斷開view妄帘,一般在onDestroy中調(diào)用
*/
public void detachView() {
mView = null;
}
/**
* 獲取連接的view
*/
public T getView() {
return mView;
}
/**
* 是否與View建立連接
* 每次調(diào)用業(yè)務(wù)請(qǐng)求的時(shí)候都要出先調(diào)用方法檢查是否與View建立連接
*/
public boolean isViewAttached() {
return mView != null;
}
}
到這就已經(jīng)抽好了view和Presenter層的基類了楞黄,為了方便管理業(yè)務(wù)層與UI的交互,咱們得能使其相互通信抡驼,否則業(yè)務(wù)層怎么知道UI層的需求和狀態(tài)呢鬼廓,此時(shí)就用一個(gè)契約類來(lái)進(jìn)行管理,此契約類中是抽出的Presenter與view交互的接口(使用時(shí)致盟,可以根據(jù)自己的UI需求碎税,進(jìn)行相應(yīng)的添加接口),即發(fā)出指令和指令回應(yīng)馏锡,代碼如下:
/**
* 類描述:管理Presenter與Activity交互的view的接口
* Presenter 與 View 的契約(交互)
* 注明:可以添加各個(gè)業(yè)務(wù)對(duì)應(yīng)view與Presenter交互接口
*/
public interface PresenterViewContract {
//通過(guò)以下接口把與UI層相關(guān)的數(shù)據(jù)通知給UI層
interface View extends BaseView {
//接口成功
void onResultSucceed(String msg);
//接口失斃柞濉(此處包括接口異常)
void onResultFail(int retCode, Exception exception);
}
interface Presenter {
//接到UI的指令,進(jìn)行處理數(shù)據(jù)
void getData_1(String keyword);
}
}
下面咱們就進(jìn)行對(duì)業(yè)務(wù)(Presenter)層實(shí)現(xiàn)杯道,直接上代碼啦
/**
* 類描述:MVP模式的P模式萎河,用于V和M的數(shù)據(jù)交互使用
* 修改備注:
*/
public class DataPresenter extends BasePresenter<PresenterViewContract.View> implements PresenterViewContract.Presenter {
/**
* 關(guān)閉數(shù)據(jù)加載進(jìn)度提示
*/
private void cancelDialog() {
if (isViewAttached()) {
getView().hideLoading();
}
}
/**
* 展示數(shù)據(jù)加載進(jìn)度
*/
private void showDialog() {
if (isViewAttached()) {
getView().showLoading();
}
}
/**
* 接到UI層的指令,發(fā)起數(shù)據(jù)請(qǐng)求
* @param msg 假數(shù)據(jù)蕉饼,msg為空代表返回失敗結(jié)果虐杯;msg不為空返回成功結(jié)果
*/
@Override
public void getData_1(String msg) {
// TODO: 2020/9/15 此處接口調(diào)用屬于Model層,因?yàn)楣疽延凶约旱囊惶讛?shù)據(jù)包處理昧港,所以此處就用假數(shù)據(jù)代替了
if (isViewAttached()) {//判斷是否關(guān)聯(lián)View
if (TextUtils.isEmpty(msg)) {
cancelDialog();
//通知view層數(shù)據(jù)請(qǐng)求失敗
getView().onResultFail(-2, null);
} else {
showDialog();
//通知View層數(shù)據(jù)請(qǐng)求成功
getView().onResultSucceed(msg);
}
}
}
}
到這view和Presenter的契約就簽好了擎椰,那咱們就開始對(duì)activity和fragment的基礎(chǔ)類進(jìn)攻,它們需要去實(shí)現(xiàn)抽好的BaseView基類的接口(不一定都實(shí)現(xiàn)创肥,用到那個(gè)就實(shí)現(xiàn)那個(gè))达舒,代碼如下:
/**
* 類描述:Activity的基類
* 修改備注:
*/
public class BaseActivity extends AppCompatActivity implements BaseView {
private String TAG = "";
private Toast toast;
private BaseFragment currentFragment = null;//(全局)
//Loading框的對(duì)象
private ProgressWaitDialog skyProgressWaitDialog;
//mvp模式的p對(duì)象
private DataPresenter dataPresenter = null;
public DataPresenter getDataPresenter() {
if (dataPresenter == null) {//初始化P,用于綁定view
dataPresenter = new DataPresenter();
}
return dataPresenter;
}
@Override
public void showLoading() {//展示全局的接口加載進(jìn)度提示
if (skyProgressWaitDialog == null) {
skyProgressWaitDialog = new ProgressWaitDialog(this);
}
skyProgressWaitDialog.show();
}
@Override
public void hideLoading() {//關(guān)閉提示
if (skyProgressWaitDialog != null) {
skyProgressWaitDialog.cancel();
}
}
@Override
public void showToast(String msg) {
if (toast == null) {
// 創(chuàng)建土司
toast = new Toast(this);
// 設(shè)置土司顯示的時(shí)間長(zhǎng)短
toast.setDuration(Toast.LENGTH_LONG);
// 創(chuàng)建ImageView
ImageView img = new ImageView(this);
// 設(shè)置圖片的資源路徑
img.setImageResource(R.mipmap.ic_launcher);
// 設(shè)置土司的視圖圖片
toast.setView(img);
// toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0);
toast.setGravity(Gravity.CENTER, 0, 0);
}
// 顯示土司
toast.show();
/********************/
// // 自定義土司顯示位置
// // 創(chuàng)建土司
// Toast toast = new Toast(this);
// // 找到toast布局的位置
// View layout = View.inflate(this, R.layout.toast, null);
// // 設(shè)置toast文本叹侄,把設(shè)置好的布局傳進(jìn)來(lái)
// toast.setView(layout);
// // 設(shè)置土司顯示在屏幕的位置
// toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 70);
// // 顯示土司
// toast.show();
}
public void setTag(String tag) {
this.TAG = tag;
}
@Override
public void log(String msg) {
Log.i(TAG, " \n----------------------- \n " + msg + "\n----------------------- \n");
}
@Override
public Context getContext() {
return this;
}
//處理Fragment相互切換的方法
public FragmentTransaction switchFragment(int layoutId, BaseFragment targetFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (!targetFragment.isAdded()) {
//第一次使用switchFragment()時(shí)currentFragment為null巩搏,所以要判斷一下
if (currentFragment != null) {
transaction.hide(currentFragment);
}
transaction.add(layoutId, targetFragment, targetFragment.getClass().getName());
} else {
transaction.hide(currentFragment).show(targetFragment);
}
currentFragment = targetFragment;
return transaction;
}
}
BaseFragment實(shí)現(xiàn)BaseView接口如下:
public class BaseFragment extends Fragment implements BaseView {
//mvp模式的p對(duì)象
private DataPresenter dataPresenter = null;
public DataPresenter getDataPresenter() {
if (dataPresenter == null) {
dataPresenter = new DataPresenter();
}
return dataPresenter;
}
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
@Override
public void showToast(String msg) {
Toast.makeText(getActivity(),msg,Toast.LENGTH_LONG).show();
}
@Override
public void log(String msg) {
Log.i("TGH-MainActivity", " \n----------------------- \n " + msg + "\n----------------------- \n");
}
}
到這里,兩個(gè)View的基礎(chǔ)類實(shí)現(xiàn)完畢了趾代。接下來(lái)咱么寫個(gè)幾個(gè)按鈕進(jìn)行測(cè)試一下以上的封裝是否好用贯底,代碼如下:
MainActivity代碼
public class MainActivity extends BaseActivity implements PresenterViewContract.View {
private int addFragment = 0;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main_activity);
setTag("TGH-MainActivity");
getDataPresenter().attachView(this);
initView();
switchFragment(getLayoutId(), Fragment1.getInstance("name", "小小話")).commit();
}
private void initView() {
findViewById(R.id.button_1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getDataPresenter().getData_1("測(cè)試接口調(diào)用");
}
});
findViewById(R.id.button_2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getDataPresenter().getData_1("");
}
});
findViewById(R.id.button_3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addFragment++;
if (addFragment == 0) {
switchFragment(getLayoutId(), Fragment1.getInstance()).commit();
} else if (addFragment == 1) {
switchFragment(getLayoutId(), Fragment2.getInstance()).commit();
} else if (addFragment == 2) {
switchFragment(getLayoutId(), Fragment3.getInstance()).commit();
addFragment = -1;
}
}
});
}
@Override
public void onResultSucceed(String msg) {
showToast("");
log(msg);
}
@Override
public void onResultFail(int retCode, Exception exception) {
showToast("onResultFail :" + retCode);
log("onResultFail :" + retCode);
}
/**
* fragment的容器
*/
private int getLayoutId() {
return R.id.fragment_layout;
}
@Override
protected void onDestroy() {
super.onDestroy();
log("main Activity onDestroy");
getDataPresenter().detachView();
}
}
MainActivity對(duì)應(yīng)的xml如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="成功" />
<Button
android:id="@+id/button_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="失敗" />
<Button
android:id="@+id/button_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="切換" />
<RelativeLayout
android:id="@+id/fragment_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent" />
</LinearLayout>
Fragment1代碼如下:
public class Fragment1 extends BaseFragment implements PresenterViewContract.View {
private static Fragment1 fragment1 = null;
private static String mKey = "";
public static BaseFragment getInstance(String key, String msg) {
Log.i("TGH-MainActivity", "Fragment1 進(jìn)行接參數(shù)處理 " + key + " " + msg);
getInstance();
mKey = key;
Bundle bundle = new Bundle();
bundle.putString(key, msg);
getInstance().setArguments(bundle);
return getInstance();
}
public static BaseFragment getInstance() {
if (fragment1 == null) {
fragment1 = new Fragment1();
}
return fragment1;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_fragment, container, false);
TextView viewById = view.findViewById(R.id.text_view);
viewById.setText("Fragment1");
viewById.setTextColor(Color.WHITE);
log("Fragment1 初始化 " + arguments(mKey));
getDataPresenter().attachView(this);
getDataPresenter().getData_1("頁(yè)面卡片 1");
return view;
}
private String arguments(String mKey) {
Bundle arguments = getArguments();
if (arguments != null) {
return arguments.getString(mKey);
}
return null;
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {//隱藏時(shí),可以清空數(shù)據(jù)
showToast("Fragment1 隱藏");
} else {
log("Fragment1 展示 " + arguments(mKey));
showToast("Fragment1 展示");
getDataPresenter().getData_1("");
}
}
@Override
public void onResultSucceed(String msg) {
log("卡片 1 中 數(shù)據(jù)成功");
}
@Override
public void onResultFail(int retCode, Exception exception) {
log("卡片 1 中 數(shù)據(jù)失敗");
}
@Override
public void onDestroy() {
super.onDestroy();
log("onDestroy");
getDataPresenter().detachView();
}
}
Fragment1使用的xml如下撒强,F(xiàn)ragment2和Fragment3同樣使用此xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content" />
</LinearLayout>
Fragment2代碼如下:
public class Fragment2 extends BaseFragment implements PresenterViewContract.View {
private static Fragment2 fragment2 = null;
public static BaseFragment getInstance() {
if (fragment2 == null) {
fragment2 = new Fragment2();
}
return fragment2;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_fragment, container, false);
TextView viewById = view.findViewById(R.id.text_view);
viewById.setText("Fragment2");
viewById.setTextColor(Color.RED);
log("Fragment2 初始化");
getDataPresenter().attachView(this);
getDataPresenter().getData_1("頁(yè)面卡 2");
return view;
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
showToast("Fragment2 隱藏");
} else {
showToast("Fragment2 展示");
log("Fragment2 展示");
}
}
@Override
public void onResultSucceed(String msg) {
log("頁(yè)面卡 2 成功");
}
@Override
public void onResultFail(int retCode, Exception exception) {
log("頁(yè)面卡 2 失敗");
}
@Override
public void onDestroy() {
super.onDestroy();
getDataPresenter().detachView();
}
}
Fragment3代碼如下:
public class Fragment3 extends BaseFragment {
private static Fragment3 fragment3 = null;
public static BaseFragment getInstance() {
if (fragment3 == null) {
fragment3 = new Fragment3();
}
return fragment3;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_fragment, container,false);
TextView viewById = view.findViewById(R.id.text_view);
viewById.setText("Fragment3");
viewById.setTextColor(Color.YELLOW);
log("Fragment3 初始化");
return view;
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
showToast("Fragment3 隱藏");
} else {//用于刷新數(shù)據(jù)
showToast("Fragment3 展示");
log("Fragment3 展示");
}
}
}
全局提示框代碼也很簡(jiǎn)單禽捆,ProgressWaitDialog代碼如下:
/**
* 類描述:Loading的工具類
* 修改備注:
*/
public class ProgressWaitDialog extends ProgressDialog {
private Context mContext;
public ProgressWaitDialog(Context context) {
super(context, R.style.dialog_fullscreen);
mContext = context.getApplicationContext();
setCancelable(false);
}
@Override
public void show() {
if (mContext instanceof Activity && ((Activity) mContext).isFinishing()) {
return;
}
super.show();
setContentView(R.layout.progress_wait_dialog);
ImageView img = findViewById(R.id.progress_wait_dialog_image);
img.setAnimation(AnimationUtils.loadAnimation(mContext, R.anim.loading_animation));
setCancelable(true);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
cancel();
return super.onKeyDown(keyCode, event);
}
@Override
public void cancel() {
super.cancel();
dismiss();
}
@Override
public void hide() {
super.hide();
dismiss();
}
}
style中dialog_fullscreen的配置為:
<style name="dialog_fullscreen" parent="Theme.AppCompat.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowFrame">@null</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
</style>
以下是R.layout.progress_wait_dialog文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dialog_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:minWidth="180dp"
android:minHeight="60dp"
android:orientation="vertical"
android:padding="10dp">
<ImageView
android:id="@+id/progress_wait_dialog_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/progress_wait_icon" />
<TextView
android:id="@+id/progress_wait_dialog_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/please_wait"
android:textColor="@android:color/white"
tools:text="請(qǐng)稍候..." />
</LinearLayout>
R.anim.loading_animation文件放在res下的anim下,如下
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<rotate
android:duration="1000"
android:fromDegrees="0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="-1"
android:repeatMode="restart"
android:startOffset="-1"
android:toDegrees="+360" />
</set>
好飘哨,今天就到這了