Android 之路 (7) - 对BaseActivity的简
引言
终于到了BaseActivity的封装了,在本章中将对通用性的一些方法和操作进行抽取,放到Base中。
正文
先起个名字,我们的Base就叫CandyBaseActivity吧,Candy是糖果的意思,我希望这一套东西能让人像吃糖果一样的甜!
分析
在本篇中关于Base,我们需要进行两种封装:
- CandyBaseActivity,最基本,最底层的Base,附带通用操作的封装。
- CandyLoadingBaseActivity继承至CandyBaseActivity,不是所有的页面都是需要弹窗的,像弹窗需要重写很多的方法,就不适合放到最底层。
- MVPBaseActivity,进行MVP分层的Base,里面包含生命周期的订阅和取消订阅。
CandyBaseActivity
首先确定我们现阶段能封装什么:
- mActivity
- 将T.showToast封装到底层
- startActivity
- overridePendingTransition(Activity的切换动画)
- initToolbar
mActivity
每次要引用上下文都用类名的方式来指定,这就比较繁琐了。
protected Activity mActivity;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = this;
}
showToast
直接通过工具类T来调用showToast方法在原来的基础上市比较方便,但是一直需要传递一个 context
就比较让人烦躁了,所以将showToast放到Base中。代码如下:
/**
* 显示文本信息
*
* @param text 文本信息
*/
public void showToast(String text) {
T.showToast(mActivity, text);
}
/**
* 显示文本信息
*
* @param resId 文本资源id信息
*/
public void showToast(int resId) {
T.showToast(mActivity, resId);
}
showToast方法定义为public,是为了能在其他地方,如:Presenter中进行 强转
后直接调用。
overridePendingTransition(Activity的切换动画)
关于Activity的切换动画有各种各样的,根据不同的喜好有不同的做法,我这里使用的是:右滑进入、左滑退出
,其他动画自行探索。
动画xml
setup_next_in.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="100%p"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="0" >
<!--
下一页 进入
位置为100 到达0
-->
</translate>
setup_next_out.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="-100%p"
android:toYDelta="0" />
setup_pre_in.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="-100%p"
android:fromYDelta="0"
android:toYDelta="0"
android:toXDelta="0"/>
setup_pre_out.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100%p"
android:toYDelta="0" />
创建工具类:ActivityAnimUtils
/**
* Activity的跳转动画
*
* @author aohanyao create in 2018年10月04日02:36:34
*/
public class ActivityAnimUtils {
/**
* 跳转到
*
* @param activity
*/
public static void to(Activity activity) {
activity.overridePendingTransition(R.anim.setup_next_in,
R.anim.setup_next_out);
}
/**
* 退出动画
*/
public static void out(Activity activity) {
activity.overridePendingTransition(R.anim.setup_pre_in, R.anim.setup_pre_out);
}
}
CandyBaseActivity创建两个方法:
/**
* 右边划出
*/
protected void slideLeftOut() {
ActivityAnimUtils.out(mActivity);
}
/**
* 进入
*/
protected void slideRightIn() {
ActivityAnimUtils.to(mActivity);
}
在startActivity之后调用 slideRightIn()
,finish()
的时候调用 slideLeftOut()
startActivity
原本常用的方法是:startActivity(new Intent(mActivity, Target.class));
,这其中 new Intent()这一部分都是冗余的,我们可以封装一下,其中关于值的传递采用的是一个Pair<String, Object>
的可变参数,然后根据不同的类型,将数据填充到intent中。具体代码如下:
/**
* 打开 Activity
*
* @param activity
*/
protected void launchActivity(Class<? extends Activity> activity) {
startActivity(new Intent(mActivity, activity));
// 加上动画
slideRightIn();
}
/**
* 打开 Activity
*
* @param activity
*/
protected void launchActivityForResult(Class<? extends Activity> activity, int requestCode) {
startActivityForResult(new Intent(mActivity, activity), requestCode);
// 加上动画
slideRightIn();
}
/**
* 打开新的 Activity
*
* @param activity 目标Activity
* @param pairs 键值对
*/
protected void launchActivity(Class<? extends Activity> activity, Pair<String, Object>... pairs) {
Intent intent = new Intent(mActivity, activity);
// 填充数据
IntentUtils.fillIntent(intent, pairs);
startActivity(intent);
// 加上动画
slideRightIn();
}
/**
* 打开新的 Activity
*
* @param activity 目标Activity
* @param pairs 键值对
*/
protected void launchActivityForResult(Class<? extends Activity> activity, int requestCode, Pair<String, Object>... pairs) {
Intent intent = new Intent(mActivity, activity);
// 填充数据
IntentUtils.fillIntent(intent, pairs);
startActivityForResult(intent, requestCode);
// 加上动画
slideRightIn();
}
关于 fillIntent
,这个方法主要是判断参数中值的类类型,然后进行intent的填充:
/**
* Intent工具类
*/
public class IntentUtils {
/**
* 填充intent数据,暂时只写了常用的一些数据格式,不常用的没写
*
* @param intent
* @param pairs
*/
public static void fillIntent(Intent intent, Pair<String, Object>[] pairs) {
if (pairs != null) {
for (Pair<String, Object> pair : pairs) {
Object value = pair.second;
//判断不同的类型,进行强转和存放
if (value instanceof Boolean) {
intent.putExtra(pair.first, (Boolean) value);
}
if (value instanceof Byte) {
intent.putExtra(pair.first, (Byte) value);
}
if (value instanceof Short) {
intent.putExtra(pair.first, (Short) value);
}
if (value instanceof Long) {
intent.putExtra(pair.first, (Long) value);
}
if (value instanceof Float) {
intent.putExtra(pair.first, (Float) value);
}
if (value instanceof Double) {
intent.putExtra(pair.first, (Double) value);
}
if (value instanceof Integer) {
intent.putExtra(pair.first, (Integer) value);
}
if (value instanceof String) {
intent.putExtra(pair.first, (String) value);
}
if (value instanceof Parcelable) {
intent.putExtra(pair.first, (Parcelable) value);
}
if (value instanceof Serializable) {
intent.putExtra(pair.first, (Serializable) value);
}
}
}
}
}
综上,还把前面的动画用上了。
如何调用:
//普通
launchActivity(DialogExampleActivity.class);
//普通携带参数
launchActivity(ToastExampleActivityActivity.class,
new Pair<String, Object>("key1", "value1"),
new Pair<String, Object>("key1", "value1"));
//返回值
launchActivityForResult(LoginActivity.class,200);
// 返回值携带参数
launchActivityForResult(LoginActivity.class,
200,
new Pair<String, Object>("key1", "value1"),
new Pair<String, Object>("key1", "value1"));
initToolbar
关于Toolbar这里使用的是 android.support.v7.widget.Toolbar
,简单又方便,先看看我们在创建的时候系统生成的Toolbar:
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
为了以后的扩展性和能够统一的在Base中操作,要做一下操作:
- 为AppBarLayout和Toolbar定义id资源,通过统一id来管理
- 为AppBarLayout和Toolbar定义style,将id加入到style中,在xml中直接引用style
定义id
<resources>
<item name="base_toolbar" type="id" />
<item name="base_appbar" type="id" />
</resources>
定义style
<resources>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="BaseAppBarLayoutStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">44dp</item>
<item name="android:theme">@style/AppTheme.AppBarOverlay</item>
<!--这里设置 标题栏的颜色-->
<item name="android:id">@id/base_appbar</item>
</style>
<!--标题栏的样式-->
<style name="BaseToolbarStyle" parent="Widget.AppCompat.Toolbar">
<!--高度-->
<item name="android:layout_height">?attr/actionBarSize</item>
<!--id-->
<item name="android:id">@id/base_toolbar</item>
<!--宽度-->
<item name="android:layout_width">match_parent</item>
<!--背景颜色-->
<item name="android:background">?attr/colorPrimary</item>
</style>
</resources>
使用样式
将原本activity_layout的内容更改一下:
<android.support.design.widget.AppBarLayout
style="@style/BaseAppBarLayoutStyle">
<android.support.v7.widget.Toolbar
style="@style/BaseToolbarStyle"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
initToolbar
在初始化Toolbar的时候需要有一些属性是需要可配置的,需要为这些配置项创建不同的方法,让子类来进行复写:
Toolbar颜色
Toolbar返回icon
Toolbar返回事件
前面定义了一个id资源 base_toolbar
,我们可以直接在Base中通过 findViewById
来获取Toolbar。
initToolbar具体代码如下:
/**
* 是否初始化了toolbar
*/
private boolean isInitToolbar = false;
@Override
protected void onStart() {
super.onStart();
if (!isInitToolbar) {
initToolbar();
}
}
/**
* 初始化toolbar
*/
private void initToolbar() {
Toolbar mToolbar = findViewById(R.id.base_toolbar);
if (null != mToolbar) {
//设置返回按钮
setSupportActionBar(mToolbar);
mToolbar.setBackgroundColor(getToolbarBackground());
mToolbar.setNavigationIcon(getNavigationIcon());
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onNavigationOnClickListener();
}
});
isInitToolbar = true;
}
}
/**
* 获取toolbar的背景
* @return
*/
private int getToolbarBackground() {
return getResources().getColor(R.color.colorPrimary);
}
/**
* 返回按钮点击
*/
protected void onNavigationOnClickListener() {
finish();
slideLeftOut();
}
/**
* 返回按钮
*
* @return
*/
protected int getNavigationIcon() {
return R.drawable.ic_arrow_back_white_24dp;
}
子类不用管Toolbar的初始化 ,需要在xml中使用定义的style就可以完成初始化。
CandyLoadingBaseActivity
在本次CandyLoadingBaseActivity中需要封装的是 DialogHelper,让子类直接通过 show...()
的方式来显示弹窗,而不用加上 mDialogHelper
的方式。而封装的方式差不多是将DialogHelper的方法重写一遍,只不过方法体内的调用对象换成DialogHelper,具体直接看下面代码:
public class CandyLoadingBaseActivity extends CandyBaseActivity implements OnDialogCancelListener {
protected DialogHelper mDialogHelper;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mDialogHelper == null) {
mDialogHelper = new DialogHelper(mActivity, this);
}
}
/**
* 显示 loading 弹窗,默认不能点击空白处进行取消
*
* @param loadingTip 信息提示
*/
public void showLoadingDialog(String loadingTip) {
showLoadingDialog(loadingTip, true);
}
/**
* 显示 loading 弹窗
*
* @param loadingTip 信息提示
* @param cancelable 能不能点击空白的地方
*/
public void showLoadingDialog(String loadingTip, Boolean cancelable) {
mDialogHelper.showLoadingDialog(loadingTip, cancelable);
}
/**
* 信息提示弹窗
*
* @param message 提示信息的内容
*/
public void showMessageDialog(String message) {
mDialogHelper.showMessageDialog(message);
}
/**
* 信息提示弹窗
*
* @param message 提示信息的内容
* @param confirmListener 确认按钮点击的回调
*/
public void showMessageDialog(String message, OnDialogConfirmListener confirmListener) {
mDialogHelper.showMessageDialog(message, confirmListener);
}
/**
* 成功提示弹窗
*
* @param message 提示信息的内容
*/
public void showSuccessDialog(String message) {
mDialogHelper.showSuccessDialog(message);
}
/**
* 成功提示弹窗
*
* @param message 提示信息的内容
* @param confirmListener 确认按钮点击的回调
*/
public void showSuccessDialog(String message, OnDialogConfirmListener confirmListener) {
mDialogHelper.showSuccessDialog(message, confirmListener);
}
/**
* 警告提示弹窗
*
* @param message 提示信息的内容
*/
public void showWarningDialog(String message) {
mDialogHelper.showWarningDialog(message);
}
/**
* 警告提示弹窗
*
* @param message 提示信息的内容
* @param confirmListener 确认按钮点击的回调
*/
public void showWarningDialog(String message, OnDialogConfirmListener confirmListener) {
mDialogHelper.showWarningDialog(message, confirmListener);
}
/**
* 错误提示弹窗
*
* @param message 提示信息的内容
*/
public void showErrorDialog(String message) {
mDialogHelper.showErrorDialog(message);
}
/**
* 错误提示弹窗
*
* @param message 提示信息的内容
* @param confirmListener 确认按钮点击的回调
*/
public void showErrorDialog(String message, OnDialogConfirmListener confirmListener) {
mDialogHelper.showErrorDialog(message, confirmListener);
}
/**
* 显示确认弹窗
*
* @param message 提示信息
* @param confirmText 确认按钮文字
* @param cancelText 取消按钮文字
* @param confirmListener 确认按钮点击回调
* @param cancelListener 取消按钮点击回调
*/
public void showConfirmDialog(String message,
String confirmText,
String cancelText,
final OnDialogConfirmListener confirmListener,
final OnDialogCancelListener cancelListener) {
mDialogHelper.showConfirmDialog(message, confirmText, cancelText, confirmListener, cancelListener);
}
/**
* 显示确认弹窗
*
* @param message 提示信息
* @param confirmText 确认按钮文字
* @param cancelText 取消按钮文字
* @param confirmListener 确认按钮点击回调
*/
public void showConfirmDialog(String message,
String confirmText,
String cancelText,
OnDialogConfirmListener confirmListener) {
showConfirmDialog(message, confirmText, cancelText, confirmListener, null);
}
/**
* 显示确认弹窗
*
* @param message 提示信息
* @param confirmListener 确认按钮点击回调
*/
public void showConfirmDialog(String message,
OnDialogConfirmListener confirmListener) {
showConfirmDialog(message, "确定", "取消", confirmListener, null);
}
/**
* 关闭弹窗
*/
public void dismissDialog() {
mDialogHelper.dismissDialog();
}
@Override
public void onDialogCancelListener(AlertDialog dialog) {
//空实现,让子类做自己想做的事情
}
}
示例
将DialogExampleActivity继承至CandyLoadingBaseActivity,然后注释掉DialogHelper相关的代码。
CandyLoadingBaseActivityMVPBaseActivity
关于MVPBaseActivity,这里要做的封装有:
- 使用泛型 P 来表示Presenter。
- 将Dialog与Presenter进行绑定:取消Dialog的同时取消订阅、请求完成关闭Dialog。
- 将Presenter与Activity进行绑定:onDestroy的时候取消订阅,防止内存泄漏。
泛型P
abstract public class MVPBaseActivity<P extends BasePresenter> extends CandyLoadingBaseActivity {
private P mPresenter;
/**
* 底层获取P
*
* @return P
*/
protected synchronized P getP() {
if (mPresenter == null) {
mPresenter = createPresenter();
}
return mPresenter;
}
/**
* 创建Presenter
*
* @return 返回Presenter的实例
*/
protected abstract P createPresenter();
}
将需要MVP的Activity继承至MVPBaseActivity,并且传入一个BasePresenter子类的P,然后实现 createPresenter()
返回具体的实现类,最后直接通过 getP()
来调用Presenter,为了演示效果,我已经将 LoginActivity
做了更改,详情请查看源码,核心代码如下:
public class LoginActivity extends MVPBaseActivity<LoginPresenter> {
...
private void initEvent() {
...
getP().login(account, password);
...
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter(this);
}
}
绑定Dialog
前面在封装MVP的时候我们定义了一个 BaseView
,里面包含了 onComplete
方法,所以我们可以直接让MVPBaseActivity实现BaseView,核心代码如下:
@Override
public void onComplete() {
// 请求完成、关闭dialog
dismissDialog();
}
@Override
public void onDialogCancelListener(AlertDialog dialog) {
super.onDialogCancelListener(dialog);
// dialog取消,取消订阅
getP().unDisposable();
}
绑定Activity
其实就是在onDestroy的时候取消订阅一下,代码如下:
@Override
protected void onDestroy() {
super.onDestroy();
// 销毁 取消订阅
getP().unDisposable();
}
整体代码如下:
/**
* Mvp BaseActivity
*
* @param <P>
*/
abstract public class MVPBaseActivity<P extends BasePresenter> extends CandyLoadingBaseActivity
implements BaseView {
private P mPresenter;
/**
* 底层获取P
*
* @return P
*/
protected synchronized P getP() {
if (mPresenter == null) {
mPresenter = createPresenter();
}
return mPresenter;
}
/**
* 创建Presenter
*
* @return 返回Presenter的实例
*/
protected abstract P createPresenter();
@Override
public void onComplete() {
// 请求完成、关闭dialog
dismissDialog();
}
@Override
public void onDialogCancelListener(AlertDialog dialog) {
super.onDialogCancelListener(dialog);
// dialog取消,取消订阅
getP().unDisposable();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 销毁 取消订阅
getP().unDisposable();
}
@Override
public void onFailure(String message) {
// showWarningDialog(message);
// 暂不实现,后面有一篇文章:统一错误管理
}
}
结束
总结
终于完成了三个BaseActivity的封装了,可能有人有疑惑:为什么没有BaseFragment呢?其实BaseFragment和BaseActivity相差不大,只是一些方法调用的时机不一样,还有就是没用Toolbar的初始化,这里由于篇幅的问题就没用一一列出,不过我在源码中已经完成了BaseFragment的封装,可以直接查看源码。
最后
关于Base的封装还没有完呢?预计还有还几章的篇幅,敬请期待!