Android开发经验谈Android开发Android技术知识

Android 之路 (7) - 对BaseActivity的简

2019-05-06  本文已影响55人  AndroidRookie

引言

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

正文

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

分析

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

  1. CandyBaseActivity,最基本,最底层的Base,附带通用操作的封装。
  2. CandyLoadingBaseActivity继承至CandyBaseActivity,不是所有的页面都是需要弹窗的,像弹窗需要重写很多的方法,就不适合放到最底层。
  3. MVPBaseActivity,进行MVP分层的Base,里面包含生命周期的订阅和取消订阅。

CandyBaseActivity

首先确定我们现阶段能封装什么:

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相关的代码。

CandyLoadingBaseActivity

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的封装还没有完呢?预计还有还几章的篇幅,敬请期待!

上一篇下一篇

猜你喜欢

热点阅读