前言
- 前面几章,和大家说了 DataBinding、Dagger2 在的配置,今天就说说在项目中如何使用吧,配合 MVP 模式对 BaseActivtiy 进行封装。(之前已经说过一遍了,是在 Java 平台下,传送门在此)
- KotlinTest Github
MVP
那么,我们就从 MVP 开始。
-
首先,项目中使用 Retrofit + RxJava2 进行网络请求,那么,我们在使用的时候就要考虑到 RxJava 的生命周期问题,如果不对它进行处理,那么在应用中进入一个 Activity、请求网络、在请求未完成返回这个流程中就会抛出异常,导致应用崩溃,于是,就有了这样的 BaseMVPPresenter。
/** * Presenter基类 * * @param V MVP View类型 继承[BaseMVPView] * @param M MVP Module 继承[BaseMVPModule] */ open class BaseMVPPresenter<V : BaseMVPView, M : BaseMVPModule> { /** MVP View 对象 */ protected var mView: V? = null /** MVP Module 对象 */ @Inject protected lateinit var mModule: M /** RxJava2 生命周期管理 */ private val disposables: CompositeDisposable = CompositeDisposable() /** * 界面绑定,关联 MVP View * * @param view MVP View */ fun attach(view: V) { mView = view } /** * 解除绑定,去除 MVP View 引用 */ fun detach() { mView = null } /** * 检查请求返回数据,并在登录状态异常时弹出提示 * * @param data 返回数据 * @param T 返回数据类型 * * @return 是否成功 */ protected fun <T : BaseEntity> checkResponse(data: T): Boolean { return data.code == Constants.ResponseCode.SUCCESS } /** * 将网络请求添加到 RxJava2 生命周期 */ protected fun addDisposable(dis: Disposable) { disposables.add(dis) } /** * 消费所有事件 */ fun dispose() { if (!disposables.isDisposed && disposables.size() > 0) { disposables.dispose() } } }
为了能够方便的进行复用,所以 BaseMVPPresenter 中使用泛型来确定 View 以及 Module 的类型。
定义的 attach() 方法在 presenter 使用前调用,绑定 View,并在 Activity 结束时使用 detach() 方法接触绑定,移除引用,调用 dispose() 方法消费所有事件。
-
在 Module 中进行网络请求以及其他耗时操作,所以有了 BaseMVPModule
/** * MVP Module基类 */ open class BaseMVPModule @Inject constructor() { @Inject lateinit var netClient: NetApi }
因为在所有 Module 中都会用到网络请求,所以将网络请求 API 在 BaseMVPModule 中声明。
-
其中 NetApi 的依赖注入可以新建一个 NetModule
/** * 网络模块依赖注入 */ @Module class NetModule { @Provides @Singleton fun netClient(): NetApi { val okHttpClient = OkHttpClient.Builder() .addInterceptor(ParametersInterceptor()) .addInterceptor(LogInterceptor()) .build() val retrofit = Retrofit.Builder() .baseUrl(UrlDefinition.BASE_URL) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build() return retrofit.create(NetApi::class.java) } }
这里的 @Singleton 注解表明使用单例模式,即 NetApi 对象是单例的。
-
然后还需要将 NetModule 添加到 ApplicationSub 中
/** * Application Dagger2 组件 */ @Singleton @Component(modules = arrayOf( ActivityModule::class, SupportFragmentModule::class, NetModule::class, AndroidSupportInjectionModule::class)) interface ApplicationSub : AndroidInjector<MyApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<MyApplication>() }
注意:Module 中使用了 @Singleton 注解,ApplicationSub 中也要添加 @Singleton 注解。
-
然后,就是 BaseMVPView
/** * MVP View基类 */ interface BaseMVPView { /** * 网络请求结束 */ fun onNetFinished() /** * 网络故障 */ fun onNetError() /** * 无数据 */ fun onNoData() /** * 加载中 */ fun onLoading() }
在 BaseMVPView 中,定义了一系列通用方法,即每个界面都会用到的方法。
BaseActivity
上面说完了 MVP 模式相关的模块基类封装,现在说说 Activity 的基类封装。
-
我们为什么要封装基类?当然是为了开发方便,防止重复的模版代码,那么首先就要把每个界面都需要的抽取出来,标题栏那是肯定的,还有加载数据的不同状态,即上面 BaseMVPView 中所定义的几个状态。把这些抽取成 layout_base.xml
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="@dimen/dp_48"> 标题栏 </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/fl_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> 网络异常 </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> 无数据 </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> 加载中 </LinearLayout> </RelativeLayout> </LinearLayout>
-
新建 BaseActivity 继承 DaggerAppCOmpatActivity (支持 Dagger2),使用泛型确定 DataBinding、MVPPresenter 类型。
/** * Activity 基类 */ abstract class BaseActivity<P : BaseMVPPresenter<*, *>, DB : ViewDataBinding> : DaggerAppCompatActivity(), BaseMVPView, RootHandler.OnTitleClickListener { /** 当前界面 Context 对象*/ protected lateinit var mContext: AppCompatActivity /** 当前界面 Presenter 对象 */ @Inject protected lateinit var presenter: P /** 根布局 DataBinding 对象 */ protected lateinit var rootBinding: LayoutBaseBinding /** 当前界面布局 DataBinding 对象 */ protected lateinit var mBinding: DB /** * 重写 onCreate() 方法,添加了 Dagger2 注入、Activity 管理以及根布局等初始化操作 */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 保存当前 Context 对象 mContext = this // 添加到 AppManager 应用管理 AppManager.addActivity(this) // 加载根布局,初始化 DataBinding rootBinding = DataBindingUtil.inflate( LayoutInflater.from(mContext), R.layout.layout_base, null, false ) // 绑定事件处理 rootBinding.handler = RootHandler(this) } /** * 重写 onDestroy() 方法,移除 Activity 管理以及 MVP 生命周期管理 */ override fun onDestroy() { // 从应用管理移除当前 Activity 对象 AppManager.removeActivity(this) // 界面销毁时,消费所有事件,清空引用 presenter.dispose() presenter.detach() super.onDestroy() } /** * 重写 setContentView(layoutResID) 方法,使其支持 DataBinding 以及标题栏、状态栏初始化操作 */ override fun setContentView(layoutResID: Int) { // 初始化标题栏 initTitleBar() // 加载布局,初始化 DataBinding mBinding = DataBindingUtil.inflate( LayoutInflater.from(mContext), layoutResID, null, false ) // 将当前布局添加到根布局 rootBinding.flContent.removeAllViews() rootBinding.flContent.addView(mBinding.root) // 设置布局 super.setContentView(rootBinding.root) // 初始化状态栏 initStatusBar() } /** * 初始化标题栏,抽象方法,子类实现标题栏自定义 */ protected abstract fun initTitleBar() /** * 初始化状态栏,默认主题色、不透明,修改需重写 */ protected fun initStatusBar() { setStatusBar() } /** * 设置状态栏,默认主题色、不透明 * * @param colorResId 状态栏颜色,默认主题色 * @param alpha 状态栏透明度,默认不透明,取值范围 0~255 */ protected fun setStatusBar(@ColorRes colorResId: Int = R.color.colorTheme, alpha: Int = 0) { if (alpha !in 0..255) { RuntimeException("The value of the alpha must between 0 and 255") } else { StatusBarUtil.setResColor(this, colorResId, alpha) } } /** * 显示标题栏 */ protected fun showTitle() { rootBinding.handler?.showTitle = true } /** * 设置标题文本 * * @param strResID 标题文本资源id */ protected fun setTitleStr(@StringRes strResID: Int) { rootBinding.handler?.showTvTitle = true rootBinding.handler?.tvTitle = getString(strResID) } /** * 设置标题文本 * * @param str 标题文本 */ protected fun setTitleStr(str: String) { rootBinding.handler?.showTvTitle = true rootBinding.handler?.tvTitle = str } /** * 设置标题栏左侧图标,默认返回按钮 * * @param resID 标题栏左侧图标资源id,默认返回按钮 */ protected fun setIvLeft(@DrawableRes resID: Int = R.mipmap.arrow_left_white) { rootBinding.handler?.showIvLeft = true rootBinding.handler?.ivLeftResID = resID } /** * 设置右侧图标 * * @param resID 图片资源id */ protected fun setIvRight(@DrawableRes resID: Int) { rootBinding.handler?.showIvRight = true rootBinding.handler?.ivRightResID = resID } /** * 设置右侧文本 * * @param strResID 文本资源id */ protected fun setTvRight(@StringRes strResID: Int) { rootBinding.handler?.showTvRight = true rootBinding.handler?.tvRight = getString(strResID) } /** * 重写BaseMvpView中方法,网络异常时调用 */ override fun onNetError() { val handler = rootBinding.handler handler?.let { if (handler.showNoData) { handler.showNoData = false } if (handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.stop() handler.showLoading = false } if (!handler.showNetError) { handler.showNetError = true } onListComplete() } } /** * 重写BaseMvpView中方法,无数据时调用 */ override fun onNoData() { val handler = rootBinding.handler handler?.let { if (handler.showNetError) { handler.showNetError = false } if (handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.stop() handler.showLoading = false } if (!handler.showNoData) { handler.showNoData = true } onListComplete() } } /** * 重写BaseMvpView中方法,加载数据时调用 */ override fun onLoading() { val handler = rootBinding.handler handler?.let { if (handler.showNetError) { handler.showNetError = false } if (handler.showNoData) { handler.showNoData = false } if (!handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.start() handler.showLoading = true } } } /** * 重写BaseMvpView中方法,网络请求结束后调用,隐藏其他界面 */ override fun onNetFinished() { val handler = rootBinding.handler handler?.let { if (handler.showNetError) { handler.showNetError = false } if (handler.showNoData) { handler.showNoData = false } if (handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.stop() handler.showLoading = false } onListComplete() } } /** * 使用SwipeToLoadView时重写,完成刷新步骤 */ protected fun onListComplete() {} /** * 标题栏左侧点击事件,默认结束当前界面 */ override fun onLeftClick() { finish() } /** * 标题栏右侧点击事件 */ override fun onRightClick() {} /** * 无数据界面点击事件,默认显示加载中 */ override fun onNoDataClick() { onLoading() } /** * 网络异常界面点击事件,默认显示加载中 */ override fun onNetErrorClick() { onLoading() } }
-
就这样完成了 BaseActivity 的封装,使用起来也很简单:
/** * 主界面 */ class MainActivity : BaseActivity<BlankPresenter, ActivityMainBinding>() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mFrags = ArrayList<Fragment>() mFrags.add(MoviesListFragment()) mBinding.vp.adapter = FragVpAdapter.Builder() .manager(supportFragmentManager) .frags(mFrags) .build() } override fun initTitleBar() { showTitle() setTitleStr("高评分电影") } }
详细代码大家可以看我的 Github项目:KotlinTest
语法解析
-
在上面的代码中有几个简单的 Kotlin 语法要和大家说明:
// Java 中,如果类声明有泛型,而在使用时不需要泛型,那么直接不声明即可 abstract class BaseActivity<P extends BaseMVPPresenter, DB extends ViewDataBinding> {} // 但是在 Kotlin 中必须使用 * 代替 abstract class BaseActivity<P : BaseMVPPresenter<*,*>, DB : ViewDataBinding> {} /* * 在 DataBinding 中,Handler 的类型是可为空的,即 var handler: Handler? * 在 Kotlin 中,调用可空类型的方法或属性必须使用安全调用 ?. */ handler?.showTvTitle = true // 等价于 if(null != handler) handler.showTvTitle = true /* * Kotlin 还提供了 let 函数,{}使用了 lambda 表达式, Kotlin 是默认支持的 */ handler?.let { dosomething... } // 等价于 if(null != handler) { dosomething } /* * Kotlin 中的类型转换使用 as 关键字,如果类型错误抛出异常 * 同时 Kotlin 也提供了安全转换 as? ,转换成功返回该类型对象,转换失败返回 null */ (drawable as? AnimationDrawable)?.start() // 等价于 Kotlin if(drawable is AnimationDrawable) { // 判断是否是该类型 drawable.start() // 使用 is 关键字判断类型,如果为 true 则自动转换为该类型使用 } // 等价于 Java if(drawable instanceof AnimationDrawable) { ((AnimationDrawable) drawable).start(); } /* * Kotlin 中有智能类型转换,即在变量声明时就赋值,可以省略声明时的类型,Kotlin 会根据赋的值确定变量的类型 */ var str = "" // 自动确定为 String 类型 val handler = mBinding.handler // 自动确定为 Handler 类型
最后
- 到这里,Kotlin 下的项目框架封装就基本完成了,接下来会给大家带来详细的 Kotlin 语法解析,欢迎关注。
- 有疑问的可以在评论区提问哦。