36

Android 之路 (7) - 对BaseActivity的简单封装

 4 years ago
source link: http://fullscreendeveloper.cn/articles/2019/05/06/1557135966652.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
  • overridePendingTransition(Activity的切换动画)
  • 创建工具类:ActivityAnimUtils
  • CandyBaseActivity创建两个方法:
  • CandyLoadingBaseActivity

引言

终于到了BaseActivity的封装了,在本章中将对通用性的一些方法和操作进行抽取,放到Base中。

正文

先起个名字,我们的Base就叫CandyBaseActivity吧,Candy是糖果的意思,我希望这一套东西能让人像吃糖果一样的甜!

分析

在本篇中关于Base,我们需要进行两种封装:

  1. CandyBaseActivity,最基本,最底层的Base,附带通用操作的封装。
  2. CandyLoadingBaseActivity继承至CandyBaseActivity,不是所有的页面都是需要弹窗的,像弹窗需要重写很多的方法,就不适合放到最底层。
  3. 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相关的代码。

AVniUzz.gif

MVPBaseActivity

关于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的封装,可以直接查看源码。

源码-tag-v0.07

最后

关于Base的封装还没有完呢?预计还有还几章的篇幅,敬请期待!

软广

一个痴心妄想想成为一个全屏(栈)工程师的程序猿。

来来,关注一下吧!

mqUrme2.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK