Kotlin 中 BaseActivity 以及 MVP 封装

image.png

前言


  • 前面几章,和大家说了 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 {
    
          /** 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  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 {
          @Component.Builder
          abstract class Builder : AndroidInjector.Builder()
      }
    
  • 注意: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

      
    
              
    
                  标题栏
    
              
    
              
    
                  
    
                  
    
                      网络异常
    
                  
    
                  
    
                      无数据
    
                  
    
                  
    
                      加载中
    
                  
    
              
    
      
    
  • 新建 BaseActivity 继承 DaggerAppCOmpatActivity (支持 Dagger2),使用泛型确定 DataBinding、MVPPresenter 类型。

      /**
       * Activity 基类
       */
      abstract class BaseActivity

    , 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() {
    
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
    
              val mFrags = ArrayList()
              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

    {} // 但是在 Kotlin 中必须使用 * 代替 abstract class BaseActivity

    , 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 语法解析,欢迎关注。
  • 有疑问的可以在评论区提问哦。

你可能感兴趣的:(Kotlin 中 BaseActivity 以及 MVP 封装)