之前在做项目时用到了mvp模式,由于时间紧,没来得急进行抽基础类,趁现在有时间进行整理一下,并对应做下笔记,供以后方便使用。先扯一下M、V、P作用吧:
- m (Model ) : Model 层包含着具体的数据请求,数据源。
- V (View ) : Activity 和Fragment 视为View层,负责处理 UI。
- P (Presenter ) : Presenter 为业务处理层,既能调用UI逻辑,又能请求数据,该层为纯Java类,不涉及任何Android API。
M层是处理数据请求的,放到后期再说,今天先整理一下V、P两层。
如下图:
根据UI层常用的方法抽出对应的接口,封装成BaseView,代码如下:
/**
* 类描述:给页面基类提供的共用接口
*/
public interface BaseView {
/**
* 获取上下文
*
* @return 上下文
*/
Context getContext();
/**
* Show loading
*/
void showLoading();
/**
* hide loading
*/
void hideLoading();
/**
* 显示提示
*
* @param msg
*/
void showToast(String msg);
/**
* 输出打印
*
* @param msg 打印内容
*/
void log(String msg);
}
Presenter业务层要与view层进行交互,所以需要持有view层的对象,持有view对象就有可能出现内存泄漏或者空指针问题,所以在抽BasePresenter时,进行对view对象做对应的处理,因为view层可能是activity,也可能是fragment,所以此处用到了类的泛型,代码如下:
public class BasePresenter<T extends BaseView> {
/**
* 绑定的view
*/
protected T mView;
/**
* 绑定view,一般在初始化中调用该方法
*/
public void attachView(T view) {
this.mView = view;
}
/**
* 断开view,一般在onDestroy中调用
*/
public void detachView() {
mView = null;
}
/**
* 获取连接的view
*/
public T getView() {
return mView;
}
/**
* 是否与View建立连接
* 每次调用业务请求的时候都要出先调用方法检查是否与View建立连接
*/
public boolean isViewAttached() {
return mView != null;
}
}
到这就已经抽好了view和Presenter层的基类了,为了方便管理业务层与UI的交互,咱们得能使其相互通信,否则业务层怎么知道UI层的需求和状态呢,此时就用一个契约类来进行管理,此契约类中是抽出的Presenter与view交互的接口(使用时,可以根据自己的UI需求,进行相应的添加接口),即发出指令和指令回应,代码如下:
/**
* 类描述:管理Presenter与Activity交互的view的接口
* Presenter 与 View 的契约(交互)
* 注明:可以添加各个业务对应view与Presenter交互接口
*/
public interface PresenterViewContract {
//通过以下接口把与UI层相关的数据通知给UI层
interface View extends BaseView {
//接口成功
void onResultSucceed(String msg);
//接口失败(此处包括接口异常)
void onResultFail(int retCode, Exception exception);
}
interface Presenter {
//接到UI的指令,进行处理数据
void getData_1(String keyword);
}
}
下面咱们就进行对业务(Presenter)层实现,直接上代码啦
/**
* 类描述:MVP模式的P模式,用于V和M的数据交互使用
* 修改备注:
*/
public class DataPresenter extends BasePresenter<PresenterViewContract.View> implements PresenterViewContract.Presenter {
/**
* 关闭数据加载进度提示
*/
private void cancelDialog() {
if (isViewAttached()) {
getView().hideLoading();
}
}
/**
* 展示数据加载进度
*/
private void showDialog() {
if (isViewAttached()) {
getView().showLoading();
}
}
/**
* 接到UI层的指令,发起数据请求
* @param msg 假数据,msg为空代表返回失败结果;msg不为空返回成功结果
*/
@Override
public void getData_1(String msg) {
// TODO: 2020/9/15 此处接口调用属于Model层,因为公司已有自己的一套数据包处理,所以此处就用假数据代替了
if (isViewAttached()) {//判断是否关联View
if (TextUtils.isEmpty(msg)) {
cancelDialog();
//通知view层数据请求失败
getView().onResultFail(-2, null);
} else {
showDialog();
//通知View层数据请求成功
getView().onResultSucceed(msg);
}
}
}
}
到这view和Presenter的契约就签好了,那咱们就开始对activity和fragment的基础类进攻,它们需要去实现抽好的BaseView基类的接口(不一定都实现,用到那个就实现那个),代码如下:
/**
* 类描述:Activity的基类
* 修改备注:
*/
public class BaseActivity extends AppCompatActivity implements BaseView {
private String TAG = "";
private Toast toast;
private BaseFragment currentFragment = null;//(全局)
//Loading框的对象
private ProgressWaitDialog skyProgressWaitDialog;
//mvp模式的p对象
private DataPresenter dataPresenter = null;
public DataPresenter getDataPresenter() {
if (dataPresenter == null) {//初始化P,用于绑定view
dataPresenter = new DataPresenter();
}
return dataPresenter;
}
@Override
public void showLoading() {//展示全局的接口加载进度提示
if (skyProgressWaitDialog == null) {
skyProgressWaitDialog = new ProgressWaitDialog(this);
}
skyProgressWaitDialog.show();
}
@Override
public void hideLoading() {//关闭提示
if (skyProgressWaitDialog != null) {
skyProgressWaitDialog.cancel();
}
}
@Override
public void showToast(String msg) {
if (toast == null) {
// 创建土司
toast = new Toast(this);
// 设置土司显示的时间长短
toast.setDuration(Toast.LENGTH_LONG);
// 创建ImageView
ImageView img = new ImageView(this);
// 设置图片的资源路径
img.setImageResource(R.mipmap.ic_launcher);
// 设置土司的视图图片
toast.setView(img);
// toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0);
toast.setGravity(Gravity.CENTER, 0, 0);
}
// 显示土司
toast.show();
/********************/
// // 自定义土司显示位置
// // 创建土司
// Toast toast = new Toast(this);
// // 找到toast布局的位置
// View layout = View.inflate(this, R.layout.toast, null);
// // 设置toast文本,把设置好的布局传进来
// toast.setView(layout);
// // 设置土司显示在屏幕的位置
// 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()时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实现BaseView接口如下:
public class BaseFragment extends Fragment implements BaseView {
//mvp模式的p对象
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");
}
}
到这里,两个View的基础类实现完毕了。接下来咱么写个几个按钮进行测试一下以上的封装是否好用,代码如下:
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("测试接口调用");
}
});
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对应的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 进行接参数处理 " + 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("页面卡片 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) {//隐藏时,可以清空数据
showToast("Fragment1 隐藏");
} else {
log("Fragment1 展示 " + arguments(mKey));
showToast("Fragment1 展示");
getDataPresenter().getData_1("");
}
}
@Override
public void onResultSucceed(String msg) {
log("卡片 1 中 数据成功");
}
@Override
public void onResultFail(int retCode, Exception exception) {
log("卡片 1 中 数据失败");
}
@Override
public void onDestroy() {
super.onDestroy();
log("onDestroy");
getDataPresenter().detachView();
}
}
Fragment1使用的xml如下,Fragment2和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("页面卡 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("页面卡 2 成功");
}
@Override
public void onResultFail(int retCode, Exception exception) {
log("页面卡 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 {//用于刷新数据
showToast("Fragment3 展示");
log("Fragment3 展示");
}
}
}
全局提示框代码也很简单,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="请稍候..." />
</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>
好,今天就到这了